Enhance viewport preset management by adding active preset tracking and applying presets on image changes
This commit is contained in:
@@ -1104,6 +1104,8 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
);
|
||||
const viewportStackSignaturesRef = useRef<string[]>(Array(MAX_VIEWPORTS).fill(""));
|
||||
const defaultVoiRangeByViewportRef = useRef<Array<{ lower: number; upper: number } | null>>(Array(MAX_VIEWPORTS).fill(null));
|
||||
const activePresetByViewportRef = useRef<Array<PresetOption | null>>(Array(MAX_VIEWPORTS).fill(null));
|
||||
const lastRenderedImageIdByViewportRef = useRef<Array<string | null>>(Array(MAX_VIEWPORTS).fill(null));
|
||||
|
||||
const [viewportModes, setViewportModes] = useState<Array<'stack' | 'volume'>>(
|
||||
() => Array(MAX_VIEWPORTS).fill('stack')
|
||||
@@ -1899,6 +1901,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
if (viewportStackSignaturesRef.current[i] !== signature) {
|
||||
viewportStackSignaturesRef.current[i] = signature;
|
||||
defaultVoiRangeByViewportRef.current[i] = null;
|
||||
lastRenderedImageIdByViewportRef.current[i] = null;
|
||||
next[i] = [];
|
||||
changed = true;
|
||||
}
|
||||
@@ -2423,8 +2426,14 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
const imageId = ids[currentIndex] || ids[0];
|
||||
const defaultVoi = defaultVoiRangeByViewportRef.current[viewportIdx];
|
||||
const voiMeta = imageId ? (metaData.get("voiLutModule", imageId) || {}) as any : {};
|
||||
const pixelMeta = imageId ? (metaData.get("imagePixelModule", imageId) || {}) as any : {};
|
||||
const lutMeta = imageId ? (metaData.get("modalityLutModule", imageId) || {}) as any : {};
|
||||
const centerMeta = Array.isArray(voiMeta.windowCenter) ? voiMeta.windowCenter[0] : voiMeta.windowCenter;
|
||||
const widthMeta = Array.isArray(voiMeta.windowWidth) ? voiMeta.windowWidth[0] : voiMeta.windowWidth;
|
||||
const minRaw = Number(pixelMeta.smallestPixelValue ?? pixelMeta.smallestImagePixelValue);
|
||||
const maxRaw = Number(pixelMeta.largestPixelValue ?? pixelMeta.largestImagePixelValue);
|
||||
const slope = Number(lutMeta.rescaleSlope ?? 1);
|
||||
const intercept = Number(lutMeta.rescaleIntercept ?? 0);
|
||||
|
||||
if (defaultVoi) {
|
||||
viewport.setProperties({ voiRange: defaultVoi });
|
||||
@@ -2437,6 +2446,13 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
upper: center + width / 2,
|
||||
},
|
||||
});
|
||||
} else if (Number.isFinite(minRaw) && Number.isFinite(maxRaw) && maxRaw > minRaw) {
|
||||
viewport.setProperties({
|
||||
voiRange: {
|
||||
lower: minRaw * slope + intercept,
|
||||
upper: maxRaw * slope + intercept,
|
||||
},
|
||||
});
|
||||
} else if (typeof viewport.resetProperties === "function") {
|
||||
viewport.resetProperties();
|
||||
}
|
||||
@@ -2492,7 +2508,21 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
slope = Number((image as any)?.slope ?? slope);
|
||||
intercept = Number((image as any)?.intercept ?? intercept);
|
||||
} catch (err) {
|
||||
// Keep metadata-derived fallbacks below.
|
||||
const fallbackIds = ids.filter((id): id is string => !!id).slice(0, 6);
|
||||
for (const fallbackId of fallbackIds) {
|
||||
try {
|
||||
const fallbackImage = await imageLoader.loadAndCacheImage(fallbackId);
|
||||
minRaw = Number((fallbackImage as any)?.minPixelValue);
|
||||
maxRaw = Number((fallbackImage as any)?.maxPixelValue);
|
||||
slope = Number((fallbackImage as any)?.slope ?? slope);
|
||||
intercept = Number((fallbackImage as any)?.intercept ?? intercept);
|
||||
if (Number.isFinite(minRaw) && Number.isFinite(maxRaw) && maxRaw > minRaw) {
|
||||
break;
|
||||
}
|
||||
} catch (innerErr) {
|
||||
// Keep trying additional images in the stack.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const centerMeta = Array.isArray(voiMeta.windowCenter) ? voiMeta.windowCenter[0] : voiMeta.windowCenter;
|
||||
@@ -2528,9 +2558,13 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
});
|
||||
}, [applyDefaultWindowLevel, updateOverlay, viewportImageIds]);
|
||||
|
||||
const applyPresetOption = useCallback((viewportIdx: number, preset: PresetOption) => {
|
||||
const applyPresetOption = useCallback((viewportIdx: number, preset: PresetOption, persistSelection = true, resetCameraOnReset = true) => {
|
||||
if (persistSelection) {
|
||||
activePresetByViewportRef.current[viewportIdx] = preset;
|
||||
}
|
||||
|
||||
if (preset.action === "reset") {
|
||||
applyDefaultWindowLevel(viewportIdx, true);
|
||||
applyDefaultWindowLevel(viewportIdx, resetCameraOnReset);
|
||||
return;
|
||||
}
|
||||
if (preset.action === "autolevel") {
|
||||
@@ -2541,6 +2575,42 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
handleApplyPreset(viewportIdx, wlPreset.center, wlPreset.width);
|
||||
}, [applyDefaultWindowLevel, handleApplyPreset, handleAutoLevel]);
|
||||
|
||||
useEffect(() => {
|
||||
const applyPresetOnImageChange = () => {
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (!renderingEngine) return;
|
||||
|
||||
const visibleCount = viewportGrid.rows * viewportGrid.cols;
|
||||
for (let i = 0; i < visibleCount; i++) {
|
||||
if (viewportModes[i] !== "stack") continue;
|
||||
const preset = activePresetByViewportRef.current[i];
|
||||
if (!preset) continue;
|
||||
|
||||
const viewport = renderingEngine.getViewport(`CT_${i}`);
|
||||
if (!viewport || typeof viewport.getImageIds !== "function") continue;
|
||||
|
||||
const ids = viewport.getImageIds() || [];
|
||||
if (!ids.length) continue;
|
||||
|
||||
const idx = typeof viewport.getCurrentImageIdIndex === "function"
|
||||
? viewport.getCurrentImageIdIndex()
|
||||
: 0;
|
||||
const currentImageId = ids[idx] || ids[0];
|
||||
if (!currentImageId) continue;
|
||||
|
||||
if (lastRenderedImageIdByViewportRef.current[i] === currentImageId) {
|
||||
continue;
|
||||
}
|
||||
lastRenderedImageIdByViewportRef.current[i] = currentImageId;
|
||||
|
||||
applyPresetOption(i, preset, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
const timer = window.setTimeout(applyPresetOnImageChange, 0);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [applyPresetOption, imageInfos, viewportGrid, viewportModes]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePresetHotkey = (e: KeyboardEvent) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
|
||||
Reference in New Issue
Block a user