feat: make hanging protocol work on displaySets instead of series (#2837)

* feat(HP): Apply HP to display sets, fix race condition

* fix: displaySetService no event not needed (#2912)

* fix various styles and renamings

* feat: refactored hp service

* fix: use HP service event for viewport grid

* remove unnecessary doc

* fix test

* apply review comment

* fix: segmentation creation

Co-authored-by: Alireza <ar.sedghi@gmail.com>
This commit is contained in:
Bill WallaceandAlireza authored and GitHub committed 2022-09-07 11:59:11 -04:00
1 parent 7c0756824e
commit e1d366e1e1
26 files changed
+893 -979

No files matched your search

@@ -39,6 +39,8 @@ class CornerstoneViewportService implements IViewportService {
renderingEngine: Types.IRenderingEngine | null;
viewportsInfo: Map<number, ViewportInfo>;
viewportGridResizeObserver: ResizeObserver | null;
// TODO - get the right type here.
hangingProtocolService: object;
/**
* Service-specific
@@ -59,7 +61,7 @@ class CornerstoneViewportService implements IViewportService {
this.listeners = {};
this.EVENTS = EVENTS;
const { HangingProtocolService } = servicesManager.services;
this.HangingProtocolService = HangingProtocolService;
this.hangingProtocolService = HangingProtocolService;
Object.assign(this, pubSubServiceInterface);
//
}
@@ -166,12 +168,14 @@ class CornerstoneViewportService implements IViewportService {
const viewportInfo = this.viewportsInfo.get(viewportIndex);
viewportInfo.setRenderingEngineId(renderingEngine.id);
const { viewportOptions, displaySetOptions } =
this._getViewportAndDisplaySetOptions(
publicViewportOptions,
publicDisplaySetOptions,
viewportInfo
);
const {
viewportOptions,
displaySetOptions,
} = this._getViewportAndDisplaySetOptions(
publicViewportOptions,
publicDisplaySetOptions,
viewportInfo
);
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
@@ -265,7 +269,6 @@ class CornerstoneViewportService implements IViewportService {
viewportInfo: ViewportInfo
) {
const displaySetOptions = viewportInfo.getDisplaySetOptions();
const { imageIds, initialImageIndex } = viewportData;
let initialImageIndexToUse = initialImageIndex;
@@ -352,7 +355,7 @@ class CornerstoneViewportService implements IViewportService {
// (This call may or may not create sub-requests for series metadata)
const volumeInputArray = [];
const displaySetOptionsArray = viewportInfo.getDisplaySetOptions();
const { HangingProtocolService } = this;
const { hangingProtocolService } = this;
for (let i = 0; i < viewportData.imageIds.length; i++) {
const imageIds = viewportData.imageIds[i];
@@ -384,11 +387,11 @@ class CornerstoneViewportService implements IViewportService {
}
if (
HangingProtocolService.hasCustomImageLoadStrategy() &&
!HangingProtocolService.customImageLoadPerformed
hangingProtocolService.hasCustomImageLoadStrategy() &&
!hangingProtocolService.customImageLoadPerformed
) {
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
return HangingProtocolService.runImageLoadStrategy({
return hangingProtocolService.runImageLoadStrategy({
viewportId: viewport.id,
volumeInputArray,
});
@@ -415,8 +418,9 @@ class CornerstoneViewportService implements IViewportService {
) {
const { index, preset } = initialImageOptions;
const { numberOfSlices } =
csUtils.getImageSliceDataForVolumeViewport(viewport);
const { numberOfSlices } = csUtils.getImageSliceDataForVolumeViewport(
viewport
);
const imageIndex = this._getInitialImageIndex(
numberOfSlices,
@@ -16,7 +16,6 @@ export type ViewportOptions = {
viewportId: string;
orientation?: Types.Orientation;
background?: Types.Point3;
initialView?: string;
syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions;
customViewportProps?: Record<string, unknown>;
@@ -28,7 +27,6 @@ export type PublicViewportOptions = {
viewportId?: string;
orientation?: string;
background?: Types.Point3;
initialView?: string;
syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions;
customViewportProps?: Record<string, unknown>;
@@ -8,8 +8,8 @@ const viewportIdVolumeInputArrayMap = new Map<string, unknown[]>();
/**
* This function caches the volumeUIDs until all the volumes inside the
* hangging protocol are initialized. Then it goes through the imageIds
* of the volumes, and interleav them, in order for the volumes to be loaded
* hanging protocol are initialized. Then it goes through the imageIds
* of the volumes, and interleave them, in order for the volumes to be loaded
* together from middle to the start and the end.
* @param {Object} props image loading properties from Cornerstone ViewportService
* @returns
@@ -17,7 +17,7 @@ const viewportIdVolumeInputArrayMap = new Map<string, unknown[]>();
export default function interleaveCenterLoader({
data: { viewportId, volumeInputArray },
displaySetsMatchDetails,
matchDetails,
viewportMatchDetails: matchDetails,
}) {
viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray);
@@ -16,7 +16,7 @@ const viewportIdVolumeInputArrayMap = new Map<string, unknown[]>();
export default function interleaveTopToBottom({
data: { viewportId, volumeInputArray },
displaySetsMatchDetails,
matchDetails,
viewportMatchDetails: matchDetails,
}) {
viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray);
@@ -473,12 +473,13 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
const numberOfSeries = seriesPromises.length;
seriesPromises.forEach(async (seriesPromise, index) => {
const instances = await seriesPromise;
storeInstances(instances);
if (index === numberOfSeries - 1) setSuccessFlag();
});
const seriesDeliveredPromises = seriesPromises.map(promise =>
promise.then(instances => {
storeInstances(instances);
})
);
await Promise.all(seriesDeliveredPromises);
setSuccessFlag();
},
deleteStudyMetadataPromise,
getImageIdsForDisplaySet(displaySet) {
@@ -23,7 +23,9 @@ const defaultProtocol = {
displaySets: [
{
id: 'displaySet',
// Unused currently
imageMatchingRules: [],
// Matches displaysets, NOT series
seriesMatchingRules: [],
studyMatchingRules: [],
},
+28 -29
View File
@@ -28,32 +28,32 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
DisplaySetService,
ToolGroupService,
ToolBarService,
HangingProtocolService,
} = servicesManager.services;
const [metadata, setMetadata] = useState(DEFAULT_MEATADATA);
const [ptDisplaySet, setPtDisplaySet] = useState(null);
const handleMetadataChange = useCallback(
metadata => {
setMetadata(prevState => {
const newState = { ...prevState };
Object.keys(metadata).forEach(key => {
if (typeof metadata[key] === 'object') {
newState[key] = {
...prevState[key],
...metadata[key],
};
} else {
newState[key] = metadata[key];
}
});
return newState;
const handleMetadataChange = metadata => {
setMetadata(prevState => {
const newState = { ...prevState };
Object.keys(metadata).forEach(key => {
if (typeof metadata[key] === 'object') {
newState[key] = {
...prevState[key],
...metadata[key],
};
} else {
newState[key] = metadata[key];
}
});
},
[metadata]
);
return newState;
});
};
const getMatchingPTDisplaySet = useCallback(() => {
const ptDisplaySet = commandsManager.runCommand('getMatchingPTDisplaySet');
const getMatchingPTDisplaySet = viewportMatchDetails => {
const ptDisplaySet = commandsManager.runCommand('getMatchingPTDisplaySet', {
viewportMatchDetails,
});
if (!ptDisplaySet) {
return;
@@ -67,16 +67,16 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
ptDisplaySet,
metadata,
};
}, []);
};
useEffect(() => {
const displaySets = DisplaySetService.activeDisplaySets;
const displaySets = DisplaySetService.getActiveDisplaySets();
const { viewportMatchDetails } = HangingProtocolService.getMatchDetails();
if (!displaySets.length) {
return;
}
const displaySetInfo = getMatchingPTDisplaySet();
const displaySetInfo = getMatchingPTDisplaySet(viewportMatchDetails);
if (!displaySetInfo) {
return;
@@ -89,15 +89,14 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
// get the patientMetadata from the StudyInstanceUIDs and update the state
useEffect(() => {
const { unsubscribe } = DisplaySetService.subscribe(
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
() => {
const displaySetInfo = getMatchingPTDisplaySet();
const { unsubscribe } = HangingProtocolService.subscribe(
HangingProtocolService.EVENTS.PROTOCOL_CHANGED,
({ viewportMatchDetails }) => {
const displaySetInfo = getMatchingPTDisplaySet(viewportMatchDetails);
if (!displaySetInfo) {
return;
}
const { ptDisplaySet, metadata } = displaySetInfo;
setPtDisplaySet(ptDisplaySet);
setMetadata(metadata);
+21 -18
View File
@@ -41,9 +41,9 @@ const commandsModule = ({
}
function _getMatchedViewportsToolGroupIds() {
const [matchedViewports] = HangingProtocolService.getState();
const { viewportMatchDetails } = HangingProtocolService.getMatchDetails();
const toolGroupIds = [];
matchedViewports.forEach(({ viewportOptions }) => {
viewportMatchDetails.forEach(({ viewportOptions }) => {
const { toolGroupId } = viewportOptions;
if (toolGroupIds.indexOf(toolGroupId) === -1) {
toolGroupIds.push(toolGroupId);
@@ -54,33 +54,30 @@ const commandsModule = ({
}
const actions = {
getMatchingPTDisplaySet: () => {
getMatchingPTDisplaySet: ({ viewportMatchDetails }) => {
// Todo: this is assuming that the hanging protocol has successfully matched
// the correct PT. For future, we should have a way to filter out the PTs
// that are in the viewer layout (but then we have the problem of the attenuation
// corrected PT vs the non-attenuation correct PT)
const matches = HangingProtocolService.getDisplaySetsMatchDetails();
const matchedSeriesInstanceUIDs = Array.from(matches.values()).map(
({ SeriesInstanceUID }) => SeriesInstanceUID
);
let ptDisplaySet = null;
for (const SeriesInstanceUID of matchedSeriesInstanceUIDs) {
const displaySets = DisplaySetService.getDisplaySetsForSeries(
SeriesInstanceUID
for (const matched of viewportMatchDetails) {
const { displaySetsInfo } = matched;
const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) =>
DisplaySetService.getDisplaySetByUID(displaySetInstanceUID)
);
if (!displaySets || displaySets.length === 0) {
continue;
}
const displaySet = displaySets[0];
if (displaySet.Modality !== 'PT') {
continue;
}
ptDisplaySet = displaySets.find(
displaySet => displaySet.Modality === 'PT'
);
ptDisplaySet = displaySet;
if (ptDisplaySet) {
break;
}
}
return ptDisplaySet;
@@ -121,7 +118,10 @@ const commandsModule = ({
createNewLabelmapFromPT: async () => {
// Create a segmentation of the same resolution as the source data
// using volumeLoader.createAndCacheDerivedVolume.
const ptDisplaySet = actions.getMatchingPTDisplaySet();
const { viewportMatchDetails } = HangingProtocolService.getMatchDetails();
const ptDisplaySet = actions.getMatchingPTDisplaySet({
viewportMatchDetails,
});
if (!ptDisplaySet) {
UINotificationService.error('No matching PT display set found');
@@ -565,8 +565,11 @@ const commandsModule = ({
},
setFusionPTColormap: ({ toolGroupId, colormap }) => {
const toolGroup = ToolGroupService.getToolGroup(toolGroupId);
const { viewportMatchDetails } = HangingProtocolService.getMatchDetails();
const ptDisplaySet = actions.getMatchingPTDisplaySet();
const ptDisplaySet = actions.getMatchingPTDisplaySet({
viewportMatchDetails,
});
if (!ptDisplaySet) {
return;