lots of fixes

This commit is contained in:
Ross
2025-05-24 22:12:59 +01:00
parent 1eba20c27a
commit 8d673fe28d
43 changed files with 375 additions and 289 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+212 -127
View File
@@ -6,7 +6,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 } from "@cornerstonejs/tools" import { init as csToolsInit, addTool, ToolGroupManager, Enums as csToolsEnums, PanTool, WindowLevelTool, StackScrollTool, ZoomTool, PlanarRotateTool } from "@cornerstonejs/tools"
import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, SculptorTool, CrosshairsTool } from "@cornerstonejs/tools" import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, SculptorTool, CrosshairsTool, ReferenceLinesTool } 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 { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes'; import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
@@ -24,7 +24,6 @@ const { IMAGE_RENDERED } = Enums.Events;
const STACK_TOOL_GROUP_ID = 'STACK_TOOL_GROUP_ID'; const STACK_TOOL_GROUP_ID = 'STACK_TOOL_GROUP_ID';
const VOLUME_TOOL_GROUP_ID = 'VOLUME_TOOL_GROUP_ID'; const VOLUME_TOOL_GROUP_ID = 'VOLUME_TOOL_GROUP_ID';
const toolGroupId = 'STACK_TOOL_GROUP_ID';
// Common CT presets (add more as needed) // Common CT presets (add more as needed)
const WINDOW_LEVEL_PRESETS = [ const WINDOW_LEVEL_PRESETS = [
@@ -87,6 +86,7 @@ function App() {
const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null); const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null);
const [crossReferenceEnabled, setCrossReferenceEnabled] = useState(true); const [crossReferenceEnabled, setCrossReferenceEnabled] = useState(true);
const [referenceLinesEnabled, setReferenceLinesEnabled] = useState(false);
// Wrap setLoadedImageIds to also cache DICOM metadata // Wrap setLoadedImageIds to also cache DICOM metadata
const setLoadedImageIdsAndCacheMeta = async (imageIds: string[]) => { const setLoadedImageIdsAndCacheMeta = async (imageIds: string[]) => {
@@ -98,9 +98,9 @@ function App() {
const url = imageId.slice(8); const url = imageId.slice(8);
// Only cache if it's a blob or file URL // Only cache if it's a blob or file URL
if (url.startsWith("blob:") || url.startsWith("/")) { if (url.startsWith("blob:") || url.startsWith("/")) {
const response = await fetch(url); //const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer(); //const arrayBuffer = await response.arrayBuffer();
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer)); //const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) { if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) {
cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.load(url); cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.load(url);
@@ -252,31 +252,35 @@ function App() {
// Helper to apply all mouse tool bindings (normal or ctrl) // Helper to apply all mouse tool bindings (normal or ctrl)
const applyMouseToolBindings = useCallback(() => { const applyMouseToolBindings = useCallback(() => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId) // Apply to both stack and volume tool groups
const stackToolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
const volumeToolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(VOLUME_TOOL_GROUP_ID);
[stackToolGroup, volumeToolGroup].forEach(toolGroup => {
if (toolGroup) { if (toolGroup) {
// Set all tools to passive first, removing all bindings // Set all tools to passive first, removing all bindings
TOOL_OPTIONS.forEach(opt => { TOOL_OPTIONS.forEach(opt => {
toolGroup.setToolPassive(opt.value, { removeAllBindings: true }) toolGroup.setToolPassive(opt.value, { removeAllBindings: true });
}) });
// Collect all bindings for each tool // Collect all bindings for each tool
const toolBindings: Record<string, Array<any>> = {} const toolBindings: Record<string, Array<any>> = {};
// Normal bindings // Normal bindings
Object.entries(mouseToolBindings).forEach(([btn, tool]) => { Object.entries(mouseToolBindings).forEach(([btn, tool]) => {
if (!toolBindings[tool]) toolBindings[tool] = [] if (!toolBindings[tool]) toolBindings[tool] = [];
toolBindings[tool].push({ mouseButton: MouseBindings[btn] }) toolBindings[tool].push({ mouseButton: MouseBindings[btn] });
}) });
// Ctrl bindings // Ctrl bindings
Object.entries(ctrlMouseToolBindings).forEach(([btn, tool]) => { Object.entries(ctrlMouseToolBindings).forEach(([btn, tool]) => {
if (!toolBindings[tool]) toolBindings[tool] = [] if (!toolBindings[tool]) toolBindings[tool] = [];
toolBindings[tool].push({ mouseButton: MouseBindings[btn], modifierKey: KeyboardBindings.Ctrl }) toolBindings[tool].push({ mouseButton: MouseBindings[btn], modifierKey: KeyboardBindings.Ctrl });
}) });
// Apply all bindings for each tool in a single call // Apply all bindings for each tool in a single call
Object.entries(toolBindings).forEach(([tool, bindings]) => { Object.entries(toolBindings).forEach(([tool, bindings]) => {
toolGroup.setToolActive(tool, { bindings }) toolGroup.setToolActive(tool, { bindings });
}) });
// Always re-apply the mouse wheel binding for StackScrollTool // Always re-apply the mouse wheel binding for StackScrollTool
toolGroup.setToolActive(StackScrollTool.toolName, { toolGroup.setToolActive(StackScrollTool.toolName, {
@@ -285,9 +289,10 @@ function App() {
mouseButton: MouseBindings.Wheel, // Wheel Mouse mouseButton: MouseBindings.Wheel, // Wheel Mouse
}, },
], ],
}) });
} }
}, [mouseToolBindings, ctrlMouseToolBindings]) });
}, [mouseToolBindings, ctrlMouseToolBindings]);
const [sideMenuOpen, setSideMenuOpen] = useState(false); const [sideMenuOpen, setSideMenuOpen] = useState(false);
@@ -353,6 +358,8 @@ function App() {
}); });
}, []); }, []);
const [gridBtnActive, setGridBtnActive] = useState(false);
function saveVisibleViewportState() { function saveVisibleViewportState() {
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) { for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
const viewportId = `CT_${i}`; const viewportId = `CT_${i}`;
@@ -375,7 +382,9 @@ function App() {
if (!running.current) { if (!running.current) {
running.current = true; running.current = true;
await csRenderInit(); await csRenderInit();
await csToolsInit(); await csToolsInit(
{ showSVGCursors: true, }
);
dicomImageLoaderInit({ maxWebWorkers: 1 }); dicomImageLoaderInit({ maxWebWorkers: 1 });
} }
@@ -393,10 +402,51 @@ function App() {
renderingEngineRef.current = renderingEngine; renderingEngineRef.current = renderingEngine;
} }
// 4. Setup tool group // --- In your setup function, use the correct tool group per viewport ---
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) || setupTools(); const { stackToolGroup, volumeToolGroup } = ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID)
? { stackToolGroup: ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID), volumeToolGroup: ToolGroupManager.getToolGroup(VOLUME_TOOL_GROUP_ID) }
: setupTools();
const visibleVolumeViewports = viewportModes
.slice(0, viewportGrid.rows * viewportGrid.cols)
.filter(mode => mode === 'volume').length;
// After the viewport setup loop, outside the for-loop:
if (
crossReferenceEnabled &&
visibleVolumeViewports >= 2
) {
if (!volumeToolGroup.hasTool(CrosshairsTool.toolName)) {
volumeToolGroup.addTool(CrosshairsTool.toolName);
}
volumeToolGroup.setToolEnabled(CrosshairsTool.toolName);
volumeToolGroup.setToolActive(CrosshairsTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }],
});
} else if (volumeToolGroup.hasTool(CrosshairsTool.toolName)) {
volumeToolGroup.setToolDisabled(CrosshairsTool.toolName);
}
// --- In your setup function, after tool group creation, enable/disable ReferenceLinesTool for each tool group ---
if (referenceLinesEnabled) {
if (!stackToolGroup.hasTool(ReferenceLinesTool.toolName)) {
stackToolGroup.addTool(ReferenceLinesTool.toolName);
}
if (!volumeToolGroup.hasTool(ReferenceLinesTool.toolName)) {
volumeToolGroup.addTool(ReferenceLinesTool.toolName);
}
stackToolGroup.setToolEnabled(ReferenceLinesTool.toolName);
volumeToolGroup.setToolEnabled(ReferenceLinesTool.toolName);
} else {
if (stackToolGroup.hasTool(ReferenceLinesTool.toolName)) {
stackToolGroup.setToolDisabled(ReferenceLinesTool.toolName);
}
if (volumeToolGroup.hasTool(ReferenceLinesTool.toolName)) {
volumeToolGroup.setToolDisabled(ReferenceLinesTool.toolName);
}
}
// 6. Enable and configure each viewport // 6. Enable and configure each viewport
for (let i = 0; i < MAX_VIEWPORTS; i++) { for (let i = 0; i < MAX_VIEWPORTS; i++) {
const viewportId = `CT_${i}`; const viewportId = `CT_${i}`;
@@ -408,6 +458,11 @@ function App() {
? Enums.ViewportType.ORTHOGRAPHIC // or Enums.ViewportType.VOLUME if supported ? Enums.ViewportType.ORTHOGRAPHIC // or Enums.ViewportType.VOLUME if supported
: Enums.ViewportType.STACK; : Enums.ViewportType.STACK;
// Choose tool group based on viewport type
const toolGroup = viewportModes[i] === 'volume' ? volumeToolGroup : stackToolGroup;
if (!element._cornerstoneEnabled || element._lastViewportType !== viewportType) { if (!element._cornerstoneEnabled || element._lastViewportType !== viewportType) {
if (renderingEngine.getViewport(viewportId)) { if (renderingEngine.getViewport(viewportId)) {
renderingEngine.disableElement(viewportId); renderingEngine.disableElement(viewportId);
@@ -425,25 +480,13 @@ function App() {
// Add tool group only once // Add tool group only once
toolGroup.addViewport(viewportId, renderingEngineId); toolGroup.addViewport(viewportId, renderingEngineId);
// Inside your setup loop for each viewport
if (viewportModes[i] === 'volume' && crossReferenceEnabled) {
if (!toolGroup.hasTool(CrosshairsTool.toolName)) {
toolGroup.addTool(CrosshairsTool.toolName);
}
toolGroup.setToolEnabled(CrosshairsTool.toolName);
toolGroup.setToolActive(CrosshairsTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }],
});
} else if (toolGroup.hasTool(CrosshairsTool.toolName)) {
toolGroup.setToolDisabled(CrosshairsTool.toolName);
}
const viewport = renderingEngine.getViewport(viewportId); const viewport = renderingEngine.getViewport(viewportId);
if (!viewport) continue; if (!viewport) continue;
if (!element._resizeObserver) { if (!element._resizeObserver) {
const resizeObserver = new window.ResizeObserver(() => { const resizeObserver = new window.ResizeObserver(() => {
console.log("Resize observer triggered for viewport", i);
if (element.offsetWidth > 0 && element.offsetHeight > 0) { if (element.offsetWidth > 0 && element.offsetHeight > 0) {
renderingEngine.resize(); renderingEngine.resize();
// Always get the latest viewport instance // Always get the latest viewport instance
@@ -467,6 +510,17 @@ function App() {
// Restore scroll, properties, etc. // Restore scroll, properties, etc.
} else if (viewportModes[i] === 'volume') { } else if (viewportModes[i] === 'volume') {
let sortedImageIds = imageIds;
if (imageIds && imageIds.length > 1) {
sortedImageIds = [...imageIds].sort((a, b) => {
const metaA = metaData.get('imagePlaneModule', a);
const metaB = metaData.get('imagePlaneModule', b);
const sliceA = metaA && metaA.sliceLocation !== undefined ? Number(metaA.sliceLocation) : 0;
const sliceB = metaB && metaB.sliceLocation !== undefined ? Number(metaB.sliceLocation) : 0;
return sliceA - sliceB;
});
}
// Example: create and set a volume (you may need to adapt this for your data) // Example: create and set a volume (you may need to adapt this for your data)
const volumeId = 'myVolumeId'; const volumeId = 'myVolumeId';
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds }); const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds });
@@ -635,62 +689,30 @@ function App() {
if (!loadedImageIds || loadedImageIds.length === 0) return; if (!loadedImageIds || loadedImageIds.length === 0) return;
if (!orderBySliceLocation) return; if (!orderBySliceLocation) return;
// Helper to get slice location from metadata let cancelled = false;
const getSliceLocation = (imageId: string) => {
const meta = metaData.get('imagePlaneModule', imageId);
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
};
// Build array of { imageId, sliceLocation } const checkAndSort = () => {
const withSliceLoc = loadedImageIds.map(id => ({ // Check if all metadata is loaded
imageId: id,
sliceLoc: getSliceLocation(id),
}));
// Sort by slice location, fallback to name if missing
const sorted = [...withSliceLoc].sort((a, b) => {
if (a.sliceLoc !== null && b.sliceLoc !== null) {
return a.sliceLoc - b.sliceLoc;
}
return a.imageId.localeCompare(b.imageId);
});
// Only update if the order actually changed
const sortedIds = sorted.map(obj => obj.imageId);
const isSame =
loadedImageIds.length === sortedIds.length &&
loadedImageIds.every((id, idx) => id === sortedIds[idx]);
if (!isSame) {
setLoadedImageIds(sortedIds);
}
// eslint-disable-next-line
}, [orderBySliceLocation]);
useEffect(() => {
if (!loadedImageIds || loadedImageIds.length === 0) return;
// Only trigger if orderBySliceLocation is enabled
if (!orderBySliceLocation) return;
// Check if all images have metadata loaded
const allLoaded = loadedImageIds.every( const allLoaded = loadedImageIds.every(
id => !!metaData.get("imagePlaneModule", id) id => !!metaData.get("imagePlaneModule", id)
); );
if (!allLoaded) return; if (!allLoaded) {
// Try again soon
setTimeout(checkAndSort, 200);
return;
}
// Helper to get slice location from metadata // All metadata loaded, sort by slice location
const getSliceLocation = (imageId: string) => { const getSliceLocation = (imageId: string) => {
const meta = metaData.get('imagePlaneModule', imageId); const meta = metaData.get('imagePlaneModule', imageId);
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null; return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
}; };
// Build array of { imageId, sliceLocation }
const withSliceLoc = loadedImageIds.map(id => ({ const withSliceLoc = loadedImageIds.map(id => ({
imageId: id, imageId: id,
sliceLoc: getSliceLocation(id), sliceLoc: getSliceLocation(id),
})); }));
// Sort by slice location, fallback to name if missing
const sorted = [...withSliceLoc].sort((a, b) => { const sorted = [...withSliceLoc].sort((a, b) => {
if (a.sliceLoc !== null && b.sliceLoc !== null) { if (a.sliceLoc !== null && b.sliceLoc !== null) {
return a.sliceLoc - b.sliceLoc; return a.sliceLoc - b.sliceLoc;
@@ -698,16 +720,20 @@ function App() {
return a.imageId.localeCompare(b.imageId); return a.imageId.localeCompare(b.imageId);
}); });
// Only update if the order actually changed
const sortedIds = sorted.map(obj => obj.imageId); const sortedIds = sorted.map(obj => obj.imageId);
const isSame = const isSame =
loadedImageIds.length === sortedIds.length && loadedImageIds.length === sortedIds.length &&
loadedImageIds.every((id, idx) => id === sortedIds[idx]); loadedImageIds.every((id, idx) => id === sortedIds[idx]);
if (!isSame) { if (!isSame && !cancelled) {
setLoadedImageIds(sortedIds); setLoadedImageIds(sortedIds);
} }
}, [orderBySliceLocation, loadedImageIds]); };
checkAndSort();
return () => { cancelled = true; };
}, [loadedImageIds, orderBySliceLocation]);
const viewports = []; const viewports = [];
@@ -1215,28 +1241,70 @@ function App() {
> >
{crossReferenceEnabled ? "Disable" : "Enable"} Cross Reference Lines {crossReferenceEnabled ? "Disable" : "Enable"} Cross Reference Lines
</button> </button>
<button
style={{
padding: "10px 18px",
background: referenceLinesEnabled ? "#1976d2" : "#333",
color: "#fff",
border: "none",
borderRadius: "4px",
fontWeight: "bold",
cursor: "pointer",
fontSize: "15px",
marginBottom: "18px",
width: "100%",
}}
onClick={() => setReferenceLinesEnabled((prev) => !prev)}
>
{referenceLinesEnabled ? "Disable" : "Enable"} Reference Lines
</button>
{/* Add more menu items here if needed */} {/* Add more menu items here if needed */}
</div> </div>
</div> </div>
{/* Viewport grid menu at top center */} {/* Viewport grid menu at top center */}
<div <div
className="grid-menu-hover-container" className="grid-menu-hover-container"
style={{ style={{
position: "absolute", position: "absolute",
top: 12, top: 0,
left: "50%", left: "50%",
transform: "translateX(-50%)", transform: "translateX(-50%)",
zIndex: 100, zIndex: 100,
// Always present for hover display: "flex",
flexDirection: "column",
alignItems: "center",
width: "auto",
height: "48px",
pointerEvents: "none", // Only the hit area and button will be interactive
}} }}
> >
{/* Transparent hit area to catch hover/focus */}
<div
style={{
position: "absolute",
top: 0,
left: "50%",
transform: "translateX(-50%)",
width: 80,
height: 14,
zIndex: 1,
pointerEvents: "auto",
background: "transparent",
}}
onMouseEnter={() => setGridBtnActive(true)}
onFocus={() => setGridBtnActive(true)}
tabIndex={-1}
/>
<button <button
className="grid-menu-btn" className="grid-menu-btn"
style={{ style={{
opacity: 0, position: "relative",
transition: "opacity 0.2s", top: gridBtnActive ? "0" : "-22px",
pointerEvents: "auto", opacity: gridBtnActive ? 1 : 0.5,
transition: "top 0.25s cubic-bezier(.4,2,.6,1), opacity 0.2s",
padding: "6px 16px", padding: "6px 16px",
background: "#222", background: "#222",
color: "#fff", color: "#fff",
@@ -1246,13 +1314,18 @@ function App() {
cursor: "pointer", cursor: "pointer",
fontSize: "15px", fontSize: "15px",
marginRight: "8px", marginRight: "8px",
boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
pointerEvents: gridBtnActive ? "auto" : "none", // Only clickable when active
zIndex: 2,
}} }}
onMouseLeave={() => setGridBtnActive(false)}
onBlur={() => setGridBtnActive(false)}
onClick={() => setViewportMenuOpen(open => !open)} onClick={() => setViewportMenuOpen(open => !open)}
tabIndex={0}
> >
Grid: {viewportGrid.rows}x{viewportGrid.cols} Grid: {viewportGrid.rows}x{viewportGrid.cols}
</button> </button>
{viewportMenuOpen && ( {viewportMenuOpen && (
// ...your grid menu dropdown...
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@@ -1268,6 +1341,7 @@ function App() {
gridTemplateColumns: "repeat(3, 40px)", gridTemplateColumns: "repeat(3, 40px)",
gap: "8px", gap: "8px",
zIndex: 200, zIndex: 200,
pointerEvents: "auto",
}} }}
> >
{gridOptions.map(opt => ( {gridOptions.map(opt => (
@@ -1297,6 +1371,7 @@ function App() {
)} )}
</div> </div>
{/* Viewport grid (inside main div) */} {/* Viewport grid (inside main div) */}
<div <div
style={{ style={{
@@ -1340,7 +1415,8 @@ function App() {
</button> </button>
{/* Settings Modal (viewport-local, not fixed to page) */} {/* Settings Modal (viewport-local, not fixed to page) */}
{settingsOpen && ( {
settingsOpen && (
<div <div
style={{ style={{
position: "absolute", position: "absolute",
@@ -1437,8 +1513,10 @@ function App() {
</button> </button>
</div> </div>
</div> </div>
)} )
{metaModalOpen && ( }
{
metaModalOpen && (
<div <div
style={{ style={{
position: "fixed", position: "fixed",
@@ -1491,7 +1569,8 @@ function App() {
</button> </button>
</div> </div>
</div> </div>
)} )
}
{/* ...rest of your UI... */} {/* ...rest of your UI... */}
</div > </div >
@@ -1515,45 +1594,51 @@ function setupTools() {
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool) cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
cornerstoneTools.addTool(SculptorTool) cornerstoneTools.addTool(SculptorTool)
cornerstoneTools.addTool(CrosshairsTool) cornerstoneTools.addTool(CrosshairsTool)
cornerstoneTools.addTool(ReferenceLinesTool);
// Create tool group ONCE
let toolGroup = ToolGroupManager.getToolGroup(toolGroupId) // Stack tool group (for stack viewports)
if (!toolGroup) { let stackToolGroup = ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
toolGroup = ToolGroupManager.createToolGroup(toolGroupId) if (!stackToolGroup) {
toolGroup.addTool(WindowLevelTool.toolName) stackToolGroup = ToolGroupManager.createToolGroup(STACK_TOOL_GROUP_ID);
toolGroup.addTool(PanTool.toolName) stackToolGroup.addTool(WindowLevelTool.toolName);
toolGroup.addTool(ZoomTool.toolName) stackToolGroup.addTool(PanTool.toolName);
toolGroup.addTool(StackScrollTool.toolName, { loop: false }) stackToolGroup.addTool(ZoomTool.toolName);
toolGroup.addTool(PlanarRotateTool.toolName) stackToolGroup.addTool(StackScrollTool.toolName, { loop: false });
toolGroup.addTool(LengthTool.toolName) stackToolGroup.addTool(PlanarRotateTool.toolName);
toolGroup.addTool(ArrowAnnotateTool.toolName, { stackToolGroup.addTool(LengthTool.toolName);
getTextCallback: () => '', stackToolGroup.addTool(ArrowAnnotateTool.toolName, { getTextCallback: () => '' });
}) stackToolGroup.addTool(ProbeTool.toolName, { statsCalculator: () => null });
toolGroup.addTool(ProbeTool.toolName, { stackToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
statsCalculator: () => null, stackToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
}) stackToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
toolGroup.addTool(RectangleROITool.toolName, { stackToolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {});
calculateStats: false, stackToolGroup.addTool(SculptorTool.toolName, {});
getTextCallback: () => '', // StackScrollTool mouse wheel binding
}) stackToolGroup.setToolActive(StackScrollTool.toolName, {
toolGroup.addTool(EllipticalROITool.toolName, { bindings: [{ mouseButton: MouseBindings.Wheel }],
getTextCallback: () => '', });
})
toolGroup.addTool(PlanarFreehandROITool.toolName, {
calculateStats: false,
})
toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {})
toolGroup.addTool(SculptorTool.toolName, {})
} }
// The Stack Scroll mouse wheel is a tool using the `mouseWheelCallback` // Volume tool group (for volume/orthographic viewports)
// and needs to be registered against the 'Wheel' binding. let volumeToolGroup = ToolGroupManager.getToolGroup(VOLUME_TOOL_GROUP_ID);
toolGroup.setToolActive(StackScrollTool.toolName, { if (!volumeToolGroup) {
bindings: [ volumeToolGroup = ToolGroupManager.createToolGroup(VOLUME_TOOL_GROUP_ID);
{ volumeToolGroup.addTool(StackScrollTool.toolName, { loop: false });
mouseButton: MouseBindings.Wheel, // Wheel Mouse volumeToolGroup.addTool(WindowLevelTool.toolName);
}, volumeToolGroup.addTool(PanTool.toolName);
], volumeToolGroup.addTool(ZoomTool.toolName);
}) volumeToolGroup.addTool(PlanarRotateTool.toolName);
return toolGroup volumeToolGroup.addTool(LengthTool.toolName);
volumeToolGroup.addTool(ArrowAnnotateTool.toolName, { getTextCallback: () => '' });
volumeToolGroup.addTool(ProbeTool.toolName, { statsCalculator: () => null });
volumeToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
volumeToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
volumeToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
volumeToolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {});
volumeToolGroup.addTool(SculptorTool.toolName, {});
//volumeToolGroup.addTool(CrosshairsTool.toolName, {});
}
return { stackToolGroup, volumeToolGroup };
} }
+2 -1
View File
@@ -9,7 +9,7 @@ html, body, #root {
box-sizing: border-box; box-sizing: border-box;
} }
.grid-menu-hover-container .grid-menu-btn { /* .grid-menu-hover-container .grid-menu-btn {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transition: opacity 0.2s; transition: opacity 0.2s;
@@ -19,3 +19,4 @@ html, body, #root {
opacity: 1 !important; opacity: 1 !important;
pointer-events: auto; pointer-events: auto;
} }
*/
+2 -2
View File
@@ -63,13 +63,13 @@ import { readFile } from "dicom-parser"
return imageIds return imageIds
} */ } */
const NUM_IMAGES = 152 // set to however many images you have const NUM_IMAGES = 15 // set to however many images you have
export default async function createImageIds() { export default async function createImageIds() {
const imageIds = [] const imageIds = []
for (let i = 1; i <= NUM_IMAGES; i++) { for (let i = 1; i <= NUM_IMAGES; i++) {
const fileName = `IMG${String(i)}.dcm` const fileName = `IMG${String(i)}.dcm`
imageIds.push(`wadouri:/test_images/stack4/${fileName}`) imageIds.push(`wadouri:/test_images/stack2/${fileName}`)
} }
return imageIds return imageIds
} }