From 52f4be1a96c5d2512c65af30c0580d2229db1cdd Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 16 Mar 2026 14:08:40 +0000 Subject: [PATCH] Refactor viewport state restoration and enhance error handling for volume and stack loading --- dicom-viewer/public/penra-test.html | 96 +++++++++++++++++++++ dicom-viewer/src/App.tsx | 127 +++++++++++++--------------- 2 files changed, 154 insertions(+), 69 deletions(-) create mode 100644 dicom-viewer/public/penra-test.html diff --git a/dicom-viewer/public/penra-test.html b/dicom-viewer/public/penra-test.html new file mode 100644 index 0000000..ff9104e --- /dev/null +++ b/dicom-viewer/public/penra-test.html @@ -0,0 +1,96 @@ + + + + + + Penra Courses — Viewer Test + + + +
+ + + + + + + + + diff --git a/dicom-viewer/src/App.tsx b/dicom-viewer/src/App.tsx index 53ed25f..5410cca 100644 --- a/dicom-viewer/src/App.tsx +++ b/dicom-viewer/src/App.tsx @@ -2412,9 +2412,9 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer return; } - // Now safe to restore state for ALL viewports + // First pass: set stacks/volumes and stack positions so viewports have image resources for (let i = 0; i < numActive; i++) { - console.log("Restoring state for viewport", i); + console.log("Setting stack/volume for viewport", i); const viewport = renderingEngine.getViewport(`CT_${i}`); if (!viewport) continue; const stackIdx = state.stackIdx?.[i]; @@ -2425,86 +2425,75 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer console.log("mode", state.modes?.[i]); if (state.modes?.[i] === "volume") { - // --- Restore volume --- - console.log("Restoring volume for viewport", i, importedImageIds); - const volumeId = `myVolumeId_${i}`; - const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds }); - volume.load(); - viewport.setVolumes([{ volumeId }]); - // --- 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); - } - } - if ( - state.volumeSliceIndices - ) { - console.log("Restoring volume slice index for viewport", i, state.volumeSliceIndices[i]); - setTimeout(() => { - csUtilities.jumpToSlice(viewport.element, { imageIndex: state.volumeSliceIndices[i] }); - }, 100); - } - - viewport.render(); - } else { - // --- Restore stack --- - if (viewport.setStack) { - viewport.setStack(importedImageIds); + try { + const volumeId = `myVolumeId_${i}`; + const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds }); + volume.load(); + viewport.setVolumes([{ volumeId }]); viewport.render(); + } catch (e) { + console.warn("Failed to create/load volume for viewport", i, e); } - // Restore stack position - if ( - state.stackPos && - typeof state.stackPos[i] === "number" && - typeof viewport.setImageIdIndex === "function" - ) { - if (csUtilities && csUtilities.jumpToSlice) { - csUtilities.jumpToSlice(viewport.element, { imageIndex: state.stackPos[i] }); - } else { - viewport.setImageIdIndex(state.stackPos[i]); - } + // Best-effort orientation restore + if (state.orientations && state.orientations[i] && typeof viewport.setOrientation === "function") { + try { viewport.setOrientation(state.orientations[i]); viewport.render(); } catch (e) { /* ignore */ } + } + if (state.volumeSliceIndices) { + 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) { console.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 */ } } } + } - // Restore properties (defensive colormap check) - if ( - state.props && - state.props[i] && - typeof viewport.setProperties === "function" - ) { + // Wait briefly to allow viewports to initialize GPU/actor resources + const readyDelayMs = 1500; + console.log(`Waiting ${readyDelayMs}ms before applying properties/camera to viewports`); + await new Promise((res) => setTimeout(res, readyDelayMs)); + + // Second pass: apply renderer-sensitive properties (colormap, VOI) and camera + for (let i = 0; i < numActive; i++) { + console.log("Applying props/camera for viewport", i); + const viewport = renderingEngine.getViewport(`CT_${i}`); + if (!viewport) continue; + const stackIdx = state.stackIdx?.[i]; + const stackObj = state.stacks?.[stackIdx]; + const importedImageIds = stackObj?.i || []; + if (!importedImageIds.length) continue; + + // Restore properties with sanitization + retry + if (state.props && state.props[i] && typeof viewport.setProperties === "function") { console.log("Restoring properties for viewport", i, state.props[i]); const propsToRestore = { ...state.props[i] }; - if ( - propsToRestore.colormap !== undefined && - propsToRestore.colormap !== null && - typeof propsToRestore.colormap !== "string" && - (typeof propsToRestore.colormap !== "object" || !("name" in propsToRestore.colormap)) - ) { - delete propsToRestore.colormap; + if (propsToRestore.colormap !== undefined && propsToRestore.colormap !== null) { + const cm = propsToRestore.colormap; + if (typeof cm === 'string') { + // keep + } else if (typeof cm === 'object') { + const safe: any = {}; + if (typeof cm.name === 'string') safe.name = cm.name; + if (cm.opacity !== undefined && typeof cm.opacity === 'number') safe.opacity = cm.opacity; + if (Object.keys(safe).length === 0) delete propsToRestore.colormap; else propsToRestore.colormap = safe; + } else { + delete propsToRestore.colormap; + } } - setTimeout(() => { + try { viewport.setProperties(propsToRestore); viewport.render(); - }, 100); - } else { - console.warn("No properties to restore for viewport", i); + } catch (err) { + console.warn('viewport.setProperties failed, retrying without colormap', err); + try { const noColormap = { ...propsToRestore }; delete noColormap.colormap; viewport.setProperties(noColormap); viewport.render(); } catch (err2) { console.error('Retrying viewport.setProperties without colormap also failed', err2); } + } } - // Restore camera - if ( - state.cam && - state.cam[i] && - typeof viewport.setCamera === "function" - ) { - console.log("Restoring camera for viewport", i, state.cam[i]); - viewport.setCamera(state.cam[i]); - viewport.render(); + // Restore camera (best-effort) + if (state.cam && state.cam[i] && typeof viewport.setCamera === 'function') { + try { viewport.setCamera(state.cam[i]); viewport.render(); } catch (e) { /* ignore */ } } - // --- Restore slice index if present --- } restoringStateRef.current = false; }