fix some multiview stuff

This commit is contained in:
Ross
2025-05-22 23:15:39 +01:00
parent 792f463f9f
commit e618f0d917
+136 -129
View File
@@ -12,6 +12,7 @@ import * as cornerstoneTools from '@cornerstonejs/tools';
import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
import { segmentation, Enums as csEnums } from '@cornerstonejs/tools';
import { voi } from "@cornerstonejs/tools/utilities"
import { readDicomElementExplicit } from "dicom-parser"
const { MouseBindings, KeyboardBindings } = csToolsEnums;
@@ -73,6 +74,10 @@ function App() {
windowWidth: "",
})
const MAX_VIEWPORTS = 9;
const renderingEngineId = "mainRenderingEngine";
const renderingEngineRef = useRef<any>(null);
// Store imageIds globally after loading so they can be reused
const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null);
@@ -82,7 +87,6 @@ function App() {
// Store refs for each viewport
const viewportRefs = useRef<Array<HTMLDivElement | null>>([])
const renderingEnginesRef = useRef<Record<string, any>>({});
// Store the properties and scroll position of existing viewports before grid change
const prevViewportProps = useRef<Record<number, any>>({});
@@ -224,167 +228,163 @@ function App() {
});
}, []);
useEffect(() => {
const setup = async () => {
// 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}`;
function saveVisibleViewportState() {
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
const viewportId = `CT_${i}`;
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
const renderingEngine = renderingEngineRef.current;
if (renderingEngine) {
const viewport = renderingEngine.getViewport(viewportId);
if (viewport) {
if (typeof viewport.getProperties === "function") {
console.log("Saving previous state for viewport", i, viewport.getProperties())
prevViewportProps.current[i] = viewport.getProperties();
}
if (typeof viewport.getCurrentImageIdIndex === "function") {
prevScrollIndices.current[i] = viewport.getCurrentImageIdIndex();
}
if (viewport && typeof viewport.getProperties === "function") {
prevViewportProps.current[i] = viewport.getProperties();
}
if (viewport && typeof viewport.getCurrentImageIdIndex === "function") {
prevScrollIndices.current[i] = viewport.getCurrentImageIdIndex();
}
}
}
}
if (running.current) {
if (loadedImageIds) {
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
const renderingEngineId = `renderingEngine_${i}`;
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;
useEffect(() => {
const setup = async () => {
// 1. Initialize Cornerstone3D and tools ONCE
if (!running.current) {
running.current = true;
await csRenderInit();
await csToolsInit();
dicomImageLoaderInit({ maxWebWorkers: 1 });
}
running.current = true;
await csRenderInit();
await csToolsInit();
dicomImageLoaderInit({ maxWebWorkers: 1 });
// 2. Load image IDs if not already loaded
let imageIds = loadedImageIds;
if (!imageIds) {
imageIds = await createImageIdsAndCacheMetaData();
setLoadedImageIds(imageIds);
}
const toolGroup = setupTools();
const imageIds = await createImageIdsAndCacheMetaData();
setLoadedImageIds(imageIds);
// 3. Get or create the rendering engine
let renderingEngine = renderingEngineRef.current;
if (!renderingEngine) {
renderingEngine = new RenderingEngine(renderingEngineId);
renderingEngineRef.current = renderingEngine;
}
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
const renderingEngineId = `renderingEngine_${i}`;
// 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 element = viewportRefs.current[i];
if (!element) continue;
const renderingEngine = new RenderingEngine(renderingEngineId);
renderingEnginesRef.current[renderingEngineId] = renderingEngine;
const viewportInput = {
viewportId,
type: Enums.ViewportType.STACK,
element,
};
renderingEngine.enableElement(viewportInput);
toolGroup.addViewport(viewportId, renderingEngineId);
// Enable element only once
if (!element._cornerstoneEnabled) {
if (!renderingEngine.getViewport(viewportId)) {
renderingEngine.enableElement({
viewportId,
type: Enums.ViewportType.STACK,
element,
});
element._cornerstoneEnabled = true;
}
// Add tool group only once
toolGroup.addViewport(viewportId, renderingEngineId);
}
const viewport = renderingEngine.getViewport(viewportId);
viewport.setStack(imageIds);
if (!viewport) continue;
/* // Restore previous state for existing viewports only
if (
prevViewportProps.current[i] &&
typeof viewport.setProperties === "function"
) {
viewport.setProperties(prevViewportProps.current[i]);
// Set stack if images are loaded
if (imageIds) {
viewport.setStack(imageIds);
}
// Restore previous scroll position for existing viewports only
if (
prevScrollIndices.current[i] !== undefined &&
typeof viewport.setImageIdIndex === "function"
) {
viewport.setImageIdIndex(prevScrollIndices.current[i]);
} */
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element);
setTimeout(() => {
console.log("Resizing viewport", i);
renderingEngine.resize(false, false)
viewport.resetCameraForResize();
viewport.render();
}, 500);
}
// 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(() => {
if (viewportId === "CT_0") {
console.log("Restoring properties:", propsToRestore, viewport);
console.log("VOI range:", voiRange);
}
viewport.setProperties(propsToRestore);
if (viewportId === "CT_0") {
console.log("Restored:", propsToRestore, viewport);
console.log(viewport.getProperties());
}
viewport.render()
}, 100);
}
console.log(viewport)
// 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();
}
});
resizeObserver.observe(element);
element._resizeObserver = resizeObserver;
}
// Attach overlay update for each viewport
viewport.element.addEventListener(Enums.Events.IMAGE_RENDERED, () => updateOverlay(i, viewport));
// Initial overlay update
// Attach overlay update for each viewport (only once)
if (!element._overlayListener) {
viewport.element.addEventListener(IMAGE_RENDERED, () => updateOverlay(i, viewport));
element._overlayListener = true;
}
updateOverlay(i, viewport);
}
// 7. Apply mouse tool bindings
applyMouseToolBindings();
};
// Use requestAnimationFrame to ensure DOM is painted before setup
const raf = requestAnimationFrame(() => {
setup();
});
return () => cancelAnimationFrame(raf);
// Use requestAnimationFrame to ensure DOM is painted before setup
const raf = requestAnimationFrame(() => {
setup();
});
// Cleanup: disconnect ResizeObservers
return () => {
viewportRefs.current.forEach(el => {
if (el && el._resizeObserver) {
el._resizeObserver.disconnect();
delete el._resizeObserver;
}
});
cancelAnimationFrame(raf);
};
}, [elementRef, updateOverlay, viewportGrid, loadedImageIds]);
// Handler to reset the view (camera and window/level)
const handleDoubleClick = useCallback((viewportIdx: numebr) => {
const renderingEngineId = `renderingEngine_${viewportIdx}`;
const viewportId = `CT_${viewportIdx}`;
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
const renderingEngine = renderingEngineRef.current;
const viewport = renderingEngine.getViewport(viewportId);
if (viewport && typeof viewport.resetCamera === "function") {
viewport.resetCamera();
@@ -396,10 +396,9 @@ function App() {
// Handler to apply a preset for a specific viewport
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
console.log("Applying preset:", center, width);
const renderingEngineId = `renderingEngine_${viewportIdx}`;
const viewportId = `CT_${viewportIdx}`;
try {
const renderingEngine = renderingEnginesRef.current[renderingEngineId];
const renderingEngine = renderingEngineRef.current;
console.log("Rendering engine:", renderingEngine);
if (renderingEngine) {
const viewport = renderingEngine.getViewport(viewportId);
@@ -458,9 +457,12 @@ function App() {
};
}, []);
// Render the grid of 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(
<div
key={i}
@@ -473,8 +475,13 @@ function App() {
minHeight: 0,
position: "relative",
overflow: "hidden",
display: "flex",
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)}
>
@@ -574,7 +581,6 @@ function App() {
opacity: 0.9,
}}
onClick={() => {
console.log("Preset clicked for viewport", i, preset.name);
handleApplyPreset(i, preset.center, preset.width);
setPresetsOpenArr(prev => {
const next = [...prev];
@@ -596,6 +602,7 @@ function App() {
return (
<div style={{ position: "relative", width: "100vw", height: "100vh", background: "#111" }}>
{/* Viewport grid menu at top center */}
@@ -657,6 +664,7 @@ function App() {
fontSize: "15px",
}}
onClick={() => {
saveVisibleViewportState(viewportGrid, renderingEngineRef.current);
setViewportGrid({ rows: opt.rows, cols: opt.cols })
setViewportMenuOpen(false)
}}
@@ -870,4 +878,3 @@ function setupTools() {
})
return toolGroup
}