fix some multiview stuff
This commit is contained in:
+120
-113
@@ -12,6 +12,7 @@ import * as cornerstoneTools from '@cornerstonejs/tools';
|
|||||||
import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
|
import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
|
||||||
import { segmentation, Enums as csEnums } from '@cornerstonejs/tools';
|
import { segmentation, Enums as csEnums } from '@cornerstonejs/tools';
|
||||||
import { voi } from "@cornerstonejs/tools/utilities"
|
import { voi } from "@cornerstonejs/tools/utilities"
|
||||||
|
import { readDicomElementExplicit } from "dicom-parser"
|
||||||
|
|
||||||
|
|
||||||
const { MouseBindings, KeyboardBindings } = csToolsEnums;
|
const { MouseBindings, KeyboardBindings } = csToolsEnums;
|
||||||
@@ -73,6 +74,10 @@ function App() {
|
|||||||
windowWidth: "",
|
windowWidth: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const MAX_VIEWPORTS = 9;
|
||||||
|
const renderingEngineId = "mainRenderingEngine";
|
||||||
|
const renderingEngineRef = useRef<any>(null);
|
||||||
|
|
||||||
// Store imageIds globally after loading so they can be reused
|
// Store imageIds globally after loading so they can be reused
|
||||||
const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null);
|
const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null);
|
||||||
|
|
||||||
@@ -82,7 +87,6 @@ function App() {
|
|||||||
|
|
||||||
// Store refs for each viewport
|
// Store refs for each viewport
|
||||||
const viewportRefs = useRef<Array<HTMLDivElement | null>>([])
|
const viewportRefs = useRef<Array<HTMLDivElement | null>>([])
|
||||||
const renderingEnginesRef = useRef<Record<string, any>>({});
|
|
||||||
|
|
||||||
// Store the properties and scroll position of existing viewports before grid change
|
// Store the properties and scroll position of existing viewports before grid change
|
||||||
const prevViewportProps = useRef<Record<number, any>>({});
|
const prevViewportProps = useRef<Record<number, any>>({});
|
||||||
@@ -224,150 +228,139 @@ function App() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
function saveVisibleViewportState() {
|
||||||
const setup = async () => {
|
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
|
||||||
// 1. Save properties and scroll position for only the existing viewports
|
|
||||||
const prevCount = Object.keys(renderingEnginesRef.current).length;
|
|
||||||
for (let i = 0; i < prevCount; i++) {
|
|
||||||
const renderingEngineId = `renderingEngine_${i}`;
|
|
||||||
const viewportId = `CT_${i}`;
|
const viewportId = `CT_${i}`;
|
||||||
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
|
const renderingEngine = renderingEngineRef.current;
|
||||||
if (renderingEngine) {
|
if (renderingEngine) {
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
if (viewport) {
|
if (viewport && typeof viewport.getProperties === "function") {
|
||||||
if (typeof viewport.getProperties === "function") {
|
|
||||||
console.log("Saving previous state for viewport", i, viewport.getProperties())
|
|
||||||
prevViewportProps.current[i] = viewport.getProperties();
|
prevViewportProps.current[i] = viewport.getProperties();
|
||||||
}
|
}
|
||||||
if (typeof viewport.getCurrentImageIdIndex === "function") {
|
if (viewport && typeof viewport.getCurrentImageIdIndex === "function") {
|
||||||
prevScrollIndices.current[i] = viewport.getCurrentImageIdIndex();
|
prevScrollIndices.current[i] = viewport.getCurrentImageIdIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (running.current) {
|
useEffect(() => {
|
||||||
if (loadedImageIds) {
|
const setup = async () => {
|
||||||
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
|
// 1. Initialize Cornerstone3D and tools ONCE
|
||||||
const renderingEngineId = `renderingEngine_${i}`;
|
if (!running.current) {
|
||||||
const viewportId = `CT_${i}`;
|
|
||||||
const element = viewportRefs.current[i];
|
|
||||||
if (!element) continue;
|
|
||||||
|
|
||||||
let renderingEngine = renderingEnginesRef.current[renderingEngineId];
|
|
||||||
if (!renderingEngine) {
|
|
||||||
renderingEngine = new RenderingEngine(renderingEngineId);
|
|
||||||
renderingEnginesRef.current[renderingEngineId] = renderingEngine;
|
|
||||||
}
|
|
||||||
const viewportInput = {
|
|
||||||
viewportId,
|
|
||||||
type: Enums.ViewportType.STACK,
|
|
||||||
element,
|
|
||||||
};
|
|
||||||
renderingEngine.enableElement(viewportInput);
|
|
||||||
|
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
|
||||||
toolGroup?.addViewport(viewportId, renderingEngineId);
|
|
||||||
|
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
|
||||||
viewport.setStack(loadedImageIds);
|
|
||||||
|
|
||||||
|
|
||||||
renderingEngine.resize(true, true)
|
|
||||||
|
|
||||||
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element);
|
|
||||||
viewport.resize();
|
|
||||||
viewport.render();
|
|
||||||
|
|
||||||
// Restore previous state for existing viewports only
|
|
||||||
if (prevViewportProps.current[i] && typeof viewport.setProperties === "function") {
|
|
||||||
console.log("Restoring properties for viewport", i, prevViewportProps.current[i])
|
|
||||||
console.log(viewport)
|
|
||||||
viewport.setProperties(prevViewportProps.current[i]);
|
|
||||||
}
|
|
||||||
// Restore previous scroll position for existing viewports only
|
|
||||||
if (
|
|
||||||
prevScrollIndices.current[i] !== undefined &&
|
|
||||||
typeof viewport.setImageIdIndex === "function"
|
|
||||||
) {
|
|
||||||
viewport.setImageIdIndex(prevScrollIndices.current[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOverlay(i, viewport);
|
|
||||||
|
|
||||||
// Attach overlay update for each viewport
|
|
||||||
viewport.element.addEventListener(Enums.Events.IMAGE_RENDERED, () => updateOverlay(i, viewport));
|
|
||||||
// Initial overlay update
|
|
||||||
updateOverlay(i, viewport);
|
|
||||||
}
|
|
||||||
applyMouseToolBindings();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
running.current = true;
|
running.current = true;
|
||||||
|
|
||||||
await csRenderInit();
|
await csRenderInit();
|
||||||
await csToolsInit();
|
await csToolsInit();
|
||||||
dicomImageLoaderInit({ maxWebWorkers: 1 });
|
dicomImageLoaderInit({ maxWebWorkers: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
const toolGroup = setupTools();
|
// 2. Load image IDs if not already loaded
|
||||||
const imageIds = await createImageIdsAndCacheMetaData();
|
let imageIds = loadedImageIds;
|
||||||
|
if (!imageIds) {
|
||||||
|
imageIds = await createImageIdsAndCacheMetaData();
|
||||||
setLoadedImageIds(imageIds);
|
setLoadedImageIds(imageIds);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
|
// 3. Get or create the rendering engine
|
||||||
const renderingEngineId = `renderingEngine_${i}`;
|
let renderingEngine = renderingEngineRef.current;
|
||||||
|
if (!renderingEngine) {
|
||||||
|
renderingEngine = new RenderingEngine(renderingEngineId);
|
||||||
|
renderingEngineRef.current = renderingEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Setup tool group
|
||||||
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) || setupTools();
|
||||||
|
|
||||||
|
// 6. Enable and configure each viewport
|
||||||
|
for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
||||||
const viewportId = `CT_${i}`;
|
const viewportId = `CT_${i}`;
|
||||||
const element = viewportRefs.current[i];
|
const element = viewportRefs.current[i];
|
||||||
if (!element) continue;
|
if (!element) continue;
|
||||||
|
|
||||||
const renderingEngine = new RenderingEngine(renderingEngineId);
|
// Enable element only once
|
||||||
renderingEnginesRef.current[renderingEngineId] = renderingEngine;
|
if (!element._cornerstoneEnabled) {
|
||||||
const viewportInput = {
|
if (!renderingEngine.getViewport(viewportId)) {
|
||||||
|
renderingEngine.enableElement({
|
||||||
viewportId,
|
viewportId,
|
||||||
type: Enums.ViewportType.STACK,
|
type: Enums.ViewportType.STACK,
|
||||||
element,
|
element,
|
||||||
};
|
});
|
||||||
renderingEngine.enableElement(viewportInput);
|
element._cornerstoneEnabled = true;
|
||||||
|
}
|
||||||
|
// Add tool group only once
|
||||||
toolGroup.addViewport(viewportId, renderingEngineId);
|
toolGroup.addViewport(viewportId, renderingEngineId);
|
||||||
|
}
|
||||||
|
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
viewport.setStack(imageIds);
|
if (!viewport) continue;
|
||||||
|
|
||||||
/* // Restore previous state for existing viewports only
|
// Set stack if images are loaded
|
||||||
if (
|
if (imageIds) {
|
||||||
prevViewportProps.current[i] &&
|
viewport.setStack(imageIds);
|
||||||
typeof viewport.setProperties === "function"
|
|
||||||
) {
|
|
||||||
viewport.setProperties(prevViewportProps.current[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore previous scroll position for existing viewports only
|
// Restore previous scroll position for existing viewports only
|
||||||
if (
|
if (
|
||||||
prevScrollIndices.current[i] !== undefined &&
|
prevScrollIndices.current[i] !== undefined &&
|
||||||
typeof viewport.setImageIdIndex === "function"
|
typeof viewport.setImageIdIndex === "function"
|
||||||
) {
|
) {
|
||||||
viewport.setImageIdIndex(prevScrollIndices.current[i]);
|
viewport.setImageIdIndex(prevScrollIndices.current[i]);
|
||||||
} */
|
}
|
||||||
|
|
||||||
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element);
|
|
||||||
|
|
||||||
|
// Restore previous state for existing viewports only
|
||||||
|
const prevProps = prevViewportProps.current[i];
|
||||||
|
if (prevProps && typeof viewport.setProperties === "function") {
|
||||||
|
// Only restore safe properties
|
||||||
|
const { voiRange, invert, interpolationType, VOILUTFunction, colormap } = prevProps;
|
||||||
|
const propsToRestore: any = {};
|
||||||
|
if (voiRange && voiRange.lower !== undefined && voiRange.upper !== undefined) propsToRestore.voiRange = voiRange;
|
||||||
|
if (invert !== undefined) propsToRestore.invert = invert;
|
||||||
|
if (interpolationType !== undefined) propsToRestore.interpolationType = interpolationType;
|
||||||
|
if (VOILUTFunction) propsToRestore.VOILUTFunction = VOILUTFunction;
|
||||||
|
if (
|
||||||
|
colormap !== undefined &&
|
||||||
|
colormap !== null &&
|
||||||
|
(typeof colormap === "string" || (typeof colormap === "object" && !("actor" in colormap)))
|
||||||
|
) {
|
||||||
|
propsToRestore.colormap = colormap;
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("Resizing viewport", i);
|
if (viewportId === "CT_0") {
|
||||||
renderingEngine.resize(false, false)
|
console.log("Restoring properties:", propsToRestore, viewport);
|
||||||
viewport.resetCameraForResize();
|
console.log("VOI range:", voiRange);
|
||||||
|
}
|
||||||
|
viewport.setProperties(propsToRestore);
|
||||||
|
if (viewportId === "CT_0") {
|
||||||
|
console.log("Restored:", propsToRestore, viewport);
|
||||||
|
console.log(viewport.getProperties());
|
||||||
|
}
|
||||||
|
viewport.render()
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach ResizeObserver to handle dynamic resizing (only once)
|
||||||
|
if (!element._resizeObserver) {
|
||||||
|
const resizeObserver = new window.ResizeObserver(() => {
|
||||||
|
if (element.offsetWidth > 0 && element.offsetHeight > 0) {
|
||||||
|
renderingEngine.resize();
|
||||||
|
viewport.resize();
|
||||||
viewport.render();
|
viewport.render();
|
||||||
}, 500);
|
}
|
||||||
|
});
|
||||||
|
resizeObserver.observe(element);
|
||||||
|
element._resizeObserver = resizeObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach overlay update for each viewport (only once)
|
||||||
|
if (!element._overlayListener) {
|
||||||
console.log(viewport)
|
viewport.element.addEventListener(IMAGE_RENDERED, () => updateOverlay(i, viewport));
|
||||||
|
element._overlayListener = true;
|
||||||
// Attach overlay update for each viewport
|
}
|
||||||
viewport.element.addEventListener(Enums.Events.IMAGE_RENDERED, () => updateOverlay(i, viewport));
|
|
||||||
// Initial overlay update
|
|
||||||
updateOverlay(i, viewport);
|
updateOverlay(i, viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7. Apply mouse tool bindings
|
||||||
applyMouseToolBindings();
|
applyMouseToolBindings();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -376,15 +369,22 @@ function App() {
|
|||||||
setup();
|
setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => cancelAnimationFrame(raf);
|
// Cleanup: disconnect ResizeObservers
|
||||||
|
return () => {
|
||||||
|
viewportRefs.current.forEach(el => {
|
||||||
|
if (el && el._resizeObserver) {
|
||||||
|
el._resizeObserver.disconnect();
|
||||||
|
delete el._resizeObserver;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
};
|
||||||
}, [elementRef, updateOverlay, viewportGrid, loadedImageIds]);
|
}, [elementRef, updateOverlay, viewportGrid, loadedImageIds]);
|
||||||
|
|
||||||
// Handler to reset the view (camera and window/level)
|
// Handler to reset the view (camera and window/level)
|
||||||
const handleDoubleClick = useCallback((viewportIdx: numebr) => {
|
const handleDoubleClick = useCallback((viewportIdx: numebr) => {
|
||||||
const renderingEngineId = `renderingEngine_${viewportIdx}`;
|
|
||||||
const viewportId = `CT_${viewportIdx}`;
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
|
const renderingEngine = renderingEngineRef.current;
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
if (viewport && typeof viewport.resetCamera === "function") {
|
if (viewport && typeof viewport.resetCamera === "function") {
|
||||||
viewport.resetCamera();
|
viewport.resetCamera();
|
||||||
@@ -396,10 +396,9 @@ function App() {
|
|||||||
// Handler to apply a preset for a specific viewport
|
// Handler to apply a preset for a specific viewport
|
||||||
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
|
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
|
||||||
console.log("Applying preset:", center, width);
|
console.log("Applying preset:", center, width);
|
||||||
const renderingEngineId = `renderingEngine_${viewportIdx}`;
|
|
||||||
const viewportId = `CT_${viewportIdx}`;
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
try {
|
try {
|
||||||
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
|
const renderingEngine = renderingEngineRef.current;
|
||||||
console.log("Rendering engine:", renderingEngine);
|
console.log("Rendering engine:", renderingEngine);
|
||||||
if (renderingEngine) {
|
if (renderingEngine) {
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
@@ -458,9 +457,12 @@ function App() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Render the grid of viewports
|
|
||||||
const viewports = [];
|
const viewports = [];
|
||||||
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
|
for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
||||||
|
// Determine if this viewport should be visible in the current grid
|
||||||
|
const isVisible = i < viewportGrid.rows * viewportGrid.cols;
|
||||||
|
|
||||||
viewports.push(
|
viewports.push(
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@@ -473,8 +475,13 @@ function App() {
|
|||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
gridRow: Math.floor(i / viewportGrid.cols) + 1,
|
||||||
|
gridColumn: (i % viewportGrid.cols) + 1,
|
||||||
|
height: "100%", // <-- Add this line
|
||||||
|
// Use visibility instead of display to hide but keep size/layout
|
||||||
|
visibility: isVisible ? "visible" : "hidden",
|
||||||
|
pointerEvents: isVisible ? "auto" : "none",
|
||||||
}}
|
}}
|
||||||
onDoubleClick={() => handleDoubleClick(i)}
|
onDoubleClick={() => handleDoubleClick(i)}
|
||||||
>
|
>
|
||||||
@@ -574,7 +581,6 @@ function App() {
|
|||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("Preset clicked for viewport", i, preset.name);
|
|
||||||
handleApplyPreset(i, preset.center, preset.width);
|
handleApplyPreset(i, preset.center, preset.width);
|
||||||
setPresetsOpenArr(prev => {
|
setPresetsOpenArr(prev => {
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
@@ -596,6 +602,7 @@ function App() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative", width: "100vw", height: "100vh", background: "#111" }}>
|
<div style={{ position: "relative", width: "100vw", height: "100vh", background: "#111" }}>
|
||||||
{/* Viewport grid menu at top center */}
|
{/* Viewport grid menu at top center */}
|
||||||
@@ -657,6 +664,7 @@ function App() {
|
|||||||
fontSize: "15px",
|
fontSize: "15px",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
saveVisibleViewportState(viewportGrid, renderingEngineRef.current);
|
||||||
setViewportGrid({ rows: opt.rows, cols: opt.cols })
|
setViewportGrid({ rows: opt.rows, cols: opt.cols })
|
||||||
setViewportMenuOpen(false)
|
setViewportMenuOpen(false)
|
||||||
}}
|
}}
|
||||||
@@ -870,4 +878,3 @@ function setupTools() {
|
|||||||
})
|
})
|
||||||
return toolGroup
|
return toolGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user