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:
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,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user