fix(segmentation): Changes to fix problems with non hydrated/loaded segmentations to be viewable when switching hanging protocols (e.g. MPR) (#5139)

This commit is contained in:
Joe Boccanfuso authored and GitHub committed 2025-06-27 07:11:46 -04:00
1 parent 9a81f8cef5
commit c2624f116d
24 files changed
+199 -47

No files matched your search

@@ -31,7 +31,7 @@ function _getDisplaySetsFromSeries(
const displaySet = {
Modality: 'RTSTRUCT',
loading: false,
isReconstructable: false, // by default for now since it is a volumetric SEG currently
isReconstructable: false,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
@@ -87,6 +87,7 @@ function _getDisplaySetsFromSeries(
const addedDisplaySet = displaySetsAdded[0];
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
unsubscribe();
}
}
@@ -94,6 +95,7 @@ function _getDisplaySetsFromSeries(
} else {
const referencedDisplaySet = referencedDisplaySets[0];
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
}
displaySet.load = ({ headers, createSegmentation = true }) =>
@@ -34,7 +34,7 @@ function _getDisplaySetsFromSeries(
const displaySet = {
Modality: 'SEG',
loading: false,
isReconstructable: true, // by default for now since it is a volumetric SEG currently
isReconstructable: false,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
@@ -91,12 +91,14 @@ function _getDisplaySetsFromSeries(
const addedDisplaySet = displaySetsAdded[0];
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
unsubscribe();
}
}
);
} else {
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
}
displaySet.load = async ({ headers }) =>
@@ -4,11 +4,16 @@ import React from 'react';
const SelectItemWithModality = ({
displaySet,
showModality = true,
dataCY = `${displaySet.label}-${displaySet.Modality}`,
}: {
displaySet: AppTypes.DisplaySet;
showModality?: boolean;
dataCY?: string;
}): JSX.Element => (
<div className="flex w-[90%] items-center justify-between">
<div
className="flex w-[90%] items-center justify-between"
data-cy={dataCY}
>
<span className="text-foreground truncate text-base">{displaySet.label}</span>
{showModality && displaySet.Modality && (
<span className="text-muted-foreground flex-shrink-0 whitespace-nowrap text-xs">
@@ -206,6 +206,7 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
onClick={() => {
setPendingSegmentations([...pendingSegmentations, `seg-${Date.now()}`]);
}}
dataCY={`AddSegmentationDataOverlay-${viewportId}`}
>
<Icons.Plus className="h-4 w-4" />
Segmentation
@@ -289,7 +290,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
value={item.displaySetInstanceUID}
className="pr-2"
>
<SelectItemWithModality displaySet={item} />
<SelectItemWithModality
displaySet={item}
dataCY={`${item.label}`}
/>
</SelectItem>
))}
</SelectContent>
@@ -18,6 +18,7 @@ type DataOverlayMenuProps = {
onOpen?: () => void;
onClose?: () => void;
disabled?: boolean;
id?: string;
};
export function ViewportDataOverlayMenuWrapper(props: DataOverlayMenuProps): ReactNode {
@@ -40,6 +41,7 @@ export function ViewportDataOverlayMenuWrapper(props: DataOverlayMenuProps): Rea
const Icon = <Icons.ViewportViews className={iconClassName} />;
const idProp = rest.id ? { id: `${rest.id}-${viewportId}` } : {};
return (
<Popover
open={isOpen}
@@ -56,6 +58,7 @@ export function ViewportDataOverlayMenuWrapper(props: DataOverlayMenuProps): Rea
icon="ViewportViews"
{...rest}
{...containerProps}
{...idProp}
>
{Icon}
</IconContainer>
@@ -835,16 +835,19 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const displaySetModality = displaySet?.Modality;
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
const filteredVolumeInputArray = volumeInputArray.filter(volumeInput => {
const displaySet = displaySetService.getDisplaySetByUID(volumeInput.displaySetInstanceUID);
return !displaySet?.isOverlayDisplaySet;
});
const filteredVolumeInputArray = volumeInputArray
.map((volumeInput, index) => {
return { volumeInput, displaySetOptions: displaySetOptions[index] };
})
.filter(({ volumeInput }) => {
const displaySet = displaySetService.getDisplaySetByUID(volumeInput.displaySetInstanceUID);
return !displaySet?.isOverlayDisplaySet;
});
// Todo: use presentations states
const volumesProperties = filteredVolumeInputArray.map((volumeInput, index) => {
const volumesProperties = filteredVolumeInputArray.map(({ volumeInput, displaySetOptions }) => {
const { volumeId } = volumeInput;
const displaySetOption = displaySetOptions[index];
const { voi, voiInverted, colormap, displayPreset } = displaySetOption;
const { voi, voiInverted, colormap, displayPreset } = displaySetOptions;
const properties = {} as ViewportProperties;
if (voi && (voi.windowWidth || voi.windowCenter)) {
@@ -887,14 +890,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
if (backgroundDisplaySet.length !== 1) {
throw new Error('Background display set not found');
}
await viewport.setVolumes([
{ volumeId: `${VOLUME_LOADER_SCHEME}:${backgroundDisplaySet[0].displaySetInstanceUID}` },
]);
} else {
await viewport.setVolumes(filteredVolumeInputArray);
}
await viewport.setVolumes(volumeInputArray);
if (addOverlayFn) {
addOverlayFn();
}
@@ -48,7 +48,7 @@ export default function interleaveNthLoader({
// iterate over all volumes, and get their imageIds, and interleave
// the imageIds and save them in AllRequests for later use
const originalRequests = volumes
.map(volume => volume.getImageLoadRequests())
.map(volume => volume.getImageLoadRequests?.() ?? [])
.filter(requests => requests?.[0]?.imageId);
const orderedRequests = originalRequests.map(request => getNthFrames(request));
@@ -24,7 +24,7 @@ type DisplaySetSelectorState = {
/**
* Stores a mapping from `<activeStudyUID>:<displaySetSelectorId>:<matchOffset>` to `displaySetInstanceUID`.
*/
displaySetSelectorMap: Record<string, string>;
displaySetSelectorMap: Record<string, Array<string>>;
/**
* Sets the display set selector for a given key.
@@ -32,7 +32,7 @@ type DisplaySetSelectorState = {
* @param key - The key.
* @param value - The `displaySetInstanceUID` to associate with the key.
*/
setDisplaySetSelector: (key: string, value: string) => void;
setDisplaySetSelector: (key: string, value: Array<string>) => void;
/**
* Clears the entire display set selector map.
@@ -53,7 +53,7 @@ const createDisplaySetSelectorStore = (set): DisplaySetSelectorState => ({
/**
* Sets the display set selector for a given key.
*/
setDisplaySetSelector: (key: string, value: string) =>
setDisplaySetSelector: (key: string, value: Array<string>) =>
set(
state => ({
displaySetSelectorMap: {
@@ -65,15 +65,23 @@ export function configureViewportForLayerAddition(params: {
viewport.viewportOptions.orientation = cornerstoneViewportService.getOrientation(viewportId);
}
// Do not force volume for SEG and RTSTRUCT if there is only one display set
if (requestedLayerDisplaySet.isOverlayDisplaySet && currentDisplaySetUIDs.length === 1) {
viewport.viewportOptions.viewportType = 'stack';
} else {
viewport.viewportOptions.viewportType = 'volume';
// If a viewport type was already set do not reset it.
if (!viewport.viewportOptions.viewportType) {
// Do not force volume for SEG and RTSTRUCT if there is only one display set
if (requestedLayerDisplaySet.isOverlayDisplaySet && currentDisplaySetUIDs.length === 1) {
viewport.viewportOptions.viewportType = 'stack';
} else {
viewport.viewportOptions.viewportType = 'volume';
}
}
// create same amount of display set options as the number of display set UIDs
const displaySetOptions = allDisplaySetInstanceUIDs.map((uid, index) => {
// There is already a display set option for this display set, so return it.
if (viewport.displaySetOptions?.[index]) {
return viewport.displaySetOptions[index];
}
if (index === 0) {
// no colormap for background
return {};
@@ -6,7 +6,7 @@ import { useHangingProtocolStageIndexStore } from '../stores/useHangingProtocolS
export type ReturnType = {
hangingProtocolStageIndexMap: Record<string, Types.HangingProtocol.HPInfo>;
viewportGridStore: Record<string, unknown>;
displaySetSelectorMap: Record<string, string>;
displaySetSelectorMap: Record<string, Array<string>>;
};
/**
@@ -53,24 +53,33 @@ const reuseCachedLayout = (state, hangingProtocolService: HangingProtocolService
if (!displaySetOptions) {
return;
}
const activeDisplaySetUIDs = [];
for (let i = 0; i < displaySetOptions.length; i++) {
const displaySetUID = displaySetInstanceUIDs[i];
if (!displaySetUID) {
continue;
}
if (viewportId === activeViewportId && i === 0) {
setDisplaySetSelector(`${activeStudyUID}:activeDisplaySet:0`, displaySetUID);
if (viewportId === activeViewportId) {
activeDisplaySetUIDs.push(displaySetUID);
}
// The activeDisplaySet selector should only be set once (i.e. for the actual active display set)
if (displaySetOptions[i]?.id && displaySetOptions[i].id !== 'activeDisplaySet') {
// TODO: handle multiple layers/display sets for the non-active viewports
setDisplaySetSelector(
`${activeStudyUID}:${displaySetOptions[i].id}:${
displaySetOptions[i].matchedDisplaySetsIndex || 0
}`,
displaySetUID
[displaySetUID]
);
}
}
if (viewportId === activeViewportId) {
// After going through all the display set options for the active viewport, store the display set selector array
setDisplaySetSelector(`${activeStudyUID}:activeDisplaySet:0`, activeDisplaySetUIDs);
}
});
setHangingProtocolStageIndex(cacheId, hpInfo);