refactor image stack handling to support richer data formats and improve clarity
This commit is contained in:
+61
-26
@@ -63,7 +63,13 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
type StackEntry = { imageIds: string[], studyInstanceUID?: string };
|
||||
type StackEntry = {
|
||||
imageIds: string[],
|
||||
studyInstanceUID?: string,
|
||||
name?: string,
|
||||
caseId?: string,
|
||||
};
|
||||
|
||||
|
||||
// Add this type for clarity
|
||||
declare global {
|
||||
@@ -145,7 +151,7 @@ const ALL_MOUSE_BINDINGS = [
|
||||
|
||||
type AppProps = {
|
||||
container_id?: string;
|
||||
imageStacks: () => Promise<string[][]>;
|
||||
imageStacks: () => Promise<Array<string[] | { imageIds: string[], name?: string, caseId?: string }>>;
|
||||
autoCacheStack?: boolean; // <-- new prop
|
||||
annotationJson?: string;
|
||||
viewerState?: string;
|
||||
@@ -251,34 +257,63 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
}, [annotationJson, viewerState, container_id]);
|
||||
|
||||
// Load available stacks on mount
|
||||
useEffect(() => {
|
||||
imageStacks().then(async stacks => {
|
||||
// Try to extract StudyInstanceUID for each stack
|
||||
const stackEntries: StackEntry[] = await Promise.all(
|
||||
stacks.map(async imageIds => {
|
||||
let studyInstanceUID: string | undefined = undefined;
|
||||
try {
|
||||
// Try to fetch the first image as a blob and parse it
|
||||
const firstId = imageIds[0];
|
||||
if (firstId && firstId.startsWith("wadouri:")) {
|
||||
const url = firstId.slice(8);
|
||||
const response = await fetch(url);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
||||
studyInstanceUID = dataSet.string('x0020000d');
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors, fallback to undefined
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
imageStacks()
|
||||
.then(async (descriptors) => {
|
||||
if (!mounted) return;
|
||||
const stacksRaw = Array.isArray(descriptors) ? descriptors : [];
|
||||
const normalized: Array<StackEntry> = await Promise.all(
|
||||
stacksRaw.map(async (d) => {
|
||||
if (Array.isArray(d)) {
|
||||
return { imageIds: d as string[] };
|
||||
}
|
||||
return { imageIds, studyInstanceUID };
|
||||
const imageIds = Array.isArray((d as any).imageIds) ? (d as any).imageIds : (Array.isArray((d as any).i) ? (d as any).i : []);
|
||||
// try to preserve old studyInstanceUID extraction if needed (best-effort)
|
||||
let studyInstanceUID: string | undefined = (d as any).studyInstanceUID;
|
||||
// optional: attempt to fetch first file and extract StudyInstanceUID if dicomParser available
|
||||
if (!studyInstanceUID && imageIds.length > 0) {
|
||||
try {
|
||||
const first = imageIds[0];
|
||||
if (typeof fetch === 'function' && typeof (window as any).dicomParser !== 'undefined' && first.startsWith('wadouri:')) {
|
||||
const url = first.slice(7);
|
||||
const resp = await fetch(url);
|
||||
const buffer = await resp.arrayBuffer();
|
||||
const ds = (window as any).dicomParser.parseDicom(new Uint8Array(buffer));
|
||||
studyInstanceUID = ds.string('x0020000d');
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore UID extraction failures
|
||||
}
|
||||
}
|
||||
return {
|
||||
imageIds,
|
||||
studyInstanceUID,
|
||||
name: (d as any).name,
|
||||
caseId: (d as any).caseId,
|
||||
};
|
||||
})
|
||||
);
|
||||
setAvailableStacks(stackEntries);
|
||||
//setAvailableStacks(stacks.map(imageIds => ({ imageIds, studyInstanceUID: undefined })));
|
||||
await setLoadedImageIdsAndCacheMeta(stacks[0]);
|
||||
setViewportImageIds(Array(MAX_VIEWPORTS).fill(stacks[0] || []));
|
||||
|
||||
setAvailableStacks(normalized);
|
||||
|
||||
// Seed viewports with first stack if present (preserve old behavior)
|
||||
if (normalized[0]) {
|
||||
await setLoadedImageIdsAndCacheMeta(normalized[0].imageIds);
|
||||
setViewportImageIds(Array(MAX_VIEWPORTS).fill(normalized[0].imageIds || []));
|
||||
} else {
|
||||
setViewportImageIds(Array(MAX_VIEWPORTS).fill([]));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Failed to load imageStacks:", err);
|
||||
setAvailableStacks([]);
|
||||
setViewportImageIds(Array(MAX_VIEWPORTS).fill([]));
|
||||
});
|
||||
}, []);
|
||||
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
const handleLoadStack = async (viewportIdx: number, stackIdx: number) => {
|
||||
const stackObj = availableStacks[stackIdx];
|
||||
|
||||
@@ -10,6 +10,12 @@ import { createImageIds, availableImageIds } from "./lib/createImageIds.ts"
|
||||
const toWadouri = (arr: string[]) =>
|
||||
arr.map(url => url.startsWith("wadouri:") ? url : `wadouri:${url}`);
|
||||
|
||||
// Normalize a simple array -> descriptor object
|
||||
const toDescriptor = (arr: string[] | undefined) => {
|
||||
if (!arr) return { imageIds: [] as string[] };
|
||||
return { imageIds: toWadouri(arr) };
|
||||
};
|
||||
|
||||
export function mountDicomViewers() {
|
||||
// Test containers
|
||||
const test_containers = document.querySelectorAll(".dicom-viewer-test-root");
|
||||
@@ -28,10 +34,76 @@ export function mountDicomViewers() {
|
||||
const containers = document.querySelectorAll(".dicom-viewer-root");
|
||||
containers.forEach((container) => {
|
||||
const id = container.id || '';
|
||||
let imageStacks;
|
||||
let imageStacks: () => Promise<any[]>;
|
||||
|
||||
// New: support data-named-stacks (richer format) and fallback to data-images
|
||||
const dataNamed = container.getAttribute('data-named-stacks');
|
||||
const dataImages = container.getAttribute('data-images');
|
||||
if (dataImages) {
|
||||
|
||||
const parseToDescriptors = (parsed: any): Array<any> => {
|
||||
// Return array of descriptors: either simple string[] => { imageIds: string[] }
|
||||
// or objects: { imageIds: string[], name?, caseId? }
|
||||
if (!parsed) return [];
|
||||
|
||||
// If top-level is an array of strings -> single unnamed stack
|
||||
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "string") {
|
||||
return [toDescriptor(parsed)];
|
||||
}
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
const out: any[] = [];
|
||||
parsed.forEach(item => {
|
||||
// Case object with nested stacks: { caseId, stacks: [ { name, imageIds } ] }
|
||||
if (item && typeof item === 'object' && Array.isArray(item.stacks)) {
|
||||
const caseId = item.caseId || item.caseUID || item.case || undefined;
|
||||
item.stacks.forEach((s: any) => {
|
||||
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;
|
||||
out.push({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: s.name || s.label || undefined,
|
||||
caseId,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
out.push({
|
||||
imageIds: toWadouri(imageIds),
|
||||
name: item.name || item.label || undefined,
|
||||
caseId: item.caseId || item.caseUID || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Plain array (stack)
|
||||
if (Array.isArray(item) && item.length > 0 && typeof item[0] === "string") {
|
||||
out.push(toDescriptor(item));
|
||||
return;
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
if (dataNamed) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(dataNamed);
|
||||
} catch (e) {
|
||||
console.error("Invalid data-named-stacks JSON:", e);
|
||||
parsed = [];
|
||||
}
|
||||
const descriptors = parseToDescriptors(parsed);
|
||||
imageStacks = () => Promise.resolve(descriptors);
|
||||
} else if (dataImages) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(dataImages);
|
||||
@@ -39,6 +111,7 @@ export function mountDicomViewers() {
|
||||
console.error("Invalid data-images JSON:", e);
|
||||
parsed = [];
|
||||
}
|
||||
// Keep old behavior for data-images
|
||||
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === "string") {
|
||||
imageStacks = () => Promise.resolve([toWadouri(parsed)]);
|
||||
} else if (Array.isArray(parsed)) {
|
||||
|
||||
Reference in New Issue
Block a user