Enhance validation for DICOM viewer input data

- Added validation checks for nested stacks and stack objects to ensure imageIds are valid arrays of strings.
- Implemented console warnings for invalid entries, including nested stacks and stack objects, to aid in debugging.
- Improved error handling for unknown or invalid entries in the parsed data.
This commit is contained in:
Ross
2025-09-15 12:33:35 +01:00
parent 8de4efc3e2
commit 044057675e
2 changed files with 758 additions and 737 deletions
+37 -32
View File
@@ -2880,53 +2880,58 @@ useEffect(() => {
}} }}
> >
{availableStacks.map((stackObj, idx) => { {availableStacks.map((stackObj, idx) => {
const thisUID = stackObj.studyInstanceUID; const imageIds = (stackObj && Array.isArray((stackObj as any).imageIds)) ? (stackObj as any).imageIds : [];
const sameStudy = referenceUID && thisUID && referenceUID === thisUID; const label = (stackObj && ((stackObj as any).name || (stackObj as any).caseId))
const dotColor = sameStudy ? "#4caf50" : "#888"; ? ((stackObj as any).name || `Case: ${(stackObj as any).caseId}`)
const isEmpty = stackObj.imageIds.length === 0; : `Stack ${idx + 1}`;
const count = imageIds.length;
const invalid = !Array.isArray(imageIds) || count === 0;
return ( return (
<div <button
key={idx} key={idx}
role="option" role="option"
aria-selected={selectedStackIdx[i] === idx} aria-selected={false}
title={invalid ? `Invalid stack descriptor` : `${label}${count} image${count === 1 ? '' : 's'}${(stackObj as any)?.caseId ? ` — case ${(stackObj as any).caseId}` : ''}`}
style={{ style={{
padding: "6px 12px",
background: selectedStackIdx[i] === idx ? "#444" : "#222",
color: isEmpty ? "#aaa" : "#fff",
cursor: isEmpty ? "not-allowed" : "pointer",
display: "flex", display: "flex",
alignItems: "center", flexDirection: "column",
fontWeight: selectedStackIdx[i] === idx ? "bold" : "normal", alignItems: "flex-start",
borderBottom: "1px solid #333", width: "100%",
userSelect: "none", padding: "8px 12px",
opacity: isEmpty ? 0.6 : 1, background: "transparent",
color: "#fff",
border: "none",
textAlign: "left",
cursor: invalid ? "not-allowed" : "pointer",
}} }}
tabIndex={-1} onClick={(e) => {
onMouseDown={e => { e.stopPropagation();
e.preventDefault(); if (invalid) {
if (!isEmpty) { console.warn(`Attempted to select invalid stack at index ${idx}`, stackObj);
handleLoadStack(i, idx); return;
setOpenDropdownIdx(null);
} }
// Select the stack for this viewport
// Use the shared handler so selection behavior is consistent
handleLoadStack(i, idx).catch((err) => {
console.error("Failed to load stack:", err);
});
// Close the dropdown
setOpenDropdownIdx(null);
}} }}
> >
<span style={{ <div style={{ fontWeight: "600", fontSize: 13 }}>{label}</div>
color: dotColor, <div style={{ fontSize: 12, opacity: 0.7 }}>{count} image{count === 1 ? '' : 's'}</div>
fontWeight: "bold", </button>
marginRight: 8,
fontSize: "16px",
verticalAlign: "middle",
}}></span>
Stack {idx + 1} ({stackObj.imageIds.length} images
{isEmpty && <span style={{ color: "#f44336", marginLeft: 6 }}>(empty)</span>})
</div>
); );
})} })}
</div> </div>
)} )}
</div> </div>
</div> </div>
)} )
}
</div > </div >
); );
} }
+19 -3
View File
@@ -52,15 +52,22 @@ export function mountDicomViewers() {
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
const out: any[] = []; const out: any[] = [];
parsed.forEach(item => { parsed.forEach((item, itemIdx) => {
// Case object with nested stacks: { caseId, stacks: [ { name, imageIds } ] } // Case object with nested stacks: { caseId, stacks: [ { name, imageIds } ] }
if (item && typeof item === 'object' && Array.isArray(item.stacks)) { if (item && typeof item === "object" && Array.isArray(item.stacks)) {
const caseId = item.caseId || item.caseUID || item.case || undefined; const caseId = item.caseId || item.caseUID || item.case || undefined;
item.stacks.forEach((s: any) => { item.stacks.forEach((s: any, sIdx: number) => {
let imageIds: string[] = []; let imageIds: string[] = [];
if (Array.isArray(s)) imageIds = s; if (Array.isArray(s)) imageIds = s;
else if (Array.isArray(s.imageIds)) imageIds = s.imageIds; else if (Array.isArray(s.imageIds)) imageIds = s.imageIds;
else if (Array.isArray(s.i)) imageIds = s.i; else if (Array.isArray(s.i)) imageIds = s.i;
// Validation
if (!Array.isArray(imageIds) || imageIds.length === 0 || !imageIds.every(x => typeof x === "string")) {
console.warn(`data-named-stacks: skipping invalid nested stack at parsed[${itemIdx}].stacks[${sIdx}]`, s);
return;
}
out.push({ out.push({
imageIds: toWadouri(imageIds), imageIds: toWadouri(imageIds),
name: s.name || s.label || undefined, name: s.name || s.label || undefined,
@@ -73,6 +80,12 @@ export function mountDicomViewers() {
// Stack object: { imageIds, i, name, caseId } // Stack object: { imageIds, i, name, caseId }
if (item && typeof item === 'object' && (Array.isArray(item.imageIds) || Array.isArray(item.i))) { if (item && typeof item === 'object' && (Array.isArray(item.imageIds) || Array.isArray(item.i))) {
const imageIds = Array.isArray(item.imageIds) ? item.imageIds : item.i; const imageIds = Array.isArray(item.imageIds) ? item.imageIds : item.i;
if (!Array.isArray(imageIds) || imageIds.length === 0 || !imageIds.every(x => typeof x === "string")) {
console.warn(`data-named-stacks: skipping invalid stack object at parsed[${itemIdx}]`, item);
return;
}
out.push({ out.push({
imageIds: toWadouri(imageIds), imageIds: toWadouri(imageIds),
name: item.name || item.label || undefined, name: item.name || item.label || undefined,
@@ -86,6 +99,9 @@ export function mountDicomViewers() {
out.push(toDescriptor(item)); out.push(toDescriptor(item));
return; return;
} }
// Unknown / invalid entry
console.warn(`data-named-stacks: unknown or invalid entry at parsed[${itemIdx}] — skipping`, item);
}); });
return out; return out;
} }