.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "tsc && vite build && cp dist/index.js /home/ross/rad/rad/rad/static/dv3d/index.js",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
+120
-17
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useCallback, useState } from "react"
|
||||
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"
|
||||
import {
|
||||
RenderingEngine,
|
||||
Enums,
|
||||
@@ -15,6 +14,7 @@ import { readDicomElementExplicit } from "dicom-parser"
|
||||
import { volumeLoader } from '@cornerstonejs/core';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
import * as dicomParser from "dicom-parser";
|
||||
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"
|
||||
|
||||
import { NoLabelArrowAnnotateTool } from "./NoLabelArrowAnnotateTool";
|
||||
import { render } from "@cornerstonejs/tools/tools/displayTools/Labelmap/labelmapDisplay"
|
||||
@@ -176,9 +176,10 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
() => Array(MAX_VIEWPORTS).fill(0)
|
||||
);
|
||||
|
||||
const [stackOrderModified, setStackOrderModified] = useState(false);
|
||||
const [loadingState, setLoadingState] = useState<null | "annotations" | "viewerState">(null);
|
||||
// Load annotations and viewer state from props if provided
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
// Wait until availableStacks and viewportImageIds are loaded (i.e., app is ready)
|
||||
if (annotationJson || viewerState) {
|
||||
@@ -208,7 +209,7 @@ useEffect(() => {
|
||||
}
|
||||
}
|
||||
setLoadingState(null);
|
||||
}, 800); // 500ms delay, adjust as needed
|
||||
}, 2000); // 500ms delay, adjust as needed
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
@@ -216,7 +217,7 @@ useEffect(() => {
|
||||
setLoadingState(null);
|
||||
};
|
||||
}
|
||||
}, [annotationJson, viewerState, container_id]);
|
||||
}, [annotationJson, viewerState, container_id]);
|
||||
|
||||
// Load available stacks on mount
|
||||
useEffect(() => {
|
||||
@@ -318,6 +319,7 @@ useEffect(() => {
|
||||
}
|
||||
return sortedIds;
|
||||
});
|
||||
setStackOrderModified(true);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
@@ -371,7 +373,7 @@ useEffect(() => {
|
||||
const prevViewportProps = useRef<Record<number, any>>({});
|
||||
const prevScrollIndices = useRef<Record<number, number>>({});
|
||||
|
||||
const [orderBySliceLocation, setOrderBySliceLocation] = useState(true);
|
||||
const [orderBySliceLocation, setOrderBySliceLocation] = useState(false);
|
||||
|
||||
const [altEnablesReferenceCursors, setAltEnablesReferenceCursors] = useState(true);
|
||||
|
||||
@@ -420,7 +422,7 @@ useEffect(() => {
|
||||
const [ctrlMouseToolBindings, setCtrlMouseToolBindings] = useState({
|
||||
Primary: LengthTool.toolName,
|
||||
Auxiliary: ProbeTool.toolName,
|
||||
Secondary: ArrowAnnotateTool.toolName,
|
||||
Secondary: NoLabelArrowAnnotateTool.toolName,
|
||||
})
|
||||
|
||||
// Track if Ctrl is pressed
|
||||
@@ -780,9 +782,8 @@ useEffect(() => {
|
||||
running.current = true;
|
||||
await csRenderInit();
|
||||
await csToolsInit(
|
||||
{ showSVGCursors: true, }
|
||||
);
|
||||
dicomImageLoaderInit({ maxWebWorkers: 1 });
|
||||
dicomImageLoaderInit();
|
||||
}
|
||||
|
||||
|
||||
@@ -1077,6 +1078,25 @@ useEffect(() => {
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
}, [presetsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const preventExtraButtonDefault = (e: MouseEvent) => {
|
||||
// 4th button: button === 3, 5th button: button === 4
|
||||
if (e.button === 3 || e.button === 4) {
|
||||
e.preventDefault();
|
||||
//e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.addEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.addEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.addEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -1144,7 +1164,7 @@ useEffect(() => {
|
||||
};
|
||||
}, [setAvailableStacks, setViewportImageIds, setSelectedStackIdx, setLoadedImageIdsAndCacheMeta]);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!orderBySliceLocation) return;
|
||||
|
||||
// Only sort if all metadata is loaded for all viewports
|
||||
@@ -1155,6 +1175,7 @@ useEffect(() => {
|
||||
if (!allLoaded) return;
|
||||
|
||||
setViewportImageIds(prev => {
|
||||
let modified = false;
|
||||
const next = prev.map(imageIds => {
|
||||
if (!imageIds || imageIds.length === 0) return imageIds;
|
||||
|
||||
@@ -1174,15 +1195,16 @@ useEffect(() => {
|
||||
});
|
||||
|
||||
const sortedIds = sorted.map(obj => obj.imageId);
|
||||
if (JSON.stringify(sortedIds) === JSON.stringify(imageIds)) {
|
||||
return imageIds;
|
||||
}
|
||||
if (JSON.stringify(sortedIds) !== JSON.stringify(imageIds)) {
|
||||
modified = true;
|
||||
return sortedIds;
|
||||
}
|
||||
return imageIds;
|
||||
});
|
||||
if (modified) setStackOrderModified(true);
|
||||
return next;
|
||||
});
|
||||
}, [orderBySliceLocation, availableStacks]);
|
||||
|
||||
}, [orderBySliceLocation, availableStacks]);
|
||||
|
||||
function updateActiveReferenceLineViewport(mainToolGroup: cornerstoneTools.Types.IToolGroup) {
|
||||
// Ensure the main tool group is defined (it won't be during setup)
|
||||
@@ -1197,6 +1219,83 @@ useEffect(() => {
|
||||
mainToolGroup.setToolDisabled(ReferenceLinesTool.toolName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Inside App component
|
||||
function resetViewer() {
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (renderingEngine) {
|
||||
for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
||||
const viewport = renderingEngine.getViewport(`CT_${i}`);
|
||||
if (viewport) {
|
||||
viewport.resetCamera?.();
|
||||
viewport.resetProperties?.();
|
||||
viewport.render?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cornerstoneTools?.annotation?.state?.removeAllAnnotations) {
|
||||
cornerstoneTools.annotation.state.removeAllAnnotations();
|
||||
}
|
||||
}
|
||||
|
||||
function truncateStack(lower: number, upper: number) {
|
||||
// Only allow when 1 viewport and 1 stack is loaded
|
||||
console.log("Truncating stack from", lower, "to", upper);
|
||||
console.log(stackOrderModified)
|
||||
if (
|
||||
viewportGrid.rows !== 1 ||
|
||||
viewportGrid.cols !== 1 ||
|
||||
availableStacks.length !== 1 ||
|
||||
!viewportImageIds[0] ||
|
||||
viewportImageIds[0].length === 0 ||
|
||||
stackOrderModified
|
||||
) {
|
||||
alert("Truncation is only allowed when a single stack is loaded in a single viewport (and the order has not been modified in the viewer).");
|
||||
return false;
|
||||
}
|
||||
|
||||
const stack = availableStacks[0];
|
||||
const imageIds = stack.imageIds;
|
||||
|
||||
// Validate bounds
|
||||
if (
|
||||
lower < 0 ||
|
||||
upper >= imageIds.length ||
|
||||
lower >= upper
|
||||
) {
|
||||
alert("Invalid truncation bounds.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Truncate the stack and update state
|
||||
const truncatedImageIds = imageIds.slice(lower, upper + 1);
|
||||
setAvailableStacks([{ ...stack, imageIds: truncatedImageIds }]);
|
||||
setViewportImageIds([truncatedImageIds]);
|
||||
setSelectedStackIdx([0]);
|
||||
// Optionally reset scroll position, overlays, etc.
|
||||
|
||||
// Optionally, force a re-render or reset the viewport
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (renderingEngine) {
|
||||
const viewport = renderingEngine.getViewport("CT_0");
|
||||
if (viewport && typeof viewport.setStack === "function") {
|
||||
viewport.setStack(truncatedImageIds);
|
||||
viewport.setImageIdIndex?.(0);
|
||||
viewport.render?.();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
function getCurrentStackPosition(viewportIdx: number = 0): number | null {
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (!renderingEngine) return null;
|
||||
const viewport = renderingEngine.getViewport(`CT_${viewportIdx}`);
|
||||
if (!viewport || typeof viewport.getCurrentImageIdIndex !== "function") return null;
|
||||
return viewport.getCurrentImageIdIndex();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const apiKey = container_id || "default";
|
||||
|
||||
@@ -1217,6 +1316,9 @@ useEffect(() => {
|
||||
}
|
||||
// Attach to window with a unique name
|
||||
window[`jumpToSliceByImageId_${apiKey}`] = jumpToSliceByImageId;
|
||||
window[`resetViewer_${apiKey}`] = resetViewer;
|
||||
window[`truncateStack_${apiKey}`] = truncateStack;
|
||||
window[`getCurrentStackPosition_${apiKey}`] = getCurrentStackPosition;
|
||||
|
||||
|
||||
// Export the current viewer state as JSON
|
||||
@@ -1564,6 +1666,9 @@ useEffect(() => {
|
||||
window[`importLegacyAnnotations_${apiKey}`] = undefined;
|
||||
window[`importLegacyViewport_${apiKey}`] = undefined;
|
||||
window[`jumpToSliceByImageId_${apiKey}`] = undefined;
|
||||
window[`resetViewer_${apiKey}`] = undefined;
|
||||
window[`truncateStack_${apiKey}`] = undefined;
|
||||
window[`getCurrentStackPosition_${apiKey}`] = undefined;
|
||||
};
|
||||
}, [
|
||||
viewportGrid,
|
||||
@@ -1778,7 +1883,6 @@ useEffect(() => {
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
// Annotations buttons
|
||||
{viewportModes[i] === "stack" && (() => {
|
||||
// Get all annotations for this stack
|
||||
const imageIds = viewportImageIds[i];
|
||||
@@ -1959,7 +2063,6 @@ useEffect(() => {
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
// ...existing code...
|
||||
{/* Overlay info (bottom left of viewport) */}
|
||||
<div
|
||||
style={{
|
||||
@@ -2179,7 +2282,7 @@ useEffect(() => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isVisible && availableStacks.length > 1 && (
|
||||
{isVisible && (availableStacks.length > 1 || !viewportImageIds[i] || viewportImageIds[i].length === 0) && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
|
||||
@@ -45,4 +45,15 @@ export default defineConfig(({ command }) => ({
|
||||
},
|
||||
},
|
||||
base: command === "build" ? "/static/dv3d/" : "/",
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `index.js`,
|
||||
chunkFileNames: `[name].js`,
|
||||
assetFileNames: `[name][extname]`,
|
||||
},
|
||||
},
|
||||
outDir: "dist",
|
||||
emptyOutDir: true,
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user