update
This commit is contained in:
+85
-26
@@ -7,7 +7,7 @@ import {
|
|||||||
} from "@cornerstonejs/core"
|
} from "@cornerstonejs/core"
|
||||||
import { init as csRenderInit } 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 { 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 { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
|
||||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||||
import * as cornerstone3d from '@cornerstonejs/core';
|
import * as cornerstone3d from '@cornerstonejs/core';
|
||||||
@@ -23,6 +23,26 @@ import { getLoggerConfig, logger, parseLogFormat, parseLogLevel, setLoggerConfig
|
|||||||
import { NoLabelArrowAnnotateTool } from "./NoLabelArrowAnnotateTool";
|
import { NoLabelArrowAnnotateTool } from "./NoLabelArrowAnnotateTool";
|
||||||
import { render } from "@cornerstonejs/tools/tools/displayTools/Labelmap/labelmapDisplay"
|
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
|
// Extend the Window interface to include cornerstoneTools
|
||||||
@@ -152,7 +172,6 @@ const TOOL_OPTIONS = [
|
|||||||
{ label: "Rectangle ROI", value: RectangleROITool.toolName, icon: "▭" },
|
{ label: "Rectangle ROI", value: RectangleROITool.toolName, icon: "▭" },
|
||||||
{ label: "Ellipse ROI", value: EllipticalROITool.toolName, icon: "◯" },
|
{ label: "Ellipse ROI", value: EllipticalROITool.toolName, icon: "◯" },
|
||||||
{ label: "Freehand ROI", value: PlanarFreehandROITool.toolName, icon: "✏️" },
|
{ label: "Freehand ROI", value: PlanarFreehandROITool.toolName, icon: "✏️" },
|
||||||
{ label: "Freehand Contour Segmentation", value: PlanarFreehandContourSegmentationTool.toolName, icon: "🖊️" },
|
|
||||||
{ label: "Sculptor", value: SculptorTool.toolName, icon: "🔧" },
|
{ label: "Sculptor", value: SculptorTool.toolName, icon: "🔧" },
|
||||||
{ label: "Reference Cursors", value: ReferenceCursors.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 MOUSE_SETTINGS_KEY = "dv3d_mouseToolBindings";
|
||||||
const CTRL_MOUSE_SETTINGS_KEY = "dv3d_ctrlMouseToolBindings";
|
const CTRL_MOUSE_SETTINGS_KEY = "dv3d_ctrlMouseToolBindings";
|
||||||
|
const WINDOW_LEVEL_SENSITIVITY_KEY = "dv3d_windowLevelMouseSensitivity";
|
||||||
const initialLogMinLevel = (initialLoggerConfig?.minLevel as LogLevel | undefined)
|
const initialLogMinLevel = (initialLoggerConfig?.minLevel as LogLevel | undefined)
|
||||||
?? (initialLoggerConfig?.debug !== undefined ? (initialLoggerConfig.debug ? "debug" : "warn") : undefined)
|
?? (initialLoggerConfig?.debug !== undefined ? (initialLoggerConfig.debug ? "debug" : "warn") : undefined)
|
||||||
?? getLoggerConfig().minLevel;
|
?? getLoggerConfig().minLevel;
|
||||||
@@ -2122,6 +2142,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
Auxiliary: ProbeTool.toolName,
|
Auxiliary: ProbeTool.toolName,
|
||||||
Secondary: NoLabelArrowAnnotateTool.toolName,
|
Secondary: NoLabelArrowAnnotateTool.toolName,
|
||||||
})
|
})
|
||||||
|
const [windowLevelMouseSensitivity, setWindowLevelMouseSensitivity] = useState(1)
|
||||||
|
|
||||||
// Track if Ctrl is pressed
|
// Track if Ctrl is pressed
|
||||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
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);
|
const ctrl = localStorage.getItem(CTRL_MOUSE_SETTINGS_KEY);
|
||||||
if (mouse) setMouseToolBindings(JSON.parse(mouse));
|
if (mouse) setMouseToolBindings(JSON.parse(mouse));
|
||||||
if (ctrl) setCtrlMouseToolBindings(JSON.parse(ctrl));
|
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);
|
const rawLogging = localStorage.getItem(LOG_SETTINGS_KEY);
|
||||||
if (rawLogging) {
|
if (rawLogging) {
|
||||||
@@ -2351,6 +2379,15 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
}, [ctrlMouseToolBindings]);
|
}, [ctrlMouseToolBindings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
WINDOW_LEVEL_SENSITIVITY_KEY,
|
||||||
|
JSON.stringify({ mouseSensitivity: windowLevelMouseSensitivity })
|
||||||
|
);
|
||||||
|
} catch (e) { }
|
||||||
|
}, [windowLevelMouseSensitivity]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoggerConfig({
|
setLoggerConfig({
|
||||||
minLevel: loggingMinLevel,
|
minLevel: loggingMinLevel,
|
||||||
@@ -2479,7 +2516,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
'RectangleROI',
|
'RectangleROI',
|
||||||
'EllipticalROI',
|
'EllipticalROI',
|
||||||
'PlanarFreehandROI',
|
'PlanarFreehandROI',
|
||||||
'PlanarFreehandContourSegmentation',
|
'Sculptor',
|
||||||
// Add more if needed
|
// Add more if needed
|
||||||
].forEach(toolName => {
|
].forEach(toolName => {
|
||||||
mainToolGroup.setToolConfiguration(toolName, {
|
mainToolGroup.setToolConfiguration(toolName, {
|
||||||
@@ -2604,6 +2641,12 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
toolGroup.setToolActive(tool, { bindings });
|
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.
|
// Add touch bindings so tools are usable on phones/tablets.
|
||||||
const primaryTool = mouseToolBindings.Primary;
|
const primaryTool = mouseToolBindings.Primary;
|
||||||
if (primaryTool) {
|
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 [sideMenuOpen, setSideMenuOpen] = useState(false);
|
||||||
const [folderLoadMode, setFolderLoadMode] = useState<'add' | 'replace'>('add');
|
const [folderLoadMode, setFolderLoadMode] = useState<'add' | 'replace'>('add');
|
||||||
@@ -3872,22 +3919,6 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
cancelAnimationFrame(raf);
|
cancelAnimationFrame(raf);
|
||||||
};
|
};
|
||||||
}, [elementRef, updateOverlay, viewportGrid, viewportModes, viewportImageIds]);
|
}, [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
|
// 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) => {
|
||||||
const viewportId = `CT_${viewportIdx}`;
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
@@ -4060,6 +4091,20 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
});
|
});
|
||||||
}, [applyDefaultWindowLevel, updateOverlay, viewportImageIds]);
|
}, [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) => {
|
const applyPresetOption = useCallback((viewportIdx: number, preset: PresetOption, persistSelection = true, resetCameraOnReset = true) => {
|
||||||
if (persistSelection) {
|
if (persistSelection) {
|
||||||
activePresetByViewportRef.current[viewportIdx] = preset;
|
activePresetByViewportRef.current[viewportIdx] = preset;
|
||||||
@@ -5058,7 +5103,6 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
RectangleROITool.toolName,
|
RectangleROITool.toolName,
|
||||||
EllipticalROITool.toolName,
|
EllipticalROITool.toolName,
|
||||||
PlanarFreehandROITool.toolName,
|
PlanarFreehandROITool.toolName,
|
||||||
PlanarFreehandContourSegmentationTool.toolName,
|
|
||||||
ProbeTool.toolName,
|
ProbeTool.toolName,
|
||||||
SculptorTool.toolName,
|
SculptorTool.toolName,
|
||||||
];
|
];
|
||||||
@@ -5407,6 +5451,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
|| !mobileInteractionActive
|
|| !mobileInteractionActive
|
||||||
|| !mobileSingleViewportMode
|
|| !mobileSingleViewportMode
|
||||||
|| mobileFocusedViewport === i;
|
|| 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 currentPreviewZone = dropPreview?.viewportIdx === i ? dropPreview.zone : null;
|
||||||
const resolvedPreviewZone = currentPreviewZone ? resolveDropZone(currentPreviewZone) : null;
|
const resolvedPreviewZone = currentPreviewZone ? resolveDropZone(currentPreviewZone) : null;
|
||||||
@@ -5764,6 +5809,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
{hasCineableStack && (
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -5800,6 +5846,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
>
|
>
|
||||||
<span aria-hidden="true">▶</span>
|
<span aria-hidden="true">▶</span>
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
{viewportModes[i] === "stack" && annotationNavEnabled && (() => {
|
{viewportModes[i] === "stack" && annotationNavEnabled && (() => {
|
||||||
// Get all annotations for this stack
|
// Get all annotations for this stack
|
||||||
const imageIds = viewportImageIds[i];
|
const imageIds = viewportImageIds[i];
|
||||||
@@ -6166,7 +6213,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -6187,7 +6234,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{cineControlsOpenByViewport[i] && imageInfos[i]?.total > 1 && (
|
{cineControlsOpenByViewport[i] && hasCineableStack && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
@@ -6533,7 +6580,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 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,
|
zIndex: 20,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -7851,6 +7898,20 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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 */}
|
{/* Ctrl + Mouse Button Tool Bindings */}
|
||||||
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
|
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
|
||||||
{MOUSE_BUTTONS.map((btn) => (
|
{MOUSE_BUTTONS.map((btn) => (
|
||||||
@@ -8088,7 +8149,6 @@ function setupTools(MAIN_TOOL_GROUP_ID) {
|
|||||||
cornerstoneTools.addTool(RectangleROITool)
|
cornerstoneTools.addTool(RectangleROITool)
|
||||||
cornerstoneTools.addTool(EllipticalROITool)
|
cornerstoneTools.addTool(EllipticalROITool)
|
||||||
cornerstoneTools.addTool(PlanarFreehandROITool)
|
cornerstoneTools.addTool(PlanarFreehandROITool)
|
||||||
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
|
|
||||||
cornerstoneTools.addTool(SculptorTool)
|
cornerstoneTools.addTool(SculptorTool)
|
||||||
cornerstoneTools.addTool(ReferenceCursors)
|
cornerstoneTools.addTool(ReferenceCursors)
|
||||||
cornerstoneTools.addTool(CrosshairsTool)
|
cornerstoneTools.addTool(CrosshairsTool)
|
||||||
@@ -8113,7 +8173,6 @@ function setupTools(MAIN_TOOL_GROUP_ID) {
|
|||||||
mainToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
|
mainToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
|
||||||
mainToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
|
mainToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
|
||||||
mainToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
|
mainToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
|
||||||
mainToolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {});
|
|
||||||
mainToolGroup.addTool(SculptorTool.toolName, {});
|
mainToolGroup.addTool(SculptorTool.toolName, {});
|
||||||
//mainToolGroup.addTool(CrosshairsTool.toolName);
|
//mainToolGroup.addTool(CrosshairsTool.toolName);
|
||||||
mainToolGroup.addTool(ReferenceLinesTool.toolName);
|
mainToolGroup.addTool(ReferenceLinesTool.toolName);
|
||||||
|
|||||||
Reference in New Issue
Block a user