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
@@ -26,12 +26,12 @@ function ViewerViewportGrid(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [
|
||||
matchDetails,
|
||||
const {
|
||||
viewportMatchDetails,
|
||||
hpAlreadyApplied,
|
||||
] = HangingProtocolService.getState();
|
||||
} = HangingProtocolService.getMatchDetails();
|
||||
|
||||
if (!matchDetails.length) {
|
||||
if (!viewportMatchDetails.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,24 +43,24 @@ function ViewerViewportGrid(props) {
|
||||
}
|
||||
|
||||
// if current viewport doesn't have a match
|
||||
if (matchDetails[i] === undefined) return;
|
||||
if (viewportMatchDetails[i] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { displaySetsInfo, viewportOptions } = matchDetails[i];
|
||||
const { displaySetsInfo, viewportOptions } = viewportMatchDetails[i];
|
||||
|
||||
const displaySetUIDsToHang = [];
|
||||
const displaySetUIDsToHangOptions = [];
|
||||
displaySetsInfo.forEach(({ SeriesInstanceUID, displaySetOptions }) => {
|
||||
const matchingDisplaySet = availableDisplaySets.find(ds => {
|
||||
return ds.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
displaySetsInfo.forEach(
|
||||
({ displaySetInstanceUID, displaySetOptions }) => {
|
||||
if (!displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matchingDisplaySet) {
|
||||
return;
|
||||
displaySetUIDsToHang.push(displaySetInstanceUID);
|
||||
displaySetUIDsToHangOptions.push(displaySetOptions);
|
||||
}
|
||||
|
||||
displaySetUIDsToHang.push(matchingDisplaySet.displaySetInstanceUID);
|
||||
displaySetUIDsToHangOptions.push(displaySetOptions);
|
||||
});
|
||||
);
|
||||
|
||||
if (!displaySetUIDsToHang.length) {
|
||||
continue;
|
||||
@@ -113,10 +113,11 @@ function ViewerViewportGrid(props) {
|
||||
|
||||
// Using Hanging protocol engine to match the displaySets
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED,
|
||||
activeDisplaySets => {
|
||||
updateDisplaySetsForViewports(activeDisplaySets);
|
||||
const { unsubscribe } = HangingProtocolService.subscribe(
|
||||
HangingProtocolService.EVENTS.PROTOCOL_CHANGED,
|
||||
() => {
|
||||
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||
updateDisplaySetsForViewports(displaySets);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -369,6 +370,11 @@ function _getViewportComponent(displaySets, viewportComponents) {
|
||||
const SOPClassHandlerId = displaySets[0].SOPClassHandlerId;
|
||||
|
||||
for (let i = 0; i < viewportComponents.length; i++) {
|
||||
if (!viewportComponents[i])
|
||||
throw new Error('viewport components not defined');
|
||||
if (!viewportComponents[i].displaySetsToDisplay) {
|
||||
throw new Error('displaySetsToDisplay is null');
|
||||
}
|
||||
if (
|
||||
viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)
|
||||
) {
|
||||
@@ -376,6 +382,7 @@ function _getViewportComponent(displaySets, viewportComponents) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
throw new Error(`No display set handler for ${SOPClassHandlerId}`);
|
||||
}
|
||||
|
||||
export default ViewerViewportGrid;
|
||||
@@ -9,24 +9,26 @@ import { useQuery } from '@hooks';
|
||||
import ViewportGrid from '@components/ViewportGrid';
|
||||
import Compose from './Compose';
|
||||
|
||||
async function defaultRouteInit({
|
||||
servicesManager,
|
||||
studyInstanceUIDs,
|
||||
dataSource,
|
||||
}) {
|
||||
/**
|
||||
* Initialize the route.
|
||||
*
|
||||
* @param props.servicesManager to read services from
|
||||
* @param props.studyInstanceUIDs for a list of studies to read
|
||||
* @param props.dataSource to read the data from
|
||||
* @returns array of subscriptions to cancel
|
||||
*/
|
||||
function defaultRouteInit({ servicesManager, studyInstanceUIDs, dataSource }) {
|
||||
const {
|
||||
DisplaySetService,
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const unsubscriptions = [];
|
||||
// TODO: This should be baked into core, not manual?
|
||||
// DisplaySetService would wire this up?
|
||||
const {
|
||||
unsubscribe: instanceAddedUnsubscribe,
|
||||
} = DicomMetadataStore.subscribe(
|
||||
DicomMetadataStore.EVENTS.INSTANCES_ADDED,
|
||||
({ StudyInstanceUID, SeriesInstanceUID, madeInClient = false }) => {
|
||||
function({ StudyInstanceUID, SeriesInstanceUID, madeInClient = false }) {
|
||||
const seriesMetadata = DicomMetadataStore.getSeries(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
@@ -38,19 +40,43 @@ async function defaultRouteInit({
|
||||
|
||||
unsubscriptions.push(instanceAddedUnsubscribe);
|
||||
|
||||
const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe(
|
||||
DicomMetadataStore.EVENTS.SERIES_ADDED,
|
||||
({ StudyInstanceUID, madeInClient }) => {
|
||||
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
if (!madeInClient) {
|
||||
HangingProtocolService.run(studyMetadata);
|
||||
}
|
||||
}
|
||||
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
||||
dataSource.retrieve.series.metadata({ StudyInstanceUID })
|
||||
);
|
||||
unsubscriptions.push(seriesAddedUnsubscribe);
|
||||
|
||||
studyInstanceUIDs.forEach(StudyInstanceUID => {
|
||||
dataSource.retrieve.series.metadata({ StudyInstanceUID });
|
||||
// The hanging protocol matching service is fairly expensive to run multiple
|
||||
// times, and doesn't allow partial matches to be made (it will simply fail
|
||||
// to display anything if a required match fails), so we wait here until all metadata
|
||||
// is retrieved (which will synchronously trigger the display set creation)
|
||||
// until we run the hanging protocol matching service.
|
||||
|
||||
Promise.allSettled(allRetrieves).then(() => {
|
||||
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||
|
||||
if (!displaySets || !displaySets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const studyMap = {};
|
||||
|
||||
// Prior studies don't quite work properly yet, but the studies list
|
||||
// is at least being generated and passed in.
|
||||
const studies = displaySets.reduce((prev, curr) => {
|
||||
const { StudyInstanceUID } = curr;
|
||||
if (!studyMap[StudyInstanceUID]) {
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
studyMap[StudyInstanceUID] = study;
|
||||
prev.push(study);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
// The assumption is that the display set at position 0 is the first
|
||||
// study being displayed, and is thus the "active" study.
|
||||
const activeStudy = studies[0];
|
||||
|
||||
// run the hanging protocol matching service on the displaySets
|
||||
HangingProtocolService.run({ studies, activeStudy, displaySets });
|
||||
});
|
||||
|
||||
return unsubscriptions;
|
||||
@@ -236,7 +262,7 @@ export default function ModeRoute({
|
||||
});
|
||||
}
|
||||
|
||||
return await defaultRouteInit({
|
||||
return defaultRouteInit({
|
||||
servicesManager,
|
||||
studyInstanceUIDs,
|
||||
dataSource,
|
||||
|
||||
Reference in new issue
Block a user