get legacy annotation import working

This commit is contained in:
Ross
2025-06-03 15:45:11 +01:00
parent 2518da4413
commit 8c218d42f4
3 changed files with 518 additions and 341 deletions
+9 -3
View File
@@ -7,10 +7,16 @@
<title>Vite + React + TS</title> <title>Vite + React + TS</title>
</head> </head>
<body> <body>
<div id="root" class="dicom-viewer-test-root" style="max-height:500px; height:500px; overflow:auto;"></div> <!-- <div id="root" class="dicom-viewer-test-root" style="max-height:500px; height:500px; overflow:auto;"></div> -->
<div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;" <div id="root" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;"
data-images="[&quot;http://localhost:8000/media/atlas/dicom/4596ea7654b7a5074d2c3f40039c556b11fb01ae.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/b2fa301bc731d15be3e000402c9a79c114d17aa5.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/66310ef801fbecb37c5ced160c450dfd5ecc6d2d.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/63d2d117bf256a76a61190066f91eda6c4d67c8f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/181a3575ea7fb8bf8f6449ff61e6e369a3247454.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/7c5c0538339266ac8897fa21c481b2f61172068e.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1681410681308af7111af6644fed598fde304b96.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/4adcd3d7a64a9c59b530f730ce0d991bf7f27813.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/671e6004c7fb402a627a2282bbf304d7b4dcc12b.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/8d18ac56914b9de72b8aecced09654ec4b2c9bb7.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/74ce708a90dff0636fc4bc914fe47e8b6d9320bd.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/a96b6c9900570e076edf849a0ae3b52734ed2650.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/0183718d9336458478ec42130395d78e01492bd6.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/52eee968bbce5590274346eed43f1837f9daba11.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/961449f88197566c4643fcfd20f48ea4e810490b.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1a16ad5b25e11739eadbcb8ea461072b100113a0.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/b89ddcc3f499059d2dedb0028a2bb1ce4abf5574.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/c1379c03a4e449c2fa88784d601cb766af907739.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/4fb532d5003c59a73f7e4248f365b1a8660ea710.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/e861b0de042a1f213cbccc995679aacd29c7ef53.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1a854282afe264c3afff6a6e287cd646b2193a0c.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/f59d6d9bc33160abad3af8e2b246179b3574d884.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/04fadab0ed8b3f1925e601c4f9cfb792c95626c2.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/d45d0b12fb650e65daeeee4e5fa3b21c988597f6.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/e19c47b7787ac45dbc225319dfa143dc2c1fdf1f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/dccabf3103a5be254a45457ce2d8d9ce9cb19a6a.dcm&quot;]" data-images="[&quot;http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm&quot;]"
data-auto-cache-stack="true"
></div> ></div>
<!-- <div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;"
data-images="[&quot;http://localhost:8000/media/atlas/dicom/4596ea7654b7a5074d2c3f40039c556b11fb01ae.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/b2fa301bc731d15be3e000402c9a79c114d17aa5.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/66310ef801fbecb37c5ced160c450dfd5ecc6d2d.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/63d2d117bf256a76a61190066f91eda6c4d67c8f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/181a3575ea7fb8bf8f6449ff61e6e369a3247454.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/7c5c0538339266ac8897fa21c481b2f61172068e.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1681410681308af7111af6644fed598fde304b96.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/4adcd3d7a64a9c59b530f730ce0d991bf7f27813.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/671e6004c7fb402a627a2282bbf304d7b4dcc12b.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/8d18ac56914b9de72b8aecced09654ec4b2c9bb7.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/74ce708a90dff0636fc4bc914fe47e8b6d9320bd.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/a96b6c9900570e076edf849a0ae3b52734ed2650.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/0183718d9336458478ec42130395d78e01492bd6.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/52eee968bbce5590274346eed43f1837f9daba11.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/961449f88197566c4643fcfd20f48ea4e810490b.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1a16ad5b25e11739eadbcb8ea461072b100113a0.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/b89ddcc3f499059d2dedb0028a2bb1ce4abf5574.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/c1379c03a4e449c2fa88784d601cb766af907739.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/4fb532d5003c59a73f7e4248f365b1a8660ea710.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/e861b0de042a1f213cbccc995679aacd29c7ef53.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/1a854282afe264c3afff6a6e287cd646b2193a0c.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/f59d6d9bc33160abad3af8e2b246179b3574d884.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/04fadab0ed8b3f1925e601c4f9cfb792c95626c2.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/d45d0b12fb650e65daeeee4e5fa3b21c988597f6.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/e19c47b7787ac45dbc225319dfa143dc2c1fdf1f.dcm&quot;, &quot;http://localhost:8000/media/atlas/dicom/dccabf3103a5be254a45457ce2d8d9ce9cb19a6a.dcm&quot;]"
data-auto-cache-stack="true"
></div> -->
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
<input type="file" id="dicomInput" multiple /> <input type="file" id="dicomInput" multiple />
+182 -16
View File
@@ -21,9 +21,28 @@ import { NoLabelArrowAnnotateTool } from "./NoLabelArrowAnnotateTool";
// Extend the Window interface to include cornerstoneTools // Extend the Window interface to include cornerstoneTools
declare global { declare global {
interface Window { interface Window {
cornerstoneTools: typeof cornerstoneTools; cornerstone3dTools: typeof cornerstoneTools;
} }
} }
function pixelToWorld(imageId: string, point: { x: number, y: number }) {
const plane = metaData.get("imagePlaneModule", imageId);
if (!plane) return undefined;
const { rowCosines, columnCosines, imagePositionPatient, rowPixelSpacing, columnPixelSpacing } = plane;
if (!rowCosines || !columnCosines || !imagePositionPatient) return undefined;
// Default to 1 if spacing is missing
const dx = columnPixelSpacing || 1;
const dy = rowPixelSpacing || 1;
// Convert cosines to vectors
const rc = rowCosines;
const cc = columnCosines;
const ipp = imagePositionPatient;
// World = IPP + x*col*cos*spacing + y*row*cos*spacing
return [
ipp[0] + point.x * cc[0] * dx + point.y * rc[0] * dy,
ipp[1] + point.x * cc[1] * dx + point.y * rc[1] * dy,
ipp[2] + point.x * cc[2] * dx + point.y * rc[2] * dy,
];
}
// Add to global types: // Add to global types:
declare global { declare global {
@@ -32,6 +51,8 @@ declare global {
importViewerState?: (json: string) => void; importViewerState?: (json: string) => void;
exportAnnotations?: () => string; exportAnnotations?: () => string;
importAnnotations?: (json: string) => void; importAnnotations?: (json: string) => void;
importLegacyAnnotations?: (json: string) => void;
importLegacyViewport?: (json: string) => void;
} }
} }
@@ -50,7 +71,7 @@ declare global {
} }
} }
window.cornerstoneTools = cornerstoneTools; window.cornerstone3dTools = cornerstoneTools;
type ViewportDiv = HTMLDivElement & { type ViewportDiv = HTMLDivElement & {
@@ -116,11 +137,12 @@ const ALL_MOUSE_BINDINGS = [
type AppProps = { type AppProps = {
container_id?: string; container_id?: string;
imageStacks: () => Promise<string[][]>; imageStacks: () => Promise<string[][]>;
autoCacheStack?: boolean; // <-- new prop
// ...other props if needed // ...other props if needed
}; };
// App definintion // App definintion
function App({ container_id, imageStacks, ...props }: AppProps) { function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps) {
const elementRef = useRef<HTMLDivElement>(null) const elementRef = useRef<HTMLDivElement>(null)
const running = useRef(false) const running = useRef(false)
@@ -175,7 +197,7 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
); );
setAvailableStacks(stackEntries); setAvailableStacks(stackEntries);
//setAvailableStacks(stacks.map(imageIds => ({ imageIds, studyInstanceUID: undefined }))); //setAvailableStacks(stacks.map(imageIds => ({ imageIds, studyInstanceUID: undefined })));
setLoadedImageIdsAndCacheMeta(stacks[0]); await setLoadedImageIdsAndCacheMeta(stacks[0]);
setViewportImageIds(Array(MAX_VIEWPORTS).fill(stacks[0] || [])); setViewportImageIds(Array(MAX_VIEWPORTS).fill(stacks[0] || []));
}); });
}, []); }, []);
@@ -214,6 +236,7 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
const [showAdvancedCtrlMouseBindings, setShowAdvancedCtrlMouseBindings] = useState(false); const [showAdvancedCtrlMouseBindings, setShowAdvancedCtrlMouseBindings] = useState(false);
const sortViewportImageIdsBySliceLocation = () => { const sortViewportImageIdsBySliceLocation = () => {
console.log("Sorting viewport imageIds by slice location");
setViewportImageIds(prev => { setViewportImageIds(prev => {
const next = prev.map((imageIds, idx) => { const next = prev.map((imageIds, idx) => {
if (!imageIds || imageIds.length === 0) return imageIds; if (!imageIds || imageIds.length === 0) return imageIds;
@@ -255,9 +278,13 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
// Wrap setLoadedImageIds to also cache DICOM metadata // Wrap setLoadedImageIds to also cache DICOM metadata
const setLoadedImageIdsAndCacheMeta = async (imageIds: string[]) => { const setLoadedImageIdsAndCacheMeta = async (imageIds: string[]) => {
console.log("Setting loaded imageIds:", imageIds);
console.log("autoCacheStack:", autoCacheStack);
const loadPromises: Promise<any>[] = []; const loadPromises: Promise<any>[] = [];
if (autoCacheStack) {
for (const imageId of imageIds) { for (const imageId of imageIds) {
console.log("Caching metadata for imageId:", imageId);
if (imageId.startsWith("wadouri:")) { if (imageId.startsWith("wadouri:")) {
try { try {
const url = imageId.slice(8); const url = imageId.slice(8);
@@ -286,8 +313,10 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
// Trigger sort if enabled // Trigger sort if enabled
if (orderBySliceLocation) { if (orderBySliceLocation) {
console.log("Sorting viewport imageIds by slice location");
sortViewportImageIdsBySliceLocation(); sortViewportImageIdsBySliceLocation();
} }
}
}; };
@@ -667,7 +696,7 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
// --- In your setup function, use the correct tool group per viewport --- // --- In your setup function, use the correct tool group per viewport ---
let mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID); let mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
if (!mainToolGroup) { if (!mainToolGroup) {
mainToolGroup = setupTools(MAIN_TOOL_GROUP_ID).mainToolGroup; mainToolGroup = setupTools(MAIN_TOOL_GROUP_ID)
} }
@@ -1249,12 +1278,149 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
} }
}; };
window[`importLegacyAnnotations_${apiKey}`] = (json: string) => {
let legacyData: any;
try {
legacyData = typeof json === "string" ? JSON.parse(json) : json;
} catch (e) {
alert("Invalid legacy annotation JSON");
return;
}
// Remove all existing annotations
if (cornerstoneTools.annotation.state.removeAllAnnotations) {
cornerstoneTools.annotation.state.removeAllAnnotations();
}
const toolNameMap: Record<string, string> = {
ArrowAnnotate: "ArrowAnnotate",
// Add more mappings if needed
};
Object.entries(legacyData).forEach(([imageId, tools]) => {
Object.entries(tools as any).forEach(([legacyTool, toolData]: [string, any]) => {
const cs3Tool = toolNameMap[legacyTool] || legacyTool;
if (toolData.data && Array.isArray(toolData.data)) {
toolData.data.forEach((ann: any) => {
// Convert pixel handles to world coordinates
const startWorld = pixelToWorld(imageId, { x: ann.handles.start.y, y: ann.handles.start.x });
const endWorld = pixelToWorld(imageId, { x: ann.handles.end.y, y: ann.handles.end.x });
console.log("Importing legacy annotation:")
console.log(" Image ID:", imageId);
console.log(" Start World:", startWorld);
console.log(" End World:", endWorld);
// Get spatial metadata
const plane = metaData.get("imagePlaneModule", imageId);
console.log(metaData);
let FrameOfReferenceUID = metaData.get("frameOfReferenceUID", imageId);
if (!FrameOfReferenceUID && plane && plane.frameOfReferenceUID) {
FrameOfReferenceUID = plane.frameOfReferenceUID;
}
const viewPlaneNormal = plane?.rowCosines && plane?.columnCosines
? [
plane.rowCosines[1] * plane.columnCosines[2] - plane.rowCosines[2] * plane.columnCosines[1],
plane.rowCosines[2] * plane.columnCosines[0] - plane.rowCosines[0] * plane.columnCosines[2],
plane.rowCosines[0] * plane.columnCosines[1] - plane.rowCosines[1] * plane.columnCosines[0],
]
: undefined;
const annotation = {
annotationUID: ann.uuid || cornerstoneTools.utilities.uuidv4(),
metadata: {
toolName: cs3Tool,
referencedImageId: imageId,
FrameOfReferenceUID,
viewPlaneNormal,
// Optionally add viewUp, cameraFocalPoint, etc.
},
data: {
handles: {
points: [startWorld, endWorld],
textBox: ann.handles.textBox,
},
text: ann.text || "",
// Add more fields as needed
},
visibility: ann.visible !== false,
active: ann.active || false,
};
cornerstoneTools.annotation.state.addAnnotation(annotation, annotation.annotationUID);
});
}
});
});
if (renderingEngineRef.current) {
renderingEngineRef.current.render();
}
};
// Import and apply a legacy viewport state to the active viewport
window[`importLegacyViewport_${apiKey}`] = (json: string) => {
let legacy: any;
try {
legacy = typeof json === "string" ? JSON.parse(json) : json;
} catch (e) {
alert("Invalid legacy viewport JSON");
return;
}
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return;
const viewportId = `CT_${activeViewport ?? 0}`;
const viewport = renderingEngine.getViewport(viewportId);
if (!viewport) return;
// Map legacy fields to Cornerstone3D properties
const props: any = {};
if (legacy.voi) {
props.voiRange = {
lower: legacy.voi.windowCenter - legacy.voi.windowWidth / 2,
upper: legacy.voi.windowCenter + legacy.voi.windowWidth / 2,
};
}
if (typeof legacy.invert === "boolean") props.invert = legacy.invert;
if (typeof legacy.pixelReplication === "boolean") props.interpolationType = legacy.pixelReplication ? 0 : 1;
if (typeof legacy.rotation === "number") props.rotation = legacy.rotation;
if (typeof legacy.hflip === "boolean") props.hflip = legacy.hflip;
if (typeof legacy.vflip === "boolean") props.vflip = legacy.vflip;
if (typeof viewport.setProperties === "function") {
viewport.setProperties(props);
}
// Apply translation and scale (pan/zoom)
if (legacy.translation || legacy.scale) {
const camera = viewport.getCamera ? viewport.getCamera() : {};
if (legacy.translation) {
camera.translation = { ...camera.translation, ...legacy.translation };
}
if (legacy.scale) {
camera.parallelScale = 1 / legacy.scale;
}
if (typeof viewport.setCamera === "function") {
viewport.setCamera(camera);
}
}
viewport.render();
updateOverlay(activeViewport ?? 0, viewport);
};
// Cleanup // Cleanup
return () => { return () => {
window[`exportViewerState_${apiKey}`] = undefined; window[`exportViewerState_${apiKey}`] = undefined;
window[`importViewerState_${apiKey}`] = undefined; window[`importViewerState_${apiKey}`] = undefined;
window[`exportAnnotations_${apiKey}`] = undefined; window[`exportAnnotations_${apiKey}`] = undefined;
window[`importAnnotations_${apiKey}`] = undefined; window[`importAnnotations_${apiKey}`] = undefined;
window[`importLegacyAnnotations_${apiKey}`] = undefined;
window[`importLegacyViewport_${apiKey}`] = undefined;
}; };
}, [ }, [
viewportGrid, viewportGrid,
@@ -1279,7 +1445,7 @@ function App({ container_id, imageStacks, ...props }: AppProps) {
const numVisible = viewportGrid.rows * viewportGrid.cols; const numVisible = viewportGrid.rows * viewportGrid.cols;
const viewports = []; const viewports = [];
//for (let i = 0; i < MAX_VIEWPORTS; i++) { //for (let i = 0; i < MAX_VIEWPORTS; i++) {
for (let i = 0; i < numVisible; i++) { for (let i = 0; i < numVisible; i++) {
// Determine if this viewport should be visible in the current grid // Determine if this viewport should be visible in the current grid
const isVisible = i < viewportGrid.rows * viewportGrid.cols; const isVisible = i < viewportGrid.rows * viewportGrid.cols;
@@ -1651,8 +1817,8 @@ for (let i = 0; i < numVisible; i++) {
idx < viewportImageIds[i].length - 1 idx < viewportImageIds[i].length - 1
? "1px solid #222" ? "1px solid #222"
: "none", : "none",
cursor: loaded ? "pointer" : "default", pointerEvents: "auto",
pointerEvents: loaded ? "auto" : "none", cursor: "pointer",
}} }}
title={ title={
isCurrent isCurrent
@@ -1683,7 +1849,7 @@ for (let i = 0; i < numVisible; i++) {
</div> </div>
</div> </div>
)} )}
{isVisible && ( {isVisible && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@@ -1840,7 +2006,7 @@ for (let i = 0; i < numVisible; i++) {
)} )}
</div> </div>
</div> </div>
)} )}
</div> </div>
); );
} }
@@ -1850,8 +2016,8 @@ for (let i = 0; i < numVisible; i++) {
return ( return (
// App root // App root
<div style={{ <div style={{
position: "absolute", position: "relative",
inset: 0, inset: 0,
width: "100%", width: "100%",
height: "100%", height: "100%",
@@ -1859,7 +2025,7 @@ for (let i = 0; i < numVisible; i++) {
background: "#222", background: "#222",
padding: "12px", padding: "12px",
overflow: "hidden", overflow: "hidden",
}}> }}>
<div> <div>
{/* Side menu toggle button */} {/* Side menu toggle button */}
<button <button
@@ -1888,7 +2054,7 @@ for (let i = 0; i < numVisible; i++) {
</button> </button>
{/* Side menu drawer */} {/* Side menu drawer */}
{sideMenuOpen && ( {sideMenuOpen && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@@ -2056,7 +2222,7 @@ for (let i = 0; i < numVisible; i++) {
</div> </div>
</div> </div>
)} )}
</div> </div>
{/* Viewport grid menu at top center */} {/* Viewport grid menu at top center */}
@@ -2515,5 +2681,5 @@ function setupTools(MAIN_TOOL_GROUP_ID) {
mainToolGroup.addTool(ReferenceCursors.toolName); mainToolGroup.addTool(ReferenceCursors.toolName);
} }
return { mainToolGroup }; return mainToolGroup;
} }
+5
View File
@@ -51,10 +51,15 @@ containers.forEach((container) => {
imageStacks = () => Promise.resolve([[]]); imageStacks = () => Promise.resolve([[]]);
} }
// Read autoCacheStack from data-auto-cache-stack attribute
const autoCacheStackAttr = container.getAttribute('data-auto-cache-stack');
const autoCacheStack = autoCacheStackAttr === "true" || autoCacheStackAttr === "1";
createRoot(container).render( createRoot(container).render(
<App <App
container_id={id} container_id={id}
imageStacks={imageStacks} imageStacks={imageStacks}
autoCacheStack={autoCacheStack}
/> />
); );
}); });