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:
+53
-48
@@ -257,7 +257,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
}, [annotationJson, viewerState, container_id]);
|
||||
|
||||
// Load available stacks on mount
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
imageStacks()
|
||||
@@ -313,7 +313,7 @@ useEffect(() => {
|
||||
});
|
||||
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
const handleLoadStack = async (viewportIdx: number, stackIdx: number) => {
|
||||
const stackObj = availableStacks[stackIdx];
|
||||
@@ -613,8 +613,8 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
|
||||
// --- Load from localStorage on mount ---
|
||||
useEffect(() => {
|
||||
// --- Load from localStorage on mount ---
|
||||
useEffect(() => {
|
||||
try {
|
||||
const mouse = localStorage.getItem(MOUSE_SETTINGS_KEY);
|
||||
const ctrl = localStorage.getItem(CTRL_MOUSE_SETTINGS_KEY);
|
||||
@@ -623,20 +623,20 @@ useEffect(() => {
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
// --- Save to localStorage when changed ---
|
||||
useEffect(() => {
|
||||
// --- Save to localStorage when changed ---
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(MOUSE_SETTINGS_KEY, JSON.stringify(mouseToolBindings));
|
||||
} catch (e) {}
|
||||
}, [mouseToolBindings]);
|
||||
} catch (e) { }
|
||||
}, [mouseToolBindings]);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(CTRL_MOUSE_SETTINGS_KEY, JSON.stringify(ctrlMouseToolBindings));
|
||||
} catch (e) {}
|
||||
}, [ctrlMouseToolBindings]);
|
||||
} catch (e) { }
|
||||
}, [ctrlMouseToolBindings]);
|
||||
|
||||
useEffect(() => {
|
||||
window.loadDicomStackFromFiles = (files: FileList | File[]) => {
|
||||
@@ -2880,61 +2880,66 @@ useEffect(() => {
|
||||
}}
|
||||
>
|
||||
{availableStacks.map((stackObj, idx) => {
|
||||
const thisUID = stackObj.studyInstanceUID;
|
||||
const sameStudy = referenceUID && thisUID && referenceUID === thisUID;
|
||||
const dotColor = sameStudy ? "#4caf50" : "#888";
|
||||
const isEmpty = stackObj.imageIds.length === 0;
|
||||
const imageIds = (stackObj && Array.isArray((stackObj as any).imageIds)) ? (stackObj as any).imageIds : [];
|
||||
const label = (stackObj && ((stackObj as any).name || (stackObj as any).caseId))
|
||||
? ((stackObj as any).name || `Case: ${(stackObj as any).caseId}`)
|
||||
: `Stack ${idx + 1}`;
|
||||
const count = imageIds.length;
|
||||
const invalid = !Array.isArray(imageIds) || count === 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
key={idx}
|
||||
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={{
|
||||
padding: "6px 12px",
|
||||
background: selectedStackIdx[i] === idx ? "#444" : "#222",
|
||||
color: isEmpty ? "#aaa" : "#fff",
|
||||
cursor: isEmpty ? "not-allowed" : "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontWeight: selectedStackIdx[i] === idx ? "bold" : "normal",
|
||||
borderBottom: "1px solid #333",
|
||||
userSelect: "none",
|
||||
opacity: isEmpty ? 0.6 : 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
width: "100%",
|
||||
padding: "8px 12px",
|
||||
background: "transparent",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
textAlign: "left",
|
||||
cursor: invalid ? "not-allowed" : "pointer",
|
||||
}}
|
||||
tabIndex={-1}
|
||||
onMouseDown={e => {
|
||||
e.preventDefault();
|
||||
if (!isEmpty) {
|
||||
handleLoadStack(i, idx);
|
||||
setOpenDropdownIdx(null);
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (invalid) {
|
||||
console.warn(`Attempted to select invalid stack at index ${idx}`, stackObj);
|
||||
return;
|
||||
}
|
||||
// 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={{
|
||||
color: dotColor,
|
||||
fontWeight: "bold",
|
||||
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 style={{ fontWeight: "600", fontSize: 13 }}>{label}</div>
|
||||
<div style={{ fontSize: 12, opacity: 0.7 }}>{count} image{count === 1 ? '' : 's'}</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
// App root
|
||||
<div
|
||||
ref={appRootRef}
|
||||
@@ -3634,7 +3639,7 @@ useEffect(() => {
|
||||
{/* ...rest of your UI... */}
|
||||
|
||||
</div >
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,22 @@ export function mountDicomViewers() {
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
const out: any[] = [];
|
||||
parsed.forEach(item => {
|
||||
parsed.forEach((item, itemIdx) => {
|
||||
// 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;
|
||||
item.stacks.forEach((s: any) => {
|
||||
item.stacks.forEach((s: any, sIdx: number) => {
|
||||
let imageIds: string[] = [];
|
||||
if (Array.isArray(s)) imageIds = s;
|
||||
else if (Array.isArray(s.imageIds)) imageIds = s.imageIds;
|
||||
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({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: s.name || s.label || undefined,
|
||||
@@ -73,6 +80,12 @@ export function mountDicomViewers() {
|
||||
// Stack object: { imageIds, i, name, caseId }
|
||||
if (item && typeof item === 'object' && (Array.isArray(item.imageIds) || Array.isArray(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({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: item.name || item.label || undefined,
|
||||
@@ -86,6 +99,9 @@ export function mountDicomViewers() {
|
||||
out.push(toDescriptor(item));
|
||||
return;
|
||||
}
|
||||
|
||||
// Unknown / invalid entry
|
||||
console.warn(`data-named-stacks: unknown or invalid entry at parsed[${itemIdx}] — skipping`, item);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user