fix(segmentation): restrict overlay segmentation menu to same frame of reference as viewport background display set (#5900)
- Add FrameOfReferenceUID to SEG and RTSTRUCT displaySet in SOP Class Handlers so the FOR is available for filtering - Sync optimisticOverlayDisplaySets when background display set changes so the overlay menu reflects the correct state after a background switch - Add FOR matching guard to the hydrate segmentation synchronizer to prevent the hydration synchronizer from blindly mirroring segmentations from a source viewport to a target viewport if their primary Frames of Reference do not align. - fix segmentation overlay order reversal on viewport re-render
This commit is contained in:
1 parent
a875d7de1f
commit
b9029ef6f8
15 files changed
+223
-56
No files matched your search
@@ -56,6 +56,7 @@ function _getDisplaySetsFromSeries(
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
frameOfReferenceUID: null,
|
||||
referencedImages: null,
|
||||
referencedSeriesInstanceUID: null,
|
||||
referencedDisplaySetInstanceUID: null,
|
||||
@@ -95,6 +96,9 @@ function _getDisplaySetsFromSeries(
|
||||
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||
|
||||
displaySet.frameOfReferenceUID =
|
||||
instance.ReferencedFrameOfReferenceSequence?.[0]?.FrameOfReferenceUID;
|
||||
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const referencedDisplaySets =
|
||||
displaySetService.getDisplaySetsForReferences(referencedSeriesSequence);
|
||||
@@ -113,6 +117,7 @@ function _getDisplaySetsFromSeries(
|
||||
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
|
||||
displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -121,6 +126,7 @@ function _getDisplaySetsFromSeries(
|
||||
const [referencedDisplaySet] = referencedDisplaySets;
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
|
||||
displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID;
|
||||
}
|
||||
|
||||
displaySet.load = ({ headers, createSegmentation = true }) =>
|
||||
|
||||
@@ -30,6 +30,7 @@ function _getDisplaySetsFromSeries(
|
||||
SeriesDate,
|
||||
StructureSetDate,
|
||||
SOPClassUID,
|
||||
FrameOfReferenceUID,
|
||||
wadoRoot,
|
||||
wadoUri,
|
||||
wadoUriRoot,
|
||||
@@ -49,6 +50,7 @@ function _getDisplaySetsFromSeries(
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
frameOfReferenceUID: FrameOfReferenceUID,
|
||||
referencedImages: null,
|
||||
referencedSeriesInstanceUID: null,
|
||||
referencedDisplaySetInstanceUID: null,
|
||||
@@ -104,6 +106,7 @@ function _getDisplaySetsFromSeries(
|
||||
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
|
||||
displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -111,6 +114,7 @@ function _getDisplaySetsFromSeries(
|
||||
} else {
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
|
||||
displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID;
|
||||
}
|
||||
|
||||
displaySet.load = async ({ headers }) =>
|
||||
|
||||
@@ -230,7 +230,7 @@ async function _load(
|
||||
|
||||
function _measurementBelongsToDisplaySet({ measurement, displaySet }) {
|
||||
return (
|
||||
measurement.coords[0].ReferencedFrameOfReferenceSequence === displaySet.FrameOfReferenceUID
|
||||
measurement.coords[0].ReferencedFrameOfReferenceSequence === displaySet.frameOfReferenceUID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ export default function hydrateStructuredReport(
|
||||
|
||||
for (const FrameOfReferenceUID of frameOfReferenceUIDs) {
|
||||
const displaySetsFOR = displaySetService.getDisplaySetsBy(
|
||||
ds => ds.FrameOfReferenceUID === FrameOfReferenceUID && !ds.isDerivedDisplaySet
|
||||
ds => ds.frameOfReferenceUID === FrameOfReferenceUID && !ds.isDerivedDisplaySet
|
||||
);
|
||||
const ds = chooseDisplaySet(displaySetsFOR, FrameOfReferenceUID);
|
||||
if (!ds) {
|
||||
@@ -335,7 +335,7 @@ function getReferenceData3D(toolData, servicesManager: Types.ServicesManager) {
|
||||
const { points } = toolData.annotation.data.handles;
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const displaySetsFOR = displaySetService.getDisplaySetsBy(
|
||||
ds => ds.FrameOfReferenceUID === FrameOfReferenceUID
|
||||
ds => ds.frameOfReferenceUID === FrameOfReferenceUID
|
||||
);
|
||||
if (!displaySetsFOR.length || !points?.length) {
|
||||
return {
|
||||
|
||||
+9
-2
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Icons,
|
||||
@@ -43,6 +43,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
|
||||
const [thresholdOpacityEnabled, setThresholdOpacityEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setOptimisticOverlayDisplaySets(overlayDisplaySets);
|
||||
}, [backgroundDisplaySet?.displaySetInstanceUID, overlayDisplaySets]);
|
||||
|
||||
/**
|
||||
* Change the background display set
|
||||
*/
|
||||
@@ -482,7 +486,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectTrigger
|
||||
className="flex-1"
|
||||
data-cy={`overlay-background-ds-select-${viewportId}`}
|
||||
>
|
||||
<SelectValue>
|
||||
{(
|
||||
backgroundDisplaySet?.SeriesDescription ||
|
||||
|
||||
@@ -80,8 +80,8 @@ export function getEnhancedDisplaySets({ viewportId, services }) {
|
||||
|
||||
// Check if Frame of Reference matches
|
||||
if (
|
||||
displaySet.FrameOfReferenceUID &&
|
||||
displaySet.FrameOfReferenceUID !== backgroundDisplaySet.FrameOfReferenceUID
|
||||
displaySet.frameOfReferenceUID &&
|
||||
displaySet.frameOfReferenceUID !== backgroundDisplaySet.frameOfReferenceUID
|
||||
) {
|
||||
return {
|
||||
...displaySet,
|
||||
|
||||
@@ -82,7 +82,7 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
frameOfReferenceUID: FrameOfReferenceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
SeriesDescription: SeriesDescription || 'Microscopy Data',
|
||||
|
||||
+9
-1
@@ -66,7 +66,15 @@ const segmentationRepresentationModifiedCallback = async (
|
||||
|
||||
const sharedDisplaySetExists = isAnyDisplaySetCommon(sourceDisplaySetUIDs, targetDisplaySetUIDs);
|
||||
|
||||
if (!sharedDisplaySetExists && !viewport.getFrameOfReferenceUID()) {
|
||||
const targetFrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
||||
const sourceFrameOfReferenceUID =
|
||||
getEnabledElementByViewportId(sourceViewportId)?.viewport?.getFrameOfReferenceUID();
|
||||
|
||||
if (!sharedDisplaySetExists && !targetFrameOfReferenceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sharedDisplaySetExists && targetFrameOfReferenceUID !== sourceFrameOfReferenceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -870,28 +870,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
|
||||
}
|
||||
|
||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||
viewport.setProperties({ ...properties });
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
await viewport.setStack(imageIds, initialImageIndexToUse);
|
||||
viewport.setProperties({ ...properties });
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
|
||||
if (overlayProcessingResults?.length) {
|
||||
overlayProcessingResults.forEach(overlayProcessingResult => {
|
||||
if (overlayProcessingResult?.addOverlayFn) {
|
||||
overlayProcessingResult.addOverlayFn();
|
||||
}
|
||||
});
|
||||
}
|
||||
await this._addOverlayRepresentations(overlayProcessingResults);
|
||||
|
||||
if (displayArea) {
|
||||
viewport.setDisplayArea(displayArea);
|
||||
}
|
||||
if (rotation) {
|
||||
viewport.setProperties({ rotation });
|
||||
}
|
||||
if (flipHorizontal) {
|
||||
viewport.setCamera({ flipHorizontal: true });
|
||||
}
|
||||
});
|
||||
if (displayArea) {
|
||||
viewport.setDisplayArea(displayArea);
|
||||
}
|
||||
if (rotation) {
|
||||
viewport.setProperties({ rotation });
|
||||
}
|
||||
if (flipHorizontal) {
|
||||
viewport.setCamera({ flipHorizontal: true });
|
||||
}
|
||||
}
|
||||
|
||||
private _getInitialImageIndexForViewport(
|
||||
@@ -1120,14 +1113,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
|
||||
if (overlayProcessingResults?.length) {
|
||||
overlayProcessingResults.forEach(({ addOverlayFn }) => {
|
||||
if (addOverlayFn) {
|
||||
addOverlayFn();
|
||||
}
|
||||
});
|
||||
}
|
||||
await this._addOverlayRepresentations(overlayProcessingResults);
|
||||
viewport.render();
|
||||
|
||||
volumesProperties.forEach(({ properties, volumeId }) => {
|
||||
@@ -1193,7 +1179,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
private addOverlayRepresentationForDisplaySet(
|
||||
displaySet: OhifTypes.DisplaySet,
|
||||
viewport: Types.IViewport
|
||||
) {
|
||||
): Promise<void> {
|
||||
const { segmentationService } = this.servicesManager.services;
|
||||
const segmentationId = displaySet.displaySetInstanceUID;
|
||||
|
||||
@@ -1203,18 +1189,36 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
: csToolsEnums.SegmentationRepresentations.Contour;
|
||||
|
||||
const { predecessorImageId } = displaySet;
|
||||
segmentationService.addSegmentationRepresentation(viewport.id, {
|
||||
segmentationId,
|
||||
predecessorImageId,
|
||||
type: representationType,
|
||||
config: {
|
||||
blendMode:
|
||||
viewport?.getBlendMode?.() === 1 ? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const segmentationRepresentationPromise = segmentationService.addSegmentationRepresentation(
|
||||
viewport.id,
|
||||
{
|
||||
segmentationId,
|
||||
predecessorImageId,
|
||||
type: representationType,
|
||||
config: {
|
||||
blendMode:
|
||||
viewport?.getBlendMode?.() === 1
|
||||
? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND
|
||||
: undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
// store the segmentation presentation id in the viewport info
|
||||
this.storePresentation({ viewportId: viewport.id });
|
||||
return segmentationRepresentationPromise;
|
||||
}
|
||||
|
||||
private async _addOverlayRepresentations(
|
||||
overlayProcessingResults?: Array<{ addOverlayFn?: () => Promise<void> }>
|
||||
): Promise<void> {
|
||||
if (!overlayProcessingResults?.length) {
|
||||
return;
|
||||
}
|
||||
for (const overlayProcessingResult of overlayProcessingResults) {
|
||||
if (overlayProcessingResult?.addOverlayFn) {
|
||||
await overlayProcessingResult.addOverlayFn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: keepCamera is an interim solution until we have a better solution for
|
||||
@@ -1272,7 +1276,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
if (viewport instanceof ECGViewport) {
|
||||
return this._setEcgViewport(viewport as unknown as Types.IECGViewport, viewportData as StackViewportData);
|
||||
return this._setEcgViewport(
|
||||
viewport as unknown as Types.IECGViewport,
|
||||
viewportData as StackViewportData
|
||||
);
|
||||
}
|
||||
|
||||
return this._setOtherViewport(
|
||||
|
||||
@@ -113,7 +113,7 @@ const makeDisplaySet = (instances, index) => {
|
||||
label:
|
||||
instance.SeriesDescription ||
|
||||
`${i18n.t('Series')} ${instance.SeriesNumber} - ${i18n.t(instance.Modality)}`,
|
||||
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
frameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
});
|
||||
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(imageSet);
|
||||
|
||||
@@ -329,16 +329,16 @@ export default class MicroscopyService extends PubSubService {
|
||||
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM');
|
||||
|
||||
const { FrameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet;
|
||||
const { frameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet;
|
||||
|
||||
if (!studyMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
let derivedDisplaySets = FrameOfReferenceUID
|
||||
let derivedDisplaySets = frameOfReferenceUID
|
||||
? displaySets.filter(
|
||||
ds =>
|
||||
ds.ReferencedFrameOfReferenceUID === FrameOfReferenceUID ||
|
||||
ds.ReferencedFrameOfReferenceUID === frameOfReferenceUID ||
|
||||
// sometimes each depth instance has the different FrameOfReferenceID
|
||||
othersFrameOfReferenceUID.includes(ds.ReferencedFrameOfReferenceUID)
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplayS
|
||||
const referencedDisplaySet = otherDisplaySets.find(
|
||||
displaySet =>
|
||||
displaySet.Modality === 'SM' &&
|
||||
(displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID ||
|
||||
(displaySet.frameOfReferenceUID === ReferencedFrameOfReferenceUID ||
|
||||
// sometimes each depth instance has the different FrameOfReferenceID
|
||||
displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID))
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user