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.
+370 -285
View File
@@ -6,7 +6,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 } 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 * as cornerstoneTools from '@cornerstonejs/tools';
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 VOLUME_TOOL_GROUP_ID = 'VOLUME_TOOL_GROUP_ID';
const toolGroupId = 'STACK_TOOL_GROUP_ID';
// Common CT presets (add more as needed)
const WINDOW_LEVEL_PRESETS = [
@@ -87,6 +86,7 @@ function App() {
const [loadedImageIds, setLoadedImageIds] = useState<string[] | null>(null);
const [crossReferenceEnabled, setCrossReferenceEnabled] = useState(true);
const [referenceLinesEnabled, setReferenceLinesEnabled] = useState(false);
// Wrap setLoadedImageIds to also cache DICOM metadata
const setLoadedImageIdsAndCacheMeta = async (imageIds: string[]) => {
@@ -98,9 +98,9 @@ function App() {
const url = imageId.slice(8);
// Only cache if it's a blob or file URL
if (url.startsWith("blob:") || url.startsWith("/")) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
//const response = await fetch(url);
//const arrayBuffer = await response.arrayBuffer();
//const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) {
cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.load(url);
@@ -251,32 +251,36 @@ function App() {
};
// Helper to apply all mouse tool bindings (normal or ctrl)
const applyMouseToolBindings = useCallback(() => {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
const applyMouseToolBindings = useCallback(() => {
// 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) {
// Set all tools to passive first, removing all bindings
TOOL_OPTIONS.forEach(opt => {
toolGroup.setToolPassive(opt.value, { removeAllBindings: true })
})
toolGroup.setToolPassive(opt.value, { removeAllBindings: true });
});
// Collect all bindings for each tool
const toolBindings: Record<string, Array<any>> = {}
const toolBindings: Record<string, Array<any>> = {};
// Normal bindings
Object.entries(mouseToolBindings).forEach(([btn, tool]) => {
if (!toolBindings[tool]) toolBindings[tool] = []
toolBindings[tool].push({ mouseButton: MouseBindings[btn] })
})
if (!toolBindings[tool]) toolBindings[tool] = [];
toolBindings[tool].push({ mouseButton: MouseBindings[btn] });
});
// Ctrl bindings
Object.entries(ctrlMouseToolBindings).forEach(([btn, tool]) => {
if (!toolBindings[tool]) toolBindings[tool] = []
toolBindings[tool].push({ mouseButton: MouseBindings[btn], modifierKey: KeyboardBindings.Ctrl })
})
if (!toolBindings[tool]) toolBindings[tool] = [];
toolBindings[tool].push({ mouseButton: MouseBindings[btn], modifierKey: KeyboardBindings.Ctrl });
});
// Apply all bindings for each tool in a single call
Object.entries(toolBindings).forEach(([tool, bindings]) => {
toolGroup.setToolActive(tool, { bindings })
})
toolGroup.setToolActive(tool, { bindings });
});
// Always re-apply the mouse wheel binding for StackScrollTool
toolGroup.setToolActive(StackScrollTool.toolName, {
@@ -285,9 +289,10 @@ function App() {
mouseButton: MouseBindings.Wheel, // Wheel Mouse
},
],
})
});
}
}, [mouseToolBindings, ctrlMouseToolBindings])
});
}, [mouseToolBindings, ctrlMouseToolBindings]);
const [sideMenuOpen, setSideMenuOpen] = useState(false);
@@ -353,6 +358,8 @@ function App() {
});
}, []);
const [gridBtnActive, setGridBtnActive] = useState(false);
function saveVisibleViewportState() {
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
const viewportId = `CT_${i}`;
@@ -375,7 +382,9 @@ function App() {
if (!running.current) {
running.current = true;
await csRenderInit();
await csToolsInit();
await csToolsInit(
{ showSVGCursors: true, }
);
dicomImageLoaderInit({ maxWebWorkers: 1 });
}
@@ -393,10 +402,51 @@ function App() {
renderingEngineRef.current = renderingEngine;
}
// 4. Setup tool group
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) || setupTools();
// --- In your setup function, use the correct tool group per viewport ---
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
for (let i = 0; i < MAX_VIEWPORTS; i++) {
const viewportId = `CT_${i}`;
@@ -408,6 +458,11 @@ function App() {
? Enums.ViewportType.ORTHOGRAPHIC // or Enums.ViewportType.VOLUME if supported
: Enums.ViewportType.STACK;
// Choose tool group based on viewport type
const toolGroup = viewportModes[i] === 'volume' ? volumeToolGroup : stackToolGroup;
if (!element._cornerstoneEnabled || element._lastViewportType !== viewportType) {
if (renderingEngine.getViewport(viewportId)) {
renderingEngine.disableElement(viewportId);
@@ -425,25 +480,13 @@ function App() {
// Add tool group only once
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);
if (!viewport) continue;
if (!element._resizeObserver) {
const resizeObserver = new window.ResizeObserver(() => {
console.log("Resize observer triggered for viewport", i);
if (element.offsetWidth > 0 && element.offsetHeight > 0) {
renderingEngine.resize();
// Always get the latest viewport instance
@@ -467,6 +510,17 @@ function App() {
// Restore scroll, properties, etc.
} 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)
const volumeId = 'myVolumeId';
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds });
@@ -635,79 +689,51 @@ function App() {
if (!loadedImageIds || loadedImageIds.length === 0) return;
if (!orderBySliceLocation) return;
// Helper to get slice location from metadata
const getSliceLocation = (imageId: string) => {
const meta = metaData.get('imagePlaneModule', imageId);
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
let cancelled = false;
const checkAndSort = () => {
// Check if all metadata is loaded
const allLoaded = loadedImageIds.every(
id => !!metaData.get("imagePlaneModule", id)
);
if (!allLoaded) {
// Try again soon
setTimeout(checkAndSort, 200);
return;
}
// All metadata loaded, sort by slice location
const getSliceLocation = (imageId: string) => {
const meta = metaData.get('imagePlaneModule', imageId);
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
};
const withSliceLoc = loadedImageIds.map(id => ({
imageId: id,
sliceLoc: getSliceLocation(id),
}));
const sorted = [...withSliceLoc].sort((a, b) => {
if (a.sliceLoc !== null && b.sliceLoc !== null) {
return a.sliceLoc - b.sliceLoc;
}
return a.imageId.localeCompare(b.imageId);
});
const sortedIds = sorted.map(obj => obj.imageId);
const isSame =
loadedImageIds.length === sortedIds.length &&
loadedImageIds.every((id, idx) => id === sortedIds[idx]);
if (!isSame && !cancelled) {
setLoadedImageIds(sortedIds);
}
};
// Build array of { imageId, sliceLocation }
const withSliceLoc = loadedImageIds.map(id => ({
imageId: id,
sliceLoc: getSliceLocation(id),
}));
checkAndSort();
// 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(
id => !!metaData.get("imagePlaneModule", id)
);
if (!allLoaded) return;
// Helper to get slice location from metadata
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 withSliceLoc = loadedImageIds.map(id => ({
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);
}
}, [orderBySliceLocation, loadedImageIds]);
return () => { cancelled = true; };
}, [loadedImageIds, orderBySliceLocation]);
const viewports = [];
@@ -1215,28 +1241,70 @@ function App() {
>
{crossReferenceEnabled ? "Disable" : "Enable"} Cross Reference Lines
</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 */}
</div>
</div>
{/* Viewport grid menu at top center */}
<div
className="grid-menu-hover-container"
style={{
position: "absolute",
top: 12,
top: 0,
left: "50%",
transform: "translateX(-50%)",
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
className="grid-menu-btn"
style={{
opacity: 0,
transition: "opacity 0.2s",
pointerEvents: "auto",
position: "relative",
top: gridBtnActive ? "0" : "-22px",
opacity: gridBtnActive ? 1 : 0.5,
transition: "top 0.25s cubic-bezier(.4,2,.6,1), opacity 0.2s",
padding: "6px 16px",
background: "#222",
color: "#fff",
@@ -1246,13 +1314,18 @@ function App() {
cursor: "pointer",
fontSize: "15px",
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)}
tabIndex={0}
>
Grid: {viewportGrid.rows}x{viewportGrid.cols}
</button>
{viewportMenuOpen && (
// ...your grid menu dropdown...
<div
style={{
position: "absolute",
@@ -1268,6 +1341,7 @@ function App() {
gridTemplateColumns: "repeat(3, 40px)",
gap: "8px",
zIndex: 200,
pointerEvents: "auto",
}}
>
{gridOptions.map(opt => (
@@ -1297,6 +1371,7 @@ function App() {
)}
</div>
{/* Viewport grid (inside main div) */}
<div
style={{
@@ -1340,161 +1415,165 @@ function App() {
</button>
{/* Settings Modal (viewport-local, not fixed to page) */}
{settingsOpen && (
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
background: "rgba(0,0,0,0.4)",
zIndex: 1000,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={() => setSettingsOpen(false)}
>
{
settingsOpen && (
<div
style={{
background: "#222",
color: "#fff",
borderRadius: "8px",
minWidth: "320px",
minHeight: "180px",
padding: "24px",
boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
position: "relative",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
background: "rgba(0,0,0,0.4)",
zIndex: 1000,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={e => e.stopPropagation()}
onClick={() => setSettingsOpen(false)}
>
<div style={{ fontSize: "20px", marginBottom: "16px" }}>
Settings
</div>
{/* Settings content: Tool dropdowns */}
<div style={{ marginBottom: "24px" }}>
<div style={{ marginBottom: 12, fontWeight: "bold" }}>Mouse Button Tool Bindings</div>
{MOUSE_BUTTONS.map((btn) => (
<div key={btn.value} style={{ marginBottom: 10 }}>
<label style={{ marginRight: 8 }}>{btn.label} Button:</label>
<select
value={mouseToolBindings[btn.value]}
onChange={e => setMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
style={{
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "4px",
padding: "4px 8px",
fontSize: "14px",
}}
>
{TOOL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
))}
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
{MOUSE_BUTTONS.map((btn) => (
<div key={btn.value + "_ctrl"} style={{ marginBottom: 10 }}>
<label style={{ marginRight: 8 }}>{btn.label} + Ctrl:</label>
<select
value={ctrlMouseToolBindings[btn.value]}
onChange={e => setCtrlMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
style={{
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "4px",
padding: "4px 8px",
fontSize: "14px",
}}
>
{TOOL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
))}
</div>
<button
<div
style={{
position: "absolute",
top: 12,
right: 12,
background: "transparent",
background: "#222",
color: "#fff",
border: "none",
fontSize: "20px",
cursor: "pointer",
borderRadius: "8px",
minWidth: "320px",
minHeight: "180px",
padding: "24px",
boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
position: "relative",
}}
onClick={() => setSettingsOpen(false)}
aria-label="Close"
onClick={e => e.stopPropagation()}
>
×
</button>
<div style={{ fontSize: "20px", marginBottom: "16px" }}>
Settings
</div>
{/* Settings content: Tool dropdowns */}
<div style={{ marginBottom: "24px" }}>
<div style={{ marginBottom: 12, fontWeight: "bold" }}>Mouse Button Tool Bindings</div>
{MOUSE_BUTTONS.map((btn) => (
<div key={btn.value} style={{ marginBottom: 10 }}>
<label style={{ marginRight: 8 }}>{btn.label} Button:</label>
<select
value={mouseToolBindings[btn.value]}
onChange={e => setMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
style={{
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "4px",
padding: "4px 8px",
fontSize: "14px",
}}
>
{TOOL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
))}
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
{MOUSE_BUTTONS.map((btn) => (
<div key={btn.value + "_ctrl"} style={{ marginBottom: 10 }}>
<label style={{ marginRight: 8 }}>{btn.label} + Ctrl:</label>
<select
value={ctrlMouseToolBindings[btn.value]}
onChange={e => setCtrlMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
style={{
background: "#333",
color: "#fff",
border: "1px solid #444",
borderRadius: "4px",
padding: "4px 8px",
fontSize: "14px",
}}
>
{TOOL_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
</div>
))}
</div>
<button
style={{
position: "absolute",
top: 12,
right: 12,
background: "transparent",
color: "#fff",
border: "none",
fontSize: "20px",
cursor: "pointer",
}}
onClick={() => setSettingsOpen(false)}
aria-label="Close"
>
×
</button>
</div>
</div>
</div>
)}
{metaModalOpen && (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "rgba(0,0,0,0.5)",
zIndex: 2000,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={() => setMetaModalOpen(false)}
>
)
}
{
metaModalOpen && (
<div
style={{
background: "#222",
color: "#fff",
borderRadius: "8px",
minWidth: "320px",
maxWidth: "80vw",
maxHeight: "80vh",
padding: "24px",
boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
position: "relative",
overflow: "auto",
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "rgba(0,0,0,0.5)",
zIndex: 2000,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={e => e.stopPropagation()}
onClick={() => setMetaModalOpen(false)}
>
<div style={{ fontSize: "20px", marginBottom: "16px" }}>
DICOM Metadata
</div>
{metaModalContent}
<button
<div
style={{
position: "absolute",
top: 12,
right: 12,
background: "transparent",
background: "#222",
color: "#fff",
border: "none",
fontSize: "20px",
cursor: "pointer",
borderRadius: "8px",
minWidth: "320px",
maxWidth: "80vw",
maxHeight: "80vh",
padding: "24px",
boxShadow: "0 4px 24px rgba(0,0,0,0.4)",
position: "relative",
overflow: "auto",
}}
onClick={() => setMetaModalOpen(false)}
aria-label="Close"
onClick={e => e.stopPropagation()}
>
×
</button>
<div style={{ fontSize: "20px", marginBottom: "16px" }}>
DICOM Metadata
</div>
{metaModalContent}
<button
style={{
position: "absolute",
top: 12,
right: 12,
background: "transparent",
color: "#fff",
border: "none",
fontSize: "20px",
cursor: "pointer",
}}
onClick={() => setMetaModalOpen(false)}
aria-label="Close"
>
×
</button>
</div>
</div>
</div>
)}
)
}
{/* ...rest of your UI... */}
</div>
</div >
)
}
@@ -1515,45 +1594,51 @@ function setupTools() {
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
cornerstoneTools.addTool(SculptorTool)
cornerstoneTools.addTool(CrosshairsTool)
cornerstoneTools.addTool(ReferenceLinesTool);
// Create tool group ONCE
let toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) {
toolGroup = ToolGroupManager.createToolGroup(toolGroupId)
toolGroup.addTool(WindowLevelTool.toolName)
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(StackScrollTool.toolName, { loop: false })
toolGroup.addTool(PlanarRotateTool.toolName)
toolGroup.addTool(LengthTool.toolName)
toolGroup.addTool(ArrowAnnotateTool.toolName, {
getTextCallback: () => '',
})
toolGroup.addTool(ProbeTool.toolName, {
statsCalculator: () => null,
})
toolGroup.addTool(RectangleROITool.toolName, {
calculateStats: false,
getTextCallback: () => '',
})
toolGroup.addTool(EllipticalROITool.toolName, {
getTextCallback: () => '',
})
toolGroup.addTool(PlanarFreehandROITool.toolName, {
calculateStats: false,
})
toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {})
toolGroup.addTool(SculptorTool.toolName, {})
// Stack tool group (for stack viewports)
let stackToolGroup = ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
if (!stackToolGroup) {
stackToolGroup = ToolGroupManager.createToolGroup(STACK_TOOL_GROUP_ID);
stackToolGroup.addTool(WindowLevelTool.toolName);
stackToolGroup.addTool(PanTool.toolName);
stackToolGroup.addTool(ZoomTool.toolName);
stackToolGroup.addTool(StackScrollTool.toolName, { loop: false });
stackToolGroup.addTool(PlanarRotateTool.toolName);
stackToolGroup.addTool(LengthTool.toolName);
stackToolGroup.addTool(ArrowAnnotateTool.toolName, { getTextCallback: () => '' });
stackToolGroup.addTool(ProbeTool.toolName, { statsCalculator: () => null });
stackToolGroup.addTool(RectangleROITool.toolName, { calculateStats: false, getTextCallback: () => '' });
stackToolGroup.addTool(EllipticalROITool.toolName, { getTextCallback: () => '' });
stackToolGroup.addTool(PlanarFreehandROITool.toolName, { calculateStats: false });
stackToolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {});
stackToolGroup.addTool(SculptorTool.toolName, {});
// StackScrollTool mouse wheel binding
stackToolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }],
});
}
// The Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
// and needs to be registered against the 'Wheel' binding.
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Wheel, // Wheel Mouse
},
],
})
return toolGroup
// Volume tool group (for volume/orthographic viewports)
let volumeToolGroup = ToolGroupManager.getToolGroup(VOLUME_TOOL_GROUP_ID);
if (!volumeToolGroup) {
volumeToolGroup = ToolGroupManager.createToolGroup(VOLUME_TOOL_GROUP_ID);
volumeToolGroup.addTool(StackScrollTool.toolName, { loop: false });
volumeToolGroup.addTool(WindowLevelTool.toolName);
volumeToolGroup.addTool(PanTool.toolName);
volumeToolGroup.addTool(ZoomTool.toolName);
volumeToolGroup.addTool(PlanarRotateTool.toolName);
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 };
}
+3 -2
View File
@@ -9,7 +9,7 @@ html, body, #root {
box-sizing: border-box;
}
.grid-menu-hover-container .grid-menu-btn {
/* .grid-menu-hover-container .grid-menu-btn {
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
@@ -18,4 +18,5 @@ html, body, #root {
.grid-menu-btn:focus {
opacity: 1 !important;
pointer-events: auto;
}
}
*/
+2 -2
View File
@@ -63,13 +63,13 @@ import { readFile } from "dicom-parser"
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() {
const imageIds = []
for (let i = 1; i <= NUM_IMAGES; i++) {
const fileName = `IMG${String(i)}.dcm`
imageIds.push(`wadouri:/test_images/stack4/${fileName}`)
imageIds.push(`wadouri:/test_images/stack2/${fileName}`)
}
return imageIds
}