This commit is contained in:
Ross
2026-06-08 09:50:55 +01:00
parent b2bd2791a5
commit 1ddc9a810a
+85 -26
View File
@@ -7,7 +7,7 @@ import {
} from "@cornerstonejs/core"
import { init as csRenderInit } from "@cornerstonejs/core"
import { init as csToolsInit, addTool, ToolGroupManager, Enums as csToolsEnums, PanTool, WindowLevelTool, StackScrollTool, ZoomTool, PlanarRotateTool, utilities as csToolsUtilities } from "@cornerstonejs/tools"
import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, SculptorTool, CrosshairsTool, ReferenceLinesTool, ReferenceCursors } from "@cornerstonejs/tools"
import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool, PlanarFreehandROITool, SculptorTool, CrosshairsTool, ReferenceLinesTool, ReferenceCursors } from "@cornerstonejs/tools"
import { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
import * as cornerstoneTools from '@cornerstonejs/tools';
import * as cornerstone3d from '@cornerstonejs/core';
@@ -23,6 +23,26 @@ import { getLoggerConfig, logger, parseLogFormat, parseLogLevel, setLoggerConfig
import { NoLabelArrowAnnotateTool } from "./NoLabelArrowAnnotateTool";
import { render } from "@cornerstonejs/tools/tools/displayTools/Labelmap/labelmapDisplay"
const windowLevelToolPrototype = WindowLevelTool.prototype as any;
if (!windowLevelToolPrototype._mouseSensitivityPatched) {
const originalGetNewRange = windowLevelToolPrototype.getNewRange;
windowLevelToolPrototype.getNewRange = function ({ deltaPointsCanvas, ...rest }: any) {
const sensitivity = typeof this?.configuration?.mouseSensitivity === "number"
? this.configuration.mouseSensitivity
: 1;
const scaledDeltaPointsCanvas = Array.isArray(deltaPointsCanvas)
? [deltaPointsCanvas[0] * sensitivity, deltaPointsCanvas[1] * sensitivity]
: deltaPointsCanvas;
return originalGetNewRange.call(this, {
...rest,
deltaPointsCanvas: scaledDeltaPointsCanvas,
});
};
windowLevelToolPrototype._mouseSensitivityPatched = true;
}
// Extend the Window interface to include cornerstoneTools
@@ -152,7 +172,6 @@ const TOOL_OPTIONS = [
{ label: "Rectangle ROI", value: RectangleROITool.toolName, icon: "▭" },
{ label: "Ellipse ROI", value: EllipticalROITool.toolName, icon: "◯" },
{ label: "Freehand ROI", value: PlanarFreehandROITool.toolName, icon: "✏️" },
{ label: "Freehand Contour Segmentation", value: PlanarFreehandContourSegmentationTool.toolName, icon: "🖊️" },
{ label: "Sculptor", value: SculptorTool.toolName, icon: "🔧" },
{ label: "Reference Cursors", value: ReferenceCursors.toolName, icon: "⦿" },
];
@@ -433,6 +452,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
const MOUSE_SETTINGS_KEY = "dv3d_mouseToolBindings";
const CTRL_MOUSE_SETTINGS_KEY = "dv3d_ctrlMouseToolBindings";
const WINDOW_LEVEL_SENSITIVITY_KEY = "dv3d_windowLevelMouseSensitivity";
const initialLogMinLevel = (initialLoggerConfig?.minLevel as LogLevel | undefined)
?? (initialLoggerConfig?.debug !== undefined ? (initialLoggerConfig.debug ? "debug" : "warn") : undefined)
?? getLoggerConfig().minLevel;
@@ -2122,6 +2142,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
Auxiliary: ProbeTool.toolName,
Secondary: NoLabelArrowAnnotateTool.toolName,
})
const [windowLevelMouseSensitivity, setWindowLevelMouseSensitivity] = useState(1)
// Track if Ctrl is pressed
const [ctrlPressed, setCtrlPressed] = useState(false)
@@ -2302,6 +2323,13 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
const ctrl = localStorage.getItem(CTRL_MOUSE_SETTINGS_KEY);
if (mouse) setMouseToolBindings(JSON.parse(mouse));
if (ctrl) setCtrlMouseToolBindings(JSON.parse(ctrl));
const rawWindowLevelSensitivity = localStorage.getItem(WINDOW_LEVEL_SENSITIVITY_KEY);
if (rawWindowLevelSensitivity) {
const parsedSensitivity = JSON.parse(rawWindowLevelSensitivity);
if (typeof parsedSensitivity?.mouseSensitivity === "number") {
setWindowLevelMouseSensitivity(parsedSensitivity.mouseSensitivity);
}
}
const rawLogging = localStorage.getItem(LOG_SETTINGS_KEY);
if (rawLogging) {
@@ -2351,6 +2379,15 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
} catch (e) { }
}, [ctrlMouseToolBindings]);
useEffect(() => {
try {
localStorage.setItem(
WINDOW_LEVEL_SENSITIVITY_KEY,
JSON.stringify({ mouseSensitivity: windowLevelMouseSensitivity })
);
} catch (e) { }
}, [windowLevelMouseSensitivity]);
useEffect(() => {
setLoggerConfig({
minLevel: loggingMinLevel,
@@ -2479,7 +2516,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
'RectangleROI',
'EllipticalROI',
'PlanarFreehandROI',
'PlanarFreehandContourSegmentation',
'Sculptor',
// Add more if needed
].forEach(toolName => {
mainToolGroup.setToolConfiguration(toolName, {
@@ -2604,6 +2641,12 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
toolGroup.setToolActive(tool, { bindings });
});
if (typeof toolGroup.setToolConfiguration === "function") {
toolGroup.setToolConfiguration(WindowLevelTool.toolName, {
mouseSensitivity: windowLevelMouseSensitivity,
});
}
// Add touch bindings so tools are usable on phones/tablets.
const primaryTool = mouseToolBindings.Primary;
if (primaryTool) {
@@ -2652,7 +2695,11 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
}
});
}, [mouseToolBindings, ctrlMouseToolBindings]);
}, [mouseToolBindings, ctrlMouseToolBindings, windowLevelMouseSensitivity]);
useEffect(() => {
applyMouseToolBindings();
}, [windowLevelMouseSensitivity, applyMouseToolBindings]);
const [sideMenuOpen, setSideMenuOpen] = useState(false);
const [folderLoadMode, setFolderLoadMode] = useState<'add' | 'replace'>('add');
@@ -3872,22 +3919,6 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
cancelAnimationFrame(raf);
};
}, [elementRef, updateOverlay, viewportGrid, viewportModes, viewportImageIds]);
// Handler to reset the view (camera and window/level)
const handleDoubleClick = useCallback((viewportIdx: number) => {
const viewportId = `CT_${viewportIdx}`;
const renderingEngine = renderingEngineRef.current;
const viewport = renderingEngine.getViewport(viewportId);
if (viewport && typeof viewport.resetCamera === "function") {
viewport.resetCamera();
appLogger.debug("Restoring viewport properties for viewport", viewportIdx);
viewport.resetProperties(); // Resets window/level and other properties
viewport.render();
}
}, [])
// Handler to apply a preset for a specific viewport
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
const viewportId = `CT_${viewportIdx}`;
@@ -4060,6 +4091,20 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
});
}, [applyDefaultWindowLevel, updateOverlay, viewportImageIds]);
// Handler to reset the view (camera and window/level)
const handleDoubleClick = useCallback((viewportIdx: number) => {
const viewportId = `CT_${viewportIdx}`;
const renderingEngine = renderingEngineRef.current;
const viewport = renderingEngine.getViewport(viewportId);
if (viewport && typeof viewport.resetCamera === "function") {
viewport.resetCamera();
appLogger.debug("Restoring default window/level for viewport", viewportIdx);
applyDefaultWindowLevel(viewportIdx);
viewport.render();
}
}, [applyDefaultWindowLevel])
const applyPresetOption = useCallback((viewportIdx: number, preset: PresetOption, persistSelection = true, resetCameraOnReset = true) => {
if (persistSelection) {
activePresetByViewportRef.current[viewportIdx] = preset;
@@ -5058,7 +5103,6 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
RectangleROITool.toolName,
EllipticalROITool.toolName,
PlanarFreehandROITool.toolName,
PlanarFreehandContourSegmentationTool.toolName,
ProbeTool.toolName,
SculptorTool.toolName,
];
@@ -5407,6 +5451,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|| !mobileInteractionActive
|| !mobileSingleViewportMode
|| mobileFocusedViewport === i;
const hasCineableStack = viewportModes[i] === "stack" && ((imageInfos[i]?.total || 0) > 1 || (imageInfos[i]?.totalFrames || 0) > 1);
const currentPreviewZone = dropPreview?.viewportIdx === i ? dropPreview.zone : null;
const resolvedPreviewZone = currentPreviewZone ? resolveDropZone(currentPreviewZone) : null;
@@ -5764,6 +5809,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
</span>
)}
</button>
{hasCineableStack && (
<button
style={{
position: "absolute",
@@ -5800,6 +5846,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
>
<span aria-hidden="true"></span>
</button>
)}
{viewportModes[i] === "stack" && annotationNavEnabled && (() => {
// Get all annotations for this stack
const imageIds = viewportImageIds[i];
@@ -6166,7 +6213,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
)}
</div>
</div>
{viewportModes[i] === "stack" && (cineControlsOpenByViewport[i] || (seriesGroupsByViewport[i] || []).length > 1 || (imageInfos[i]?.frameIndex && imageInfos[i]?.totalFrames)) && (
{hasCineableStack && (cineControlsOpenByViewport[i] || (seriesGroupsByViewport[i] || []).length > 1 || (imageInfos[i]?.frameIndex && imageInfos[i]?.totalFrames)) && (
<div
style={{
position: "absolute",
@@ -6187,7 +6234,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
}}
onClick={(e) => e.stopPropagation()}
>
{cineControlsOpenByViewport[i] && imageInfos[i]?.total > 1 && (
{cineControlsOpenByViewport[i] && hasCineableStack && (
<>
<button
style={{
@@ -6533,7 +6580,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
position: "absolute",
left: 0,
right: 0,
bottom: viewportModes[i] === "stack" && (cineControlsOpenByViewport[i] || (seriesGroupsByViewport[i] || []).length > 1) ? 56 : 8,
bottom: hasCineableStack && (cineControlsOpenByViewport[i] || (seriesGroupsByViewport[i] || []).length > 1) ? 56 : 8,
zIndex: 20,
display: "flex",
alignItems: "center",
@@ -7851,6 +7898,20 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
</div>
)}
</div>
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Window/Level Mouse Sensitivity</div>
<label style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 12 }}>
<span style={{ minWidth: 120 }}>Drag sensitivity</span>
<input
type="range"
min={0.25}
max={2}
step={0.05}
value={windowLevelMouseSensitivity}
onChange={(e) => setWindowLevelMouseSensitivity(Number(e.target.value))}
style={{ flex: 1 }}
/>
<span style={{ minWidth: 44, textAlign: "right" }}>{windowLevelMouseSensitivity.toFixed(2)}x</span>
</label>
{/* Ctrl + Mouse Button Tool Bindings */}
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
{MOUSE_BUTTONS.map((btn) => (
@@ -8088,7 +8149,6 @@ function setupTools(MAIN_TOOL_GROUP_ID) {
cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(EllipticalROITool)
cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
cornerstoneTools.addTool(SculptorTool)
cornerstoneTools.addTool(ReferenceCursors)
cornerstoneTools.addTool(CrosshairsTool)
@@ -8113,7 +8173,6 @@ function setupTools(MAIN_TOOL_GROUP_ID) {
mainToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
mainToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
mainToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
mainToolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {});
mainToolGroup.addTool(SculptorTool.toolName, {});
//mainToolGroup.addTool(CrosshairsTool.toolName);
mainToolGroup.addTool(ReferenceLinesTool.toolName);