start ortho automode
This commit is contained in:
+343
-69
@@ -1152,11 +1152,119 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadStackOrthogonal = async (stackIdx: number) => {
|
||||
const stackObj = availableStacks[stackIdx];
|
||||
if (!stackObj) return;
|
||||
|
||||
setLoadingState("displayset");
|
||||
try {
|
||||
const hasDeclaredGroups = stackObj.groupingSource === "declared" && (stackObj.seriesGroups || []).length > 0;
|
||||
const shouldExpandMultiframe = !(hasDeclaredGroups && (stackObj.imageIds || []).length > 1);
|
||||
const stack = shouldExpandMultiframe
|
||||
? await expandMultiframeImageIds(stackObj.imageIds || [])
|
||||
: dedupeImageIds((stackObj.imageIds || []).map(normalizeToWadouriOrExternal));
|
||||
|
||||
await setLoadedImageIdsAndCacheMeta(stack, { force: true });
|
||||
const seriesGroups = hasDeclaredGroups
|
||||
? (stackObj.seriesGroups || [])
|
||||
: await deriveSeriesGroupsFromImageIds(stack);
|
||||
const primarySeries = seriesGroups[0]?.imageIds || stack;
|
||||
|
||||
// 1. Set grid layout to 1x3 (1 row, 3 columns)
|
||||
setViewportGrid({ rows: 1, cols: 3 });
|
||||
|
||||
// 2. Set viewport modes: first 3 viewports as 'volume', others default to 'stack'
|
||||
setViewportModes(prev => {
|
||||
const next = [...prev];
|
||||
next[0] = 'volume';
|
||||
next[1] = 'volume';
|
||||
next[2] = 'volume';
|
||||
return next;
|
||||
});
|
||||
|
||||
// 3. Set viewport image IDs for first 3 viewports
|
||||
setViewportImageIds(prev => {
|
||||
const next = [...prev];
|
||||
next[0] = primarySeries;
|
||||
next[1] = primarySeries;
|
||||
next[2] = primarySeries;
|
||||
return next;
|
||||
});
|
||||
|
||||
// 4. Set selected stack index for first 3 viewports
|
||||
setSelectedStackIdx(prev => {
|
||||
const next = [...prev];
|
||||
next[0] = stackIdx;
|
||||
next[1] = stackIdx;
|
||||
next[2] = stackIdx;
|
||||
return next;
|
||||
});
|
||||
|
||||
// 5. Set series groups for first 3 viewports
|
||||
setSeriesGroupsByViewport(prev => {
|
||||
const next = [...prev];
|
||||
next[0] = seriesGroups || [];
|
||||
next[1] = seriesGroups || [];
|
||||
next[2] = seriesGroups || [];
|
||||
return next;
|
||||
});
|
||||
|
||||
// 6. Set selected series index for first 3 viewports
|
||||
setSelectedSeriesIdxByViewport(prev => {
|
||||
const next = [...prev];
|
||||
next[0] = 0;
|
||||
next[1] = 0;
|
||||
next[2] = 0;
|
||||
return next;
|
||||
});
|
||||
|
||||
// 7. Enable crosshairs
|
||||
setCrossHairsEnabled(true);
|
||||
|
||||
// 8. Cache imageIds and load metadata for this stack
|
||||
await setLoadedImageIdsAndCacheMeta(primarySeries, { force: true });
|
||||
|
||||
// 9. After a short delay, set orientations to Axial, Sagittal, Coronal
|
||||
setTimeout(() => {
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (renderingEngine) {
|
||||
const orientations = ["axial", "sagittal", "coronal"];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const vp = renderingEngine.getViewport(`CT_${i}`);
|
||||
if (vp && typeof vp.setOrientation === "function") {
|
||||
try {
|
||||
vp.setOrientation(orientations[i]);
|
||||
vp.render();
|
||||
} catch (e) {
|
||||
appLogger.warn("Failed to set orthogonal orientation for viewport", i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
} catch (err) {
|
||||
appLogger.error("Failed to load stack orthogonally:", err);
|
||||
} finally {
|
||||
setLoadingState(null);
|
||||
}
|
||||
};
|
||||
|
||||
const [crossHairsEnabled, setCrossHairsEnabled] = useState(false);
|
||||
const [crosshairsProjectionType, setCrosshairsProjectionType] = useState<string>("None");
|
||||
const [syncSlabThickness, setSyncSlabThickness] = useState<boolean>(true);
|
||||
const [showCenterPoint, setShowCenterPoint] = useState<boolean>(true);
|
||||
const slabThicknessSynchronizerRef = useRef<any>(null);
|
||||
const setupEpochRef = useRef(0);
|
||||
const [setupInProgress, setSetupInProgress] = useState(false);
|
||||
const setupInProgressRef = useRef(setupInProgress);
|
||||
useEffect(() => {
|
||||
setupInProgressRef.current = setupInProgress;
|
||||
}, [setupInProgress]);
|
||||
const syncSlabThicknessRef = useRef(syncSlabThickness);
|
||||
useEffect(() => {
|
||||
syncSlabThicknessRef.current = syncSlabThickness;
|
||||
}, [syncSlabThickness]);
|
||||
const [referenceLinesEnabled, setReferenceLinesEnabled] = useState(true);
|
||||
|
||||
const [showAdvancedMouseBindings, setShowAdvancedMouseBindings] = useState(false);
|
||||
@@ -2510,25 +2618,30 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
useEffect(() => {
|
||||
const mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
|
||||
if (!mainToolGroup) return;
|
||||
const STACK_TOOL_GROUP_ID = "TOOL_GROUP_stack_" + container_id;
|
||||
const stackToolGroup = ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
|
||||
|
||||
// List all annotation tools you use
|
||||
[
|
||||
'ArrowAnnotate',
|
||||
'NoLabelArrowAnnotate',
|
||||
'Length',
|
||||
'RectangleROI',
|
||||
'EllipticalROI',
|
||||
'PlanarFreehandROI',
|
||||
'Sculptor',
|
||||
// Add more if needed
|
||||
].forEach(toolName => {
|
||||
mainToolGroup.setToolConfiguration(toolName, {
|
||||
color: '#00e676', // normal color
|
||||
selectedColor: '#ffeb3b', // yellow for selected
|
||||
[mainToolGroup, stackToolGroup].forEach(toolGroup => {
|
||||
if (!toolGroup) return;
|
||||
|
||||
// List all annotation tools you use
|
||||
[
|
||||
'ArrowAnnotate',
|
||||
'NoLabelArrowAnnotate',
|
||||
'Length',
|
||||
'RectangleROI',
|
||||
'EllipticalROI',
|
||||
'PlanarFreehandROI',
|
||||
'Sculptor',
|
||||
// Add more if needed
|
||||
].forEach(toolName => {
|
||||
toolGroup.setToolConfiguration(toolName, {
|
||||
color: '#00e676', // normal color
|
||||
selectedColor: '#ffeb3b', // yellow for selected
|
||||
});
|
||||
});
|
||||
});
|
||||
}, [MAIN_TOOL_GROUP_ID]);
|
||||
}, [MAIN_TOOL_GROUP_ID, container_id]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -2555,6 +2668,8 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
useEffect(() => {
|
||||
const mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
|
||||
const STACK_TOOL_GROUP_ID = "TOOL_GROUP_stack_" + container_id;
|
||||
const stackToolGroup = ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
|
||||
if (!mainToolGroup) return;
|
||||
const modifierActive = shiftPressed || (altEnablesReferenceCursors && altPressed);
|
||||
|
||||
@@ -2572,15 +2687,21 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
});
|
||||
});
|
||||
|
||||
if (!mainToolGroup.hasTool(ReferenceCursors.toolName)) {
|
||||
mainToolGroup.addTool(ReferenceCursors.toolName);
|
||||
}
|
||||
mainToolGroup.setToolEnabled(ReferenceCursors.toolName);
|
||||
mainToolGroup.setToolActive(ReferenceCursors.toolName);
|
||||
[mainToolGroup, stackToolGroup].forEach(toolGroup => {
|
||||
if (!toolGroup) return;
|
||||
if (!toolGroup.hasTool(ReferenceCursors.toolName)) {
|
||||
toolGroup.addTool(ReferenceCursors.toolName);
|
||||
}
|
||||
toolGroup.setToolEnabled(ReferenceCursors.toolName);
|
||||
toolGroup.setToolActive(ReferenceCursors.toolName);
|
||||
});
|
||||
} else {
|
||||
if (mainToolGroup.hasTool(ReferenceCursors.toolName)) {
|
||||
mainToolGroup.setToolDisabled(ReferenceCursors.toolName);
|
||||
}
|
||||
[mainToolGroup, stackToolGroup].forEach(toolGroup => {
|
||||
if (!toolGroup) return;
|
||||
if (toolGroup.hasTool(ReferenceCursors.toolName)) {
|
||||
toolGroup.setToolDisabled(ReferenceCursors.toolName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [
|
||||
altPressed,
|
||||
@@ -2591,14 +2712,17 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
viewportImageIds,
|
||||
viewportModes,
|
||||
setLoadedImageIdsAndCacheMeta,
|
||||
container_id,
|
||||
]);
|
||||
|
||||
// Helper to apply all mouse tool bindings (normal or ctrl)
|
||||
const applyMouseToolBindings = useCallback(() => {
|
||||
const STACK_TOOL_GROUP_ID = "TOOL_GROUP_stack_" + container_id;
|
||||
// Apply to both stack and volume tool groups
|
||||
const mainToolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
|
||||
const stackToolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
|
||||
|
||||
[mainToolGroup].forEach(toolGroup => {
|
||||
[mainToolGroup, stackToolGroup].forEach(toolGroup => {
|
||||
if (toolGroup) {
|
||||
// Set all tools to passive first, removing all bindings
|
||||
TOOL_OPTIONS.forEach(opt => {
|
||||
@@ -2699,7 +2823,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
}
|
||||
});
|
||||
}, [mouseToolBindings, ctrlMouseToolBindings, windowLevelMouseSensitivity]);
|
||||
}, [mouseToolBindings, ctrlMouseToolBindings, windowLevelMouseSensitivity, container_id]);
|
||||
|
||||
useEffect(() => {
|
||||
applyMouseToolBindings();
|
||||
@@ -3579,16 +3703,24 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
}, [applyStackDrop, getDropZoneFromClientPoint, isPhoneLayout, mobileInteractionActive, resolveDropZone, viewportGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
const setup = async () => {
|
||||
appLogger.debug("setup")
|
||||
setupEpochRef.current++;
|
||||
const currentEpoch = setupEpochRef.current;
|
||||
setupInProgressRef.current = true;
|
||||
setSetupInProgress(true);
|
||||
|
||||
// 1. Initialize Cornerstone3D and tools ONCE
|
||||
if (!running.current) {
|
||||
running.current = true;
|
||||
await csRenderInit();
|
||||
await csToolsInit(
|
||||
);
|
||||
dicomImageLoaderInit();
|
||||
const setup = async () => {
|
||||
appLogger.debug("setup", currentEpoch);
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
try {
|
||||
// 1. Initialize Cornerstone3D and tools ONCE
|
||||
if (!running.current) {
|
||||
running.current = true;
|
||||
await csRenderInit();
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
await csToolsInit(
|
||||
);
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
dicomImageLoaderInit();
|
||||
// Register client-side image loader for external images (JPEG/PNG/blob/data)
|
||||
try {
|
||||
imageLoader.registerImageLoader('external', (imageId: string) => {
|
||||
@@ -3653,19 +3785,40 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
// --- In your setup function, use the correct tool group per viewport ---
|
||||
let mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
|
||||
if (!mainToolGroup) {
|
||||
mainToolGroup = setupTools(MAIN_TOOL_GROUP_ID)
|
||||
mainToolGroup = setupTools(MAIN_TOOL_GROUP_ID);
|
||||
}
|
||||
|
||||
const STACK_TOOL_GROUP_ID = "TOOL_GROUP_stack_" + container_id;
|
||||
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(NoLabelArrowAnnotateTool.toolName, { getTextCallback: () => '' });
|
||||
stackToolGroup.addTool(ArrowAnnotateTool.toolName);
|
||||
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(SculptorTool.toolName, {});
|
||||
stackToolGroup.addTool(ReferenceLinesTool.toolName);
|
||||
stackToolGroup.addTool(ReferenceCursors.toolName);
|
||||
}
|
||||
|
||||
const visibleVolumeViewports = viewportModes
|
||||
.slice(0, viewportGrid.rows * viewportGrid.cols)
|
||||
.filter(mode => mode === 'volume').length;
|
||||
|
||||
// --- In your setup function, after tool group creation, enable/disable ReferenceLinesTool for each tool group ---
|
||||
updateActiveReferenceLineViewport(mainToolGroup)
|
||||
updateActiveReferenceLineViewport(mainToolGroup, stackToolGroup);
|
||||
|
||||
// 6. Enable and configure each viewport
|
||||
for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
|
||||
let imageIds = viewportImageIds[i];
|
||||
if (!imageIds || imageIds.length === 0) continue;
|
||||
@@ -3703,8 +3856,44 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
if (!viewport) continue;
|
||||
|
||||
// Patch setSlabThickness to support robust synchronization
|
||||
if (viewportModes[i] === 'volume' && typeof (viewport as any).setSlabThickness === 'function' && !(viewport as any)._setSlabThicknessPatched) {
|
||||
const originalSetSlabThickness = (viewport as any).setSlabThickness;
|
||||
(viewport as any).setSlabThickness = function (slabThickness: any, actorUIDs: any) {
|
||||
originalSetSlabThickness.call(this, slabThickness, actorUIDs);
|
||||
if (syncSlabThicknessRef.current && !setupInProgressRef.current) {
|
||||
if ((this as any)._isSyncingSlabThickness) return;
|
||||
(this as any)._isSyncingSlabThickness = true;
|
||||
try {
|
||||
const engine = renderingEngineRef.current;
|
||||
if (engine) {
|
||||
const otherViewports = engine.getViewports().filter(v => v.id !== this.id && (v as any).setSlabThickness);
|
||||
otherViewports.forEach(otherVp => {
|
||||
if ((otherVp as any).getSlabThickness() !== slabThickness) {
|
||||
(otherVp as any)._isSyncingSlabThickness = true;
|
||||
try {
|
||||
(otherVp as any).setSlabThickness(slabThickness);
|
||||
otherVp.render();
|
||||
} catch (err) { /* ignore */ }
|
||||
finally {
|
||||
(otherVp as any)._isSyncingSlabThickness = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
(this as any)._isSyncingSlabThickness = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
(viewport as any)._setSlabThicknessPatched = true;
|
||||
}
|
||||
|
||||
if (!element._resizeObserver) {
|
||||
const resizeObserver = new window.ResizeObserver(() => {
|
||||
if (setupInProgressRef.current) {
|
||||
return;
|
||||
}
|
||||
if (element.offsetWidth > 0 && element.offsetHeight > 0) {
|
||||
renderingEngine.resize();
|
||||
// Always get the latest viewport instance
|
||||
@@ -3751,12 +3940,16 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
// Do not attach DICOM-specific tools to external images
|
||||
mainToolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
if (stackToolGroup) {
|
||||
stackToolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
}
|
||||
} catch (err) {
|
||||
appLogger.warn('Failed to render external image in viewport', err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
viewport.setStack(imageIds);
|
||||
await viewport.setStack(imageIds);
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
} catch (err) {
|
||||
appLogger.warn('viewport.setStack failed', err);
|
||||
}
|
||||
@@ -3765,13 +3958,22 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
// CrosshairsTool is not applicable for stack viewports
|
||||
if (!crossHairsEnabled) {
|
||||
mainToolGroup.addViewport(viewportId, renderingEngineId);
|
||||
if (stackToolGroup) {
|
||||
stackToolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
}
|
||||
} else {
|
||||
mainToolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
if (stackToolGroup) {
|
||||
stackToolGroup.addViewport(viewportId, renderingEngineId);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore scroll, properties, etc.
|
||||
} else if (viewportModes[i] === 'volume') {
|
||||
mainToolGroup.addViewport(viewportId, renderingEngineId);
|
||||
if (stackToolGroup) {
|
||||
stackToolGroup.removeViewports(renderingEngineId, viewportId);
|
||||
}
|
||||
|
||||
//let sortedImageIds = imageIds;
|
||||
//if (imageIds && imageIds.length > 1) {
|
||||
@@ -3784,11 +3986,25 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
// });
|
||||
//}
|
||||
|
||||
// Force cache metadata for the volume to prevent undefined extent errors
|
||||
await setLoadedImageIdsAndCacheMeta(imageIds, { force: true });
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
|
||||
// Use a unique volumeId per viewport
|
||||
const volumeId = `myVolumeId_${i}`;
|
||||
try {
|
||||
if (viewport.getActorUIDs().includes(volumeId)) {
|
||||
viewport.removeActors([volumeId]);
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
try {
|
||||
cornerstone3d.cache.removeVolumeLoadObject(volumeId);
|
||||
} catch (e) { /* ignore */ }
|
||||
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: imageIds });
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
volume.load();
|
||||
viewport.setVolumes([{ volumeId }]);
|
||||
await viewport.setVolumes([{ volumeId }]);
|
||||
if (currentEpoch !== setupEpochRef.current) return;
|
||||
if (typeof viewport.getNumberOfSlices === "function") {
|
||||
const totalSlices = viewport.getNumberOfSlices();
|
||||
const pending = pendingSliceIndexByViewportRef.current[i] || 0;
|
||||
@@ -3913,12 +4129,29 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
// 7. Apply mouse tool bindings
|
||||
applyMouseToolBindings();
|
||||
};
|
||||
|
||||
// Use requestAnimationFrame to ensure DOM is painted before setup
|
||||
const raf = requestAnimationFrame(() => {
|
||||
setup();
|
||||
});
|
||||
if (currentEpoch === setupEpochRef.current) {
|
||||
setupInProgressRef.current = false;
|
||||
setSetupInProgress(false);
|
||||
if (renderingEngineRef.current) {
|
||||
renderingEngineRef.current.resize();
|
||||
renderingEngineRef.current.render();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
appLogger.error("setup failed", err);
|
||||
} finally {
|
||||
if (currentEpoch === setupEpochRef.current) {
|
||||
setupInProgressRef.current = false;
|
||||
setSetupInProgress(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Use requestAnimationFrame to ensure DOM is painted before setup
|
||||
const raf = requestAnimationFrame(() => {
|
||||
setup();
|
||||
});
|
||||
|
||||
// Cleanup: disconnect ResizeObservers
|
||||
return () => {
|
||||
@@ -3990,15 +4223,12 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
// Manage Slab Thickness Synchronizer
|
||||
useEffect(() => {
|
||||
// Clean up previous synchronizer
|
||||
if (slabThicknessSynchronizerRef.current) {
|
||||
try {
|
||||
slabThicknessSynchronizerRef.current.destroy();
|
||||
} catch (e) {
|
||||
appLogger.warn("Failed to destroy slab thickness synchronizer", e);
|
||||
}
|
||||
slabThicknessSynchronizerRef.current = null;
|
||||
}
|
||||
try {
|
||||
cornerstoneTools.SynchronizerManager.destroySynchronizer("slabThicknessSync");
|
||||
} catch (e) { /* ignore */ }
|
||||
slabThicknessSynchronizerRef.current = null;
|
||||
|
||||
if (setupInProgress) return;
|
||||
if (!crossHairsEnabled || !syncSlabThickness) return;
|
||||
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
@@ -4029,14 +4259,12 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (slabThicknessSynchronizerRef.current) {
|
||||
try {
|
||||
slabThicknessSynchronizerRef.current.destroy();
|
||||
} catch (e) { /* ignore */ }
|
||||
slabThicknessSynchronizerRef.current = null;
|
||||
}
|
||||
try {
|
||||
cornerstoneTools.SynchronizerManager.destroySynchronizer("slabThicknessSync");
|
||||
} catch (e) { /* ignore */ }
|
||||
slabThicknessSynchronizerRef.current = null;
|
||||
};
|
||||
}, [syncSlabThickness, crossHairsEnabled, viewportGrid, viewportModes]);
|
||||
}, [syncSlabThickness, crossHairsEnabled, viewportGrid, viewportModes, setupInProgress]);
|
||||
|
||||
// Handler to apply a preset for a specific viewport
|
||||
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
|
||||
@@ -4657,17 +4885,28 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
});
|
||||
}, [orderBySliceLocation, availableStacks]);
|
||||
|
||||
function updateActiveReferenceLineViewport(mainToolGroup: cornerstoneTools.Types.IToolGroup) {
|
||||
function updateActiveReferenceLineViewport(
|
||||
mainToolGroup: cornerstoneTools.Types.IToolGroup,
|
||||
stackToolGroup?: cornerstoneTools.Types.IToolGroup
|
||||
) {
|
||||
// Ensure the main tool group is defined (it won't be during setup)
|
||||
if (!mainToolGroup) return;
|
||||
const STACK_TOOL_GROUP_ID = "TOOL_GROUP_stack_" + container_id;
|
||||
const targetStackToolGroup = stackToolGroup || ToolGroupManager.getToolGroup(STACK_TOOL_GROUP_ID);
|
||||
const sourceViewportId = `CT_${activeViewport}`
|
||||
if (referenceLinesEnabled) {
|
||||
mainToolGroup.setToolEnabled(ReferenceLinesTool.toolName)
|
||||
mainToolGroup.setToolConfiguration(ReferenceLinesTool.toolName, {
|
||||
sourceViewportId,
|
||||
})
|
||||
[mainToolGroup, targetStackToolGroup].forEach(toolGroup => {
|
||||
if (!toolGroup) return;
|
||||
toolGroup.setToolEnabled(ReferenceLinesTool.toolName)
|
||||
toolGroup.setToolConfiguration(ReferenceLinesTool.toolName, {
|
||||
sourceViewportId,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
mainToolGroup.setToolDisabled(ReferenceLinesTool.toolName)
|
||||
[mainToolGroup, targetStackToolGroup].forEach(toolGroup => {
|
||||
if (!toolGroup) return;
|
||||
toolGroup.setToolDisabled(ReferenceLinesTool.toolName)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4754,7 +4993,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
setActiveViewport(0);
|
||||
}
|
||||
|
||||
function truncateStack(lower: number, upper: number) {
|
||||
async function truncateStack(lower: number, upper: number) {
|
||||
// Only allow when 1 viewport and 1 stack is loaded
|
||||
appLogger.debug("Truncating stack from", lower, "to", upper);
|
||||
appLogger.debug(stackOrderModified)
|
||||
@@ -4795,7 +5034,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
if (renderingEngine) {
|
||||
const viewport = renderingEngine.getViewport("CT_0");
|
||||
if (viewport && typeof viewport.setStack === "function") {
|
||||
viewport.setStack(truncatedImageIds);
|
||||
await viewport.setStack(truncatedImageIds);
|
||||
viewport.setImageIdIndex?.(0);
|
||||
viewport.render?.();
|
||||
}
|
||||
@@ -5127,10 +5366,21 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
|
||||
if (state.modes?.[i] === "volume") {
|
||||
try {
|
||||
// Force cache metadata for the volume to prevent undefined extent errors
|
||||
await setLoadedImageIdsAndCacheMeta(importedImageIds, { force: true });
|
||||
|
||||
const volumeId = `myVolumeId_${i}`;
|
||||
try {
|
||||
if (viewport.getActorUIDs().includes(volumeId)) {
|
||||
viewport.removeActors([volumeId]);
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
try {
|
||||
cornerstone3d.cache.removeVolumeLoadObject(volumeId);
|
||||
} catch (e) { /* ignore */ }
|
||||
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds });
|
||||
volume.load();
|
||||
viewport.setVolumes([{ volumeId }]);
|
||||
await viewport.setVolumes([{ volumeId }]);
|
||||
viewport.render();
|
||||
} catch (e) {
|
||||
appLogger.warn("Failed to create/load volume for viewport", i, e);
|
||||
@@ -5143,7 +5393,7 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
setTimeout(() => { try { csUtilities.jumpToSlice(viewport.element, { imageIndex: state.volumeSliceIndices[i] }); } catch (e) { } }, 100);
|
||||
}
|
||||
} else {
|
||||
try { if (viewport.setStack) { viewport.setStack(importedImageIds); viewport.render(); } } catch (e) { appLogger.warn('Failed to set stack for viewport', i, e); }
|
||||
try { if (viewport.setStack) { await viewport.setStack(importedImageIds); viewport.render(); } } catch (e) { appLogger.warn('Failed to set stack for viewport', i, e); }
|
||||
if (state.stackPos && typeof state.stackPos[i] === "number" && typeof viewport.setImageIdIndex === "function") {
|
||||
try { if (csUtilities && csUtilities.jumpToSlice) csUtilities.jumpToSlice(viewport.element, { imageIndex: state.stackPos[i] }); else viewport.setImageIdIndex(state.stackPos[i]); } catch (e) { /* ignore */ }
|
||||
}
|
||||
@@ -7925,6 +8175,30 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
||||
{isActiveViewportStack && (
|
||||
<div style={{ fontSize: 11, color: '#bbdefb', fontWeight: 700 }}>ACTIVE</div>
|
||||
)}
|
||||
{!invalid && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleLoadStackOrthogonal(idx);
|
||||
}}
|
||||
style={{
|
||||
background: '#2e7d32',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 4,
|
||||
padding: '4px 8px',
|
||||
fontSize: 11,
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
marginTop: '4px',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = '#1b5e20'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = '#2e7d32'; }}
|
||||
>
|
||||
Ortho (3D)
|
||||
</button>
|
||||
)}
|
||||
{hideChromeForMobileInteraction && !invalid && (
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user