correct export volume orientation

This commit is contained in:
Ross
2025-06-23 10:46:03 +01:00
parent ce13f9cad2
commit c27a52243f
2 changed files with 149 additions and 87 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
data-images="["http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm", "http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm", "http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm", "http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm", "http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm", "http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm", "http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm", "http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm", "http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm", "http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm", "http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm", "http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm", "http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm", "http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm", "http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm", "http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm", "http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm", "http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm", "http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm", "http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm", "http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm", "http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"]" data-images="["http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm", "http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm", "http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm", "http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm", "http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm", "http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm", "http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm", "http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm", "http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm", "http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm", "http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm", "http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm", "http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm", "http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm", "http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm", "http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm", "http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm", "http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm", "http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm", "http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm", "http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm", "http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"]"
data-auto-cache-stack="true" data-auto-cache-stack="true"
data-viewerstate='{"grid":{"rows":1,"cols":3},"modes":["stack","stack","volume"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0,0,0],"stackPos":[0,0,195],"props":[{"voiRange":{"lower":137.5,"upper":1967.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":137.5,"upper":1967.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":140,"upper":1999},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}}],"cam":[{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[150.10646385504754,-62.737490075094016,23.212950690129],"focalPoint":[-66.0533860650337,-61.85298394159119,19.934941465766144],"parallelProjection":true,"parallelScale":170.33890849893743,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[150.10646385504754,-62.737490075094016,23.212950690129],"focalPoint":[-66.0533860650337,-61.85298394159119,19.934941465766144],"parallelProjection":true,"parallelScale":193.61549696093098,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[0,-1,0],"position":[-6.060784295600456,-289.53396115406485,20.844714115543866],"focalPoint":[-6.060784295600456,-62.09846953239139,20.844714115543866],"parallelProjection":true,"parallelScale":150.85189284207627,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0}]}' data-viewerstate='{"grid":{"rows":1,"cols":3},"modes":["stack","volume","volume"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0,0,0],"stackPos":[0,13,195],"props":[{"voiRange":{"lower":141.5,"upper":2021.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":140,"upper":1999},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}},{"voiRange":{"lower":140,"upper":1999},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}}],"cam":[{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[276.090926593084,-63.25300826357402,25.12347315106698],"focalPoint":[59.93107667300279,-62.36850213007119,21.845463926704127],"parallelProjection":true,"parallelScale":170.33890849893743,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[1,0,0],"position":[236.29447599313886,-62.09846953239139,20.844714115543866],"focalPoint":[8.858990153295125,-62.09846953239139,20.844714115543866],"parallelProjection":true,"parallelScale":157.88941096127814,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[0,-1,0],"position":[-6.060784295600456,-289.53396115406485,20.844714115543866],"focalPoint":[-6.060784295600456,-62.09846953239139,20.844714115543866],"parallelProjection":true,"parallelScale":150.85189284207627,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0}],"orientations":[null,"coronal","sagittal"],"volumeSliceIndices":[null,13,195]}'
></div> ></div>
<!-- <div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;" <!-- <div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;"
+148 -86
View File
@@ -1334,6 +1334,19 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
} }
} }
// Attach to window with a unique name // Attach to window with a unique name
window[`getViewport_${apiKey}`] = (idx: number) => {
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return undefined;
return renderingEngine.getViewport(`CT_${idx}`);
};
window[`getAllViewports_${apiKey}`] = () => {
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return [];
// Return all currently active viewports as an array
return Array.from({ length: MAX_VIEWPORTS }, (_, i) => renderingEngine.getViewport(`CT_${i}`)).filter(Boolean);
};
window[`jumpToSliceByImageId_${apiKey}`] = jumpToSliceByImageId; window[`jumpToSliceByImageId_${apiKey}`] = jumpToSliceByImageId;
window[`resetViewer_${apiKey}`] = resetViewer; window[`resetViewer_${apiKey}`] = resetViewer;
window[`truncateStack_${apiKey}`] = truncateStack; window[`truncateStack_${apiKey}`] = truncateStack;
@@ -1376,15 +1389,21 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
i: s.imageIds, // imageIds i: s.imageIds, // imageIds
u: s.studyInstanceUID // studyInstanceUID u: s.studyInstanceUID // studyInstanceUID
})); }));
console.log("Exporting viewer state with", numActive, "active viewports");
console.log("modes", modes);
// Only keep minimal properties // Only keep minimal properties
const renderingEngine = renderingEngineRef.current; const renderingEngine = renderingEngineRef.current;
const props: any[] = []; const props: any[] = [];
const stackPos: number[] = []; const stackPos: number[] = [];
const cam: any[] = []; const cam: any[] = [];
const orientations: (string | null)[] = [];
const volumeSliceIndices: (number | null)[] = [];
for (let i = 0; i < numActive; i++) { for (let i = 0; i < numActive; i++) {
let p = null, s = null, c = null; let p = null, s = null, c = null, o = null, vIdx = null;
console.log("Exporting state for viewport", i);
if (renderingEngine) { if (renderingEngine) {
console.log("Rendering engine found, exporting state for viewport", i);
const viewportId = `CT_${i}`; const viewportId = `CT_${i}`;
const viewport = renderingEngine.getViewport(viewportId); const viewport = renderingEngine.getViewport(viewportId);
if (viewport) { if (viewport) {
@@ -1398,11 +1417,23 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
if (typeof viewport.getCamera === "function") { if (typeof viewport.getCamera === "function") {
c = viewport.getCamera(); c = viewport.getCamera();
} }
console.log("viewport", viewportId, "properties", p);
// --- Volume orientation and slice index ---
if (modes[i] === "volume") {
console.log("Getting orientation for viewport", i);
console.log("viewport.viewportProperties", viewport.viewportProperties);
o = viewport.viewportProperties?.orientation || null;
if (typeof viewport.getSliceIndex === "function") {
vIdx = viewport.getSliceIndex();
}
}
} }
} }
props.push(p); props.push(p);
stackPos.push(s); stackPos.push(s);
cam.push(c); cam.push(c);
orientations.push(o);
volumeSliceIndices.push(vIdx);
} }
// Use short keys for compactness // Use short keys for compactness
@@ -1414,6 +1445,8 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
stackPos, stackPos,
props, props,
cam, cam,
orientations, // <-- add this
volumeSliceIndices, // <-- add this
}; };
return JSON.stringify(state); return JSON.stringify(state);
}; };
@@ -1440,6 +1473,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
const maxAttempts = 30; const maxAttempts = 30;
let attempts = 0; let attempts = 0;
// eslint-disable-next-line no-inner-declarations
async function tryRestore() { async function tryRestore() {
const renderingEngine = renderingEngineRef.current; const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) { if (!renderingEngine) {
@@ -1448,29 +1482,33 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
} }
const numActive = state.grid?.rows * state.grid?.cols || 1; const numActive = state.grid?.rows * state.grid?.cols || 1;
let allReady = true; let allReady = true;
for (let i = 0; i < numActive; i++) { for (let i = 0; i < numActive; i++) {
const viewport = renderingEngine.getViewport(`CT_${i}`); console.log("Checking viewport", i);
if (!viewport) { allReady = false; break; } const viewport = renderingEngine.getViewport(`CT_${i}`);
const stackIdx = state.stackIdx?.[i]; if (!viewport) { allReady = false; break; }
const stackObj = state.stacks?.[stackIdx]; const stackIdx = state.stackIdx?.[i];
const importedImageIds = stackObj?.i || []; const stackObj = state.stacks?.[stackIdx];
if (!importedImageIds.length) continue; const importedImageIds = stackObj?.i || [];
const viewportImageIds = viewport.getImageIds?.() || []; console.log("Checking viewport", i, "with importedImageIds", importedImageIds);
if ( console.log("viewport", viewport);
viewportImageIds.length !== importedImageIds.length || console.log("viewport.getImageIds", viewport.getImageIds?.());
!viewportImageIds.every((id, idx) => id === importedImageIds[idx]) if (!importedImageIds.length) continue;
) { const viewportImageIds = viewport.getImageIds?.() || [];
console.warn( if (
`Viewport ${i}: importedImageIds do not match viewportImageIds`, viewportImageIds.length !== importedImageIds.length ||
{ importedImageIds, viewportImageIds } !viewportImageIds.every((id, idx) => id === importedImageIds[idx])
); ) {
allReady = false; console.warn(
break; `Viewport ${i}: importedImageIds do not match viewportImageIds`,
} { importedImageIds, viewportImageIds }
} );
allReady = false;
break;
}
}
if (!allReady && attempts < maxAttempts) { if (!allReady && attempts < maxAttempts) {
attempts++; attempts++;
setTimeout(() => { tryRestore(); }, 100); setTimeout(() => { tryRestore(); }, 500);
return; return;
} }
@@ -1490,11 +1528,33 @@ for (let i = 0; i < numActive; i++) {
if (state.modes?.[i] === "volume") { if (state.modes?.[i] === "volume") {
// --- Restore volume --- // --- Restore volume ---
console.log("Restoring volume for viewport", i, importedImageIds); console.log("Restoring volume for viewport", i, importedImageIds);
const volumeId = `myVolumeId_${i}`; const volumeId = `myVolumeId_${i}`;
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds }); const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds });
volume.load(); volume.load();
viewport.setVolumes([{ volumeId }]); viewport.setVolumes([{ volumeId }]);
viewport.render(); // --- Restore orientation if present ---
if (state.orientations && state.orientations[i] && typeof viewport.setOrientation === "function") {
try {
viewport.setOrientation(state.orientations[i]);
viewport.render();
} catch (e) {
console.warn("Failed to restore orientation for viewport", i, e);
}
}
// --- Restore slice index if present ---
if (
state.volumeSliceIndices &&
typeof state.volumeSliceIndices[i] === "number" &&
typeof viewport.setSliceIndex === "function"
) {
try {
viewport.setSliceIndex(state.volumeSliceIndices[i]);
viewport.render();
} catch (e) {
console.warn("Failed to restore slice index for viewport", i, e);
}
}
viewport.render();
} else { } else {
// --- Restore stack --- // --- Restore stack ---
if (viewport.setStack) { if (viewport.setStack) {
@@ -1548,7 +1608,7 @@ for (let i = 0; i < numActive; i++) {
} }
// Delay start until after next paint // Delay start until after next paint
setTimeout(() => { tryRestore(); }, 0); setTimeout(() => { tryRestore(); }, 0);
} catch (e) { } catch (e) {
restoringStateRef.current = false; restoringStateRef.current = false;
@@ -1781,6 +1841,8 @@ for (let i = 0; i < numActive; i++) {
window[`truncateStack_${apiKey}`] = undefined; window[`truncateStack_${apiKey}`] = undefined;
window[`getCurrentStackPosition_${apiKey}`] = undefined; window[`getCurrentStackPosition_${apiKey}`] = undefined;
window[`loadAdditionalStack_${apiKey}`] = undefined; window[`loadAdditionalStack_${apiKey}`] = undefined;
window[`getViewport_${apiKey}`] = undefined;
window[`getAllViewports_${apiKey}`] = undefined;
}; };
}, [ }, [
viewportGrid, viewportGrid,
@@ -1799,69 +1861,69 @@ for (let i = 0; i < numActive; i++) {
]); ]);
useEffect(() => { useEffect(() => {
function handleArrowKeyNavigation(e: KeyboardEvent) { function handleArrowKeyNavigation(e: KeyboardEvent) {
if (activeViewport === null) return; if (activeViewport === null) return;
const renderingEngine = renderingEngineRef.current; const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return; if (!renderingEngine) return;
const viewport = renderingEngine.getViewport(`CT_${activeViewport}`); const viewport = renderingEngine.getViewport(`CT_${activeViewport}`);
if (!viewport) return; if (!viewport) return;
// --- Stack mode navigation --- // --- Stack mode navigation ---
if (viewportModes[activeViewport] === "stack") { if (viewportModes[activeViewport] === "stack") {
if ( if (
typeof viewport.getCurrentImageIdIndex !== "function" || typeof viewport.getCurrentImageIdIndex !== "function" ||
typeof viewport.setImageIdIndex !== "function" typeof viewport.setImageIdIndex !== "function"
) return; ) return;
const imageIds = viewportImageIds[activeViewport]; const imageIds = viewportImageIds[activeViewport];
if (!imageIds || imageIds.length === 0) return; if (!imageIds || imageIds.length === 0) return;
let idx = viewport.getCurrentImageIdIndex(); let idx = viewport.getCurrentImageIdIndex();
if (e.key === "ArrowUp" || e.key === "ArrowRight") { if (e.key === "ArrowUp" || e.key === "ArrowRight") {
idx = idx < imageIds.length - 1 ? idx + 1 : 0; idx = idx < imageIds.length - 1 ? idx + 1 : 0;
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx }); csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
updateOverlay(activeViewport, viewport); updateOverlay(activeViewport, viewport);
e.preventDefault(); e.preventDefault();
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") { } else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
idx = idx > 0 ? idx - 1 : imageIds.length - 1; idx = idx > 0 ? idx - 1 : imageIds.length - 1;
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx }); csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
updateOverlay(activeViewport, viewport); updateOverlay(activeViewport, viewport);
e.preventDefault(); e.preventDefault();
}
}
// --- Volume mode navigation ---
if (viewportModes[activeViewport] === "volume") {
// Try to scroll through slices in the current orientation
if (
typeof viewport.getSliceIndex === "function" &&
typeof viewport.setSliceIndex === "function" &&
typeof viewport.getNumberOfSlices === "function"
) {
const numSlices = viewport.getNumberOfSlices();
let idx = viewport.getSliceIndex();
if (numSlices > 1) {
if (e.key === "ArrowUp" || e.key === "ArrowRight") {
idx = idx < numSlices - 1 ? idx + 1 : 0;
viewport.setSliceIndex(idx);
viewport.render();
updateOverlay(activeViewport, viewport);
e.preventDefault();
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
idx = idx > 0 ? idx - 1 : numSlices - 1;
viewport.setSliceIndex(idx);
viewport.render();
updateOverlay(activeViewport, viewport);
e.preventDefault();
}
}
}
} }
} }
// --- Volume mode navigation --- window.addEventListener("keydown", handleArrowKeyNavigation);
if (viewportModes[activeViewport] === "volume") { return () => window.removeEventListener("keydown", handleArrowKeyNavigation);
// Try to scroll through slices in the current orientation }, [activeViewport, viewportModes, viewportImageIds, updateOverlay]);
if (
typeof viewport.getSliceIndex === "function" &&
typeof viewport.setSliceIndex === "function" &&
typeof viewport.getNumberOfSlices === "function"
) {
const numSlices = viewport.getNumberOfSlices();
let idx = viewport.getSliceIndex();
if (numSlices > 1) {
if (e.key === "ArrowUp" || e.key === "ArrowRight") {
idx = idx < numSlices - 1 ? idx + 1 : 0;
viewport.setSliceIndex(idx);
viewport.render();
updateOverlay(activeViewport, viewport);
e.preventDefault();
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
idx = idx > 0 ? idx - 1 : numSlices - 1;
viewport.setSliceIndex(idx);
viewport.render();
updateOverlay(activeViewport, viewport);
e.preventDefault();
}
}
}
}
}
window.addEventListener("keydown", handleArrowKeyNavigation);
return () => window.removeEventListener("keydown", handleArrowKeyNavigation);
}, [activeViewport, viewportModes, viewportImageIds, updateOverlay]);