OHIF-285/OHIF-274: Fix default hanging of display sets into the ViewportGrid (#1917)
* OHIF-285/OHIF-274: Fix default hanging of display sets into the ViewportGrid Co-authored-by: James A. Petts <jamesapetts@gmail.com>
This commit is contained in:
1 parent
9df57e7f7f
commit
55cf9f2306
33 files changed
+548
-208
No files matched your search
@@ -34,6 +34,7 @@ import {
|
||||
ToolBarSerivce, // TODO: Typo
|
||||
MeasurementService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
} from './services';
|
||||
|
||||
import IWebApiDataSource from './DataSources/IWebApiDataSource';
|
||||
@@ -80,6 +81,7 @@ const OHIF = {
|
||||
MeasurementService,
|
||||
ToolBarSerivce, // TODO: TYPO
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
IWebApiDataSource,
|
||||
DicomMetadataStore,
|
||||
//
|
||||
@@ -123,6 +125,7 @@ export {
|
||||
MeasurementService,
|
||||
ToolBarSerivce,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
IWebApiDataSource,
|
||||
DicomMetadataStore,
|
||||
ViewModelProvider,
|
||||
|
||||
@@ -72,7 +72,7 @@ const BaseImplementation = {
|
||||
study = _model.studies[_model.studies.length - 1];
|
||||
}
|
||||
|
||||
study.addSeries(instances);
|
||||
study.addInstancesToSeries(instances);
|
||||
|
||||
// Broadcast an event even if we used cached data.
|
||||
// This is because the mode needs to listen to instances that are added to build up its active displaySets.
|
||||
@@ -83,6 +83,24 @@ const BaseImplementation = {
|
||||
SeriesInstanceUID,
|
||||
});
|
||||
},
|
||||
addSeriesMetadata(seriesSummaryMetadata) {
|
||||
const { StudyInstanceUID } = seriesSummaryMetadata[0];
|
||||
let study = _getStudy(StudyInstanceUID);
|
||||
if (!study) {
|
||||
study = createStudyMetadata(StudyInstanceUID);
|
||||
_model.studies.push(study);
|
||||
}
|
||||
|
||||
seriesSummaryMetadata.forEach(series => {
|
||||
const { SeriesInstanceUID } = series;
|
||||
|
||||
study.setSeriesMetadata(SeriesInstanceUID, series);
|
||||
});
|
||||
|
||||
this._broadcastEvent(EVENTS.SERIES_ADDED, {
|
||||
StudyInstanceUID,
|
||||
});
|
||||
},
|
||||
addStudy(study) {
|
||||
const { StudyInstanceUID } = study;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const EVENTS = {
|
||||
INSTANCES_ADDED: 'event::dicomMetadataStore:instancesAdded',
|
||||
SERIES_ADDED: 'event::dicomMetadataStore:seriesAdded',
|
||||
};
|
||||
|
||||
export default EVENTS;
|
||||
@@ -10,19 +10,31 @@ function createStudyMetadata(StudyInstanceUID) {
|
||||
* @param {string} instances[].SeriesInstanceUID
|
||||
* @returns {bool} true if series were added; false if series already exist
|
||||
*/
|
||||
addSeries: function(instances) {
|
||||
addInstancesToSeries: function(instances) {
|
||||
const { SeriesInstanceUID } = instances[0];
|
||||
const seriesExists = this.series.some(
|
||||
const existingSeries = this.series.find(
|
||||
s => s.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (!seriesExists) {
|
||||
if (existingSeries) {
|
||||
existingSeries.instances.push(...instances);
|
||||
} else {
|
||||
const series = createSeriesMetadata(instances);
|
||||
this.series.push(series);
|
||||
}
|
||||
|
||||
return !seriesExists;
|
||||
},
|
||||
|
||||
setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) {
|
||||
let existingSeries = this.series.find(
|
||||
s => s.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (existingSeries) {
|
||||
existingSeries = Object.assign(existingSeries, seriesMetadata);
|
||||
} else {
|
||||
this.series.push(Object.assign({ instances: [] }, seriesMetadata));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
const name = 'HangingProtocolService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
getState: _getState,
|
||||
setHangingProtocol: _setHangingProtocol,
|
||||
setHangingProtocolAppliedForViewport: _setHangingProtocolAppliedForViewport,
|
||||
setServiceImplementation,
|
||||
reset: _reset,
|
||||
set: _set,
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
_getState: () => console.warn('getState() NOT IMPLEMENTED'),
|
||||
_setHangingProtocol: () =>
|
||||
console.warn('_setHangingProtocol() NOT IMPLEMENTED'),
|
||||
_setHangingProtocolAppliedForViewport: () =>
|
||||
console.warn('_setHangingProtocolAppliedForViewport() NOT IMPLEMENTED'),
|
||||
_reset: () => console.warn('reset() NOT IMPLEMENTED'),
|
||||
_set: () => console.warn('set() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function _getState() {
|
||||
return serviceImplementation._getState();
|
||||
}
|
||||
|
||||
function _setHangingProtocol(hangingProtocol) {
|
||||
return serviceImplementation._setHangingProtocol(hangingProtocol);
|
||||
}
|
||||
|
||||
function _setHangingProtocolAppliedForViewport(hpAlreadyApplied) {
|
||||
return serviceImplementation._setHangingProtocolAppliedForViewport(
|
||||
hpAlreadyApplied
|
||||
);
|
||||
}
|
||||
|
||||
function _set(state) {
|
||||
return serviceImplementation._set(state);
|
||||
}
|
||||
|
||||
function _reset() {
|
||||
return serviceImplementation._reset({});
|
||||
}
|
||||
|
||||
function setServiceImplementation({
|
||||
getState: getStateImplementation,
|
||||
setHangingProtocol: setHangingProtocolImplementation,
|
||||
setHangingProtocolAppliedForViewport: setHangingProtocolAppliedForViewportImplementation,
|
||||
reset: resetImplementation,
|
||||
set: setImplementation,
|
||||
}) {
|
||||
if (getStateImplementation) {
|
||||
serviceImplementation._getState = getStateImplementation;
|
||||
}
|
||||
if (setHangingProtocolImplementation) {
|
||||
serviceImplementation._setHangingProtocol = setHangingProtocolImplementation;
|
||||
}
|
||||
if (setHangingProtocolAppliedForViewportImplementation) {
|
||||
serviceImplementation._setHangingProtocolAppliedForViewport = setHangingProtocolAppliedForViewportImplementation;
|
||||
}
|
||||
if (resetImplementation) {
|
||||
serviceImplementation._reset = resetImplementation;
|
||||
}
|
||||
if (setImplementation) {
|
||||
serviceImplementation._set = setImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import HangingProtocolService from './HangingProtocolService';
|
||||
|
||||
export default HangingProtocolService;
|
||||
@@ -42,7 +42,6 @@ export default class ToolBarService {
|
||||
}
|
||||
|
||||
_buttonTypes() {
|
||||
console.log(this.extensionManager.modules);
|
||||
const buttonTypes = {};
|
||||
const registeredToolbarModules = this.extensionManager.modules[
|
||||
'toolbarModule'
|
||||
|
||||
@@ -8,6 +8,7 @@ import DicomMetadataStore from './DicomMetadataStore';
|
||||
import DisplaySetService from './DisplaySetService';
|
||||
import ToolBarSerivce from './ToolBarService';
|
||||
import ViewportGridService from './ViewportGridService';
|
||||
import HangingProtocolService from './HangingProtocolService';
|
||||
|
||||
export {
|
||||
MeasurementService,
|
||||
@@ -20,4 +21,5 @@ export {
|
||||
DisplaySetService,
|
||||
ToolBarSerivce,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import DICOMWeb from '../DICOMWeb/';
|
||||
import isLowPriorityModality from '../utils/isLowPriorityModality';
|
||||
|
||||
const INFO = Symbol('INFO');
|
||||
|
||||
/**
|
||||
* Creates an object with processed series information and saves its reference
|
||||
* inside the series object itself.
|
||||
* @param {Object} series The raw series object
|
||||
* @returns {Object} object containing some useful info from given series
|
||||
*/
|
||||
export default function getSeriesInfo(series) {
|
||||
let info = series[INFO];
|
||||
if (!info) {
|
||||
const Modality = DICOMWeb.getString(series['00080060'], '').toUpperCase();
|
||||
info = Object.freeze({
|
||||
Modality,
|
||||
isLowPriority: isLowPriorityModality(Modality),
|
||||
SeriesInstanceUID: DICOMWeb.getString(series['0020000E']),
|
||||
SeriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0,
|
||||
});
|
||||
series[INFO] = info;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import getStudyBoxData from './getStudyBoxData';
|
||||
import retrieveStudiesMetadata from './retrieveStudiesMetadata.js';
|
||||
import searchStudies from './searchStudies';
|
||||
import sortStudy from './sortStudy';
|
||||
import { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria } from './sortStudy';
|
||||
|
||||
const studies = {
|
||||
services: {
|
||||
@@ -21,6 +21,9 @@ const studies = {
|
||||
getStudyBoxData,
|
||||
searchStudies,
|
||||
sortStudy,
|
||||
sortStudySeries,
|
||||
sortStudyInstances,
|
||||
sortingCriteria,
|
||||
};
|
||||
|
||||
export default studies;
|
||||
@@ -3,7 +3,6 @@ import dcmjs from 'dcmjs';
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||
import { sortStudySeries, sortingCriteria } from '../../sortStudy';
|
||||
import getSeriesInfo from '../../getSeriesInfo';
|
||||
import {
|
||||
createStudyFromSOPInstanceList,
|
||||
addInstancesToStudy,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import getSeriesInfo from './getSeriesInfo';
|
||||
import isLowPriorityModality from '../utils/isLowPriorityModality';
|
||||
|
||||
/**
|
||||
* Series sorting criteria: series considered low priority are moved to the end
|
||||
@@ -7,15 +7,16 @@ import getSeriesInfo from './getSeriesInfo';
|
||||
* @param {Object} secondSeries
|
||||
*/
|
||||
function seriesInfoSortingCriteria(firstSeries, secondSeries) {
|
||||
const a = getSeriesInfo(firstSeries);
|
||||
const b = getSeriesInfo(secondSeries);
|
||||
if (!a.isLowPriority && b.isLowPriority) {
|
||||
const aLowPriority = isLowPriorityModality(firstSeries.Modality);
|
||||
const bLowPriority = isLowPriorityModality(secondSeries.Modality);
|
||||
if (!aLowPriority && bLowPriority) {
|
||||
return -1;
|
||||
}
|
||||
if (a.isLowPriority && !b.isLowPriority) {
|
||||
if (aLowPriority && !bLowPriority) {
|
||||
return 1;
|
||||
}
|
||||
return a.SeriesNumber - b.SeriesNumber;
|
||||
|
||||
return firstSeries.SeriesNumber - secondSeries.SeriesNumber;
|
||||
}
|
||||
|
||||
const seriesSortCriteria = {
|
||||
@@ -93,4 +94,4 @@ export default function sortStudy(
|
||||
return study;
|
||||
}
|
||||
|
||||
export { sortStudySeries, sortStudyInstances, sortingCriteria };
|
||||
export { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria };
|
||||
@@ -20,6 +20,7 @@ import formatPN from './formatPN';
|
||||
import resolveObjectPath from './resolveObjectPath';
|
||||
import * as hierarchicalListUtils from './hierarchicalListUtils';
|
||||
import * as progressTrackingUtils from './progressTrackingUtils';
|
||||
import isLowPriorityModality from './isLowPriorityModality';
|
||||
|
||||
const utils = {
|
||||
guid,
|
||||
@@ -44,6 +45,7 @@ const utils = {
|
||||
resolveObjectPath,
|
||||
hierarchicalListUtils,
|
||||
progressTrackingUtils,
|
||||
isLowPriorityModality
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -68,6 +70,7 @@ export {
|
||||
resolveObjectPath,
|
||||
hierarchicalListUtils,
|
||||
progressTrackingUtils,
|
||||
isLowPriorityModality
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -1,4 +1,4 @@
|
||||
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']);
|
||||
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR', 'SR', 'RTSTRUCT']);
|
||||
|
||||
export default function isLowPriorityModality(Modality) {
|
||||
return LOW_PRIORITY_MODALITIES.includes(Modality);
|
||||
|
||||
@@ -23,6 +23,9 @@ export {
|
||||
ViewportGridContext,
|
||||
ViewportGridProvider,
|
||||
useViewportGrid,
|
||||
HangingProtocolContext,
|
||||
HangingProtocolProvider,
|
||||
useHangingProtocol,
|
||||
} from './src/contextProviders';
|
||||
|
||||
/** COMPONENTS */
|
||||
|
||||
@@ -144,7 +144,7 @@ const Button = ({
|
||||
|
||||
Button.propTypes = {
|
||||
children: PropTypes.node,
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large', 'initial']),
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large', 'initial', 'inherit']),
|
||||
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
||||
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
||||
color: PropTypes.oneOf([
|
||||
|
||||
@@ -135,7 +135,7 @@ ButtonGroup.propTypes = {
|
||||
fullWidth: PropTypes.bool,
|
||||
orientation: PropTypes.oneOf(['vertical', 'horizontal']),
|
||||
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||
size: PropTypes.oneOf(['small', 'medium', 'large', 'inherit']),
|
||||
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
||||
};
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ SidePanel.propTypes = {
|
||||
iconLabel: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
content: PropTypes.node,
|
||||
content: PropTypes.func, // TODO: Should be node, but it keeps complaining?
|
||||
}),
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
@@ -172,7 +172,7 @@ SidePanel.propTypes = {
|
||||
iconLabel: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
content: PropTypes.node,
|
||||
content: PropTypes.func, // TODO: Should be node, but it keeps complaining?
|
||||
})
|
||||
),
|
||||
]),
|
||||
|
||||
@@ -143,7 +143,7 @@ StudyBrowser.propTypes = {
|
||||
'thumbnailNoImage',
|
||||
]).isRequired,
|
||||
isTracked: PropTypes.bool,
|
||||
viewportIdentificator: PropTypes.string,
|
||||
viewportIdentificator: PropTypes.arrayOf(PropTypes.string),
|
||||
/**
|
||||
* Data the thumbnail should expose to a receiving drop target. Use a matching
|
||||
* `dragData.type` to identify which targets can receive this draggable item.
|
||||
|
||||
@@ -113,7 +113,7 @@ ThumbnailList.propTypes = {
|
||||
'thumbnailTracked',
|
||||
'thumbnailNoImage',
|
||||
]).isRequired,
|
||||
viewportIdentificator: PropTypes.string,
|
||||
viewportIdentificator: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
isTracked: PropTypes.bool,
|
||||
/**
|
||||
* Data the thumbnail should expose to a receiving drop target. Use a matching
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
hangingProtocol: null,
|
||||
hpAlreadyApplied: {},
|
||||
};
|
||||
|
||||
export const HangingProtocolContext = createContext(DEFAULT_STATE);
|
||||
|
||||
export function HangingProtocolProvider({ children, service }) {
|
||||
const hangingProtocolReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_HANGING_PROTOCOL': {
|
||||
return {
|
||||
...state,
|
||||
...{ hangingProtocol: action.payload.hangingProtocol },
|
||||
};
|
||||
}
|
||||
case 'SET_HANGING_PROTOCOL_APPLIED_FOR_VIEWPORT': {
|
||||
const index = action.payload.index;
|
||||
const newHPAlreadyApplied = Object.assign({}, state.hpAlreadyApplied);
|
||||
|
||||
newHPAlreadyApplied[index] = true;
|
||||
|
||||
return {
|
||||
...state,
|
||||
...{ hpAlreadyApplied: newHPAlreadyApplied },
|
||||
};
|
||||
}
|
||||
case 'RESET': {
|
||||
return {
|
||||
hangingProtocol: null,
|
||||
hpAlreadyApplied: {},
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET': {
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return action.payload;
|
||||
}
|
||||
};
|
||||
|
||||
const [hangingProtocolState, dispatch] = useReducer(
|
||||
hangingProtocolReducer,
|
||||
DEFAULT_STATE
|
||||
);
|
||||
|
||||
console.log('hangingProtocolState', hangingProtocolState);
|
||||
|
||||
const getState = useCallback(() => hangingProtocolState, [
|
||||
hangingProtocolState,
|
||||
]);
|
||||
|
||||
const setHangingProtocol = useCallback(
|
||||
hangingProtocol =>
|
||||
dispatch({
|
||||
type: 'SET_HANGING_PROTOCOL',
|
||||
payload: {
|
||||
hangingProtocol,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setHangingProtocolAppliedForViewport = useCallback(
|
||||
index =>
|
||||
dispatch({
|
||||
type: 'SET_HANGING_PROTOCOL_APPLIED_FOR_VIEWPORT',
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const reset = useCallback(
|
||||
() =>
|
||||
dispatch({
|
||||
type: 'RESET',
|
||||
payload: {},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const set = useCallback(
|
||||
payload =>
|
||||
dispatch({
|
||||
type: 'SET',
|
||||
payload,
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the implementation of the HangingProtocolService that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (service) {
|
||||
service.setServiceImplementation({
|
||||
getState,
|
||||
setHangingProtocol,
|
||||
setHangingProtocolAppliedForViewport,
|
||||
reset,
|
||||
set,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
getState,
|
||||
service,
|
||||
setHangingProtocol,
|
||||
setHangingProtocolAppliedForViewport,
|
||||
reset,
|
||||
set,
|
||||
]);
|
||||
|
||||
const api = {
|
||||
// getState,
|
||||
setHangingProtocol,
|
||||
setHangingProtocolAppliedForViewport,
|
||||
reset,
|
||||
set,
|
||||
};
|
||||
|
||||
return (
|
||||
<HangingProtocolContext.Provider value={[hangingProtocolState, api]}>
|
||||
{children}
|
||||
</HangingProtocolContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
HangingProtocolProvider.propTypes = {
|
||||
children: PropTypes.any,
|
||||
service: PropTypes.shape({
|
||||
setServiceImplementation: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export const useHangingProtocol = () => useContext(HangingProtocolContext);
|
||||
@@ -35,3 +35,9 @@ export {
|
||||
ViewportGridProvider,
|
||||
useViewportGrid,
|
||||
} from './ViewportGridProvider';
|
||||
|
||||
export {
|
||||
HangingProtocolContext,
|
||||
HangingProtocolProvider,
|
||||
useHangingProtocol,
|
||||
} from './HangingProtocolProvider';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ThemeWrapper,
|
||||
ViewportDialogProvider,
|
||||
ViewportGridProvider,
|
||||
HangingProtocolProvider,
|
||||
} from '@ohif/ui';
|
||||
// Viewer Project
|
||||
// TODO: Should this influence study list?
|
||||
@@ -58,6 +59,7 @@ function App({ config, defaultExtensions }) {
|
||||
UINotificationService,
|
||||
UIViewportDialogService,
|
||||
ViewportGridService, // TODO: Should this be a "UI" Service?
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
return (
|
||||
@@ -66,15 +68,17 @@ function App({ config, defaultExtensions }) {
|
||||
<Router basename={routerBasename}>
|
||||
<ThemeWrapper>
|
||||
<ViewportGridProvider service={ViewportGridService}>
|
||||
<ViewportDialogProvider service={UIViewportDialogService}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={Modal} service={UIModalService}>
|
||||
{appRoutes}
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</ViewportDialogProvider>
|
||||
<HangingProtocolProvider service={HangingProtocolService}>
|
||||
<ViewportDialogProvider service={UIViewportDialogService}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={Modal} service={UIModalService}>
|
||||
{appRoutes}
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</ViewportDialogProvider>
|
||||
</HangingProtocolProvider>
|
||||
</ViewportGridProvider>
|
||||
</ThemeWrapper>
|
||||
</Router>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DisplaySetService,
|
||||
ToolBarSerivce,
|
||||
ViewportGridService,
|
||||
HangingProtocolService
|
||||
// utils,
|
||||
// redux as reduxOHIF,
|
||||
} from '@ohif/core';
|
||||
@@ -54,6 +55,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
DisplaySetService,
|
||||
ToolBarSerivce,
|
||||
ViewportGridService,
|
||||
HangingProtocolService
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CSS Grid Reference: http://grid.malven.co/
|
||||
*/
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
@@ -22,21 +22,53 @@ function ViewerViewportGrid(props) {
|
||||
} = viewportGrid;
|
||||
|
||||
// TODO -> Need some way of selecting which displaySets hit the viewports.
|
||||
const { DisplaySetService, MeasurementService } = servicesManager.services;
|
||||
const {
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// This is a placeholder for applying hanging protocols
|
||||
// It probably shouldn't be done here
|
||||
// For now it just hangs the first display set in the study in 1x1
|
||||
// as sorted by SeriesNumber
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED,
|
||||
displaySets => {
|
||||
displaySets.sort((a, b) => {
|
||||
const isImageSet = x => x instanceof ImageSet;
|
||||
return isImageSet(a) === isImageSet(b) ? 0 : isImageSet(a) ? -1 : 1;
|
||||
});
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
eventData => {
|
||||
const { displaySetsAdded } = eventData;
|
||||
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportIndex: 0,
|
||||
displaySetInstanceUID: displaySets[0].displaySetInstanceUID,
|
||||
});
|
||||
const data = HangingProtocolService.getState();
|
||||
|
||||
// TODO: Sometimes this is undefined?
|
||||
const { hpAlreadyApplied } = data;
|
||||
|
||||
// Match each viewport individually
|
||||
const numViewports = numRows * numCols;
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
if (hpAlreadyApplied[i] === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporary until matching is ported back over from the Meteor version.
|
||||
const reqSeriesInstanceUID =
|
||||
data.hangingProtocol.stages[0].viewports[0].seriesMatchingRules[0]
|
||||
.constraint.equals.value;
|
||||
const matchingDisplaySet = displaySetsAdded.find(ds => {
|
||||
return ds.SeriesInstanceUID === reqSeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (!matchingDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportIndex: i,
|
||||
displaySetInstanceUID: matchingDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
HangingProtocolService.setHangingProtocolAppliedForViewport(i);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -70,32 +102,6 @@ function ViewerViewportGrid(props) {
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
// TODO -> Make a HangingProtocolService
|
||||
// Commented out whilst not in use to avoid pointlessly regenerating this function.
|
||||
// const HangingProtocolService = displaySets => {
|
||||
// let displaySetInstanceUID;
|
||||
|
||||
// // Fallback
|
||||
// if (!displaySets || !displaySets.length) {
|
||||
// const displaySet = DisplaySetService.activeDisplaySets[0];
|
||||
// displaySetInstanceUID = displaySet.displaySetInstanceUID;
|
||||
// } else {
|
||||
// const displaySet = displaySets[0];
|
||||
// displaySetInstanceUID = displaySet.displaySetInstanceUID;
|
||||
// }
|
||||
|
||||
// return {
|
||||
// numRows: 1,
|
||||
// numCols: 1,
|
||||
// activeViewportIndex: 0,
|
||||
// viewports: [
|
||||
// {
|
||||
// displaySetInstanceUID,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// };
|
||||
|
||||
const onDoubleClick = viewportIndex => {
|
||||
// TODO -> Disabled for now.
|
||||
// onNewImage on a cornerstone viewport is firing setDisplaySetForViewport.
|
||||
@@ -138,30 +144,6 @@ function ViewerViewportGrid(props) {
|
||||
});
|
||||
};
|
||||
|
||||
// TODO:
|
||||
// Hmm... Should a "displaySet" being added update the viewport based on HP?
|
||||
// I guess it might.
|
||||
// This is where you would likely "fill" emptyViewports if none had content
|
||||
// Or to recheck best placement/priority based on all activeDisplaySets
|
||||
// useEffect(() => {
|
||||
// const { unsubscribe } = DisplaySetService.subscribe(
|
||||
// DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
// displaySets => {
|
||||
// displaySets.forEach(ds => console.log(`VPG:ADD::${ds.StudyInstanceUID}`));
|
||||
// const hp = HangingProtocolService(displaySets);
|
||||
// viewportGrid.setViewportGrid(hp);
|
||||
// },
|
||||
// );
|
||||
|
||||
// return unsubscribe;
|
||||
// }, []);
|
||||
|
||||
// const droppedDisplaySet = DisplaySetService.getDisplaySetByUID(
|
||||
// displaySetInstanceUID
|
||||
// );
|
||||
// const updatedViewportGridState = HangingProtocolService([
|
||||
// droppedDisplaySet,
|
||||
// ]);
|
||||
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => {
|
||||
console.warn(`DROPPED: ${displaySetInstanceUID}`);
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
@@ -188,7 +170,7 @@ function ViewerViewportGrid(props) {
|
||||
viewportComponents
|
||||
);
|
||||
|
||||
const onInterationHandler = (event) => {
|
||||
const onInterationHandler = event => {
|
||||
if (isActive) return;
|
||||
|
||||
if (event) {
|
||||
@@ -211,7 +193,11 @@ function ViewerViewportGrid(props) {
|
||||
onInteraction={onInterationHandler}
|
||||
isActive={isActive}
|
||||
>
|
||||
<div className={classNames('h-full w-full flex flex-col align-center', { 'pointer-events-none': !isActive })}>
|
||||
<div
|
||||
className={classNames('h-full w-full flex flex-col align-center', {
|
||||
'pointer-events-none': !isActive,
|
||||
})}
|
||||
>
|
||||
<ViewportComponent
|
||||
displaySet={displaySet}
|
||||
viewportIndex={viewportIndex}
|
||||
|
||||
@@ -2,13 +2,15 @@ import React, { useEffect } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import PropTypes from 'prop-types';
|
||||
// TODO: DicomMetadataStore should be injected?
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
import { DicomMetadataStore, utils } from '@ohif/core';
|
||||
import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
|
||||
//
|
||||
import { useQuery } from '@hooks';
|
||||
import ViewportGrid from '@components/ViewportGrid';
|
||||
import Compose from './Compose';
|
||||
|
||||
const { isLowPriorityModality } = utils;
|
||||
|
||||
export default function ModeRoute({
|
||||
location,
|
||||
mode,
|
||||
@@ -46,6 +48,7 @@ export default function ModeRoute({
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const layoutTemplateData = route.layoutTemplate({ location });
|
||||
@@ -98,9 +101,13 @@ export default function ModeRoute({
|
||||
useEffect(() => {
|
||||
// Core
|
||||
|
||||
// TODO: For some reason this is running before the Providers
|
||||
// are calling setServiceImplementation
|
||||
// TOOD -> iterate through services.
|
||||
MeasurementService.clearMeasurements();
|
||||
ViewportGridService.reset();
|
||||
HangingProtocolService.reset();
|
||||
|
||||
// Extension
|
||||
extensionManager.onModeEnter();
|
||||
// Mode
|
||||
@@ -150,6 +157,93 @@ export default function ModeRoute({
|
||||
dataSource,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DicomMetadataStore.subscribe(
|
||||
DicomMetadataStore.EVENTS.SERIES_ADDED,
|
||||
({ StudyInstanceUID }) => {
|
||||
const studyMetadata = DicomMetadataStore.getStudy(
|
||||
StudyInstanceUID,
|
||||
);
|
||||
|
||||
const sortedSeries = studyMetadata.series.sort((a, b) => {
|
||||
const aLowPriority = isLowPriorityModality(a.Modality);
|
||||
const bLowPriority = isLowPriorityModality(b.Modality);
|
||||
if (!aLowPriority && bLowPriority) {
|
||||
return -1;
|
||||
}
|
||||
if (aLowPriority && !bLowPriority) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a.SeriesNumber - b.SeriesNumber;
|
||||
})
|
||||
|
||||
const { SeriesInstanceUID } = sortedSeries[0]
|
||||
|
||||
HangingProtocolService.setHangingProtocol(
|
||||
{
|
||||
/*protocolMatchingRules: [
|
||||
{
|
||||
id: '7tmuq7KzDMCWFeapc',
|
||||
weight: 2,
|
||||
required: false,
|
||||
attribute: 'x00081030',
|
||||
constraint: {
|
||||
contains: {
|
||||
value: 'DFCI CT CHEST',
|
||||
},
|
||||
},
|
||||
},
|
||||
],*/
|
||||
stages: [
|
||||
{
|
||||
/*id: 'v5PfGt9F6mffZPif5',
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
Rows: 1,
|
||||
Columns: 1,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},*/
|
||||
viewports: [
|
||||
{
|
||||
viewportSettings: {},
|
||||
imageMatchingRules: [],
|
||||
seriesMatchingRules: [
|
||||
{
|
||||
id: 'mXnsCcNzZL56z7mTZ',
|
||||
weight: 1,
|
||||
required: true,
|
||||
attribute: 'SeriesInstanceUID',
|
||||
constraint: {
|
||||
equals: {
|
||||
value: SeriesInstanceUID,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
studyMatchingRules: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
return unsubscribe;
|
||||
}, [
|
||||
mode,
|
||||
dataSourceName,
|
||||
location,
|
||||
DisplaySetService,
|
||||
extensionManager,
|
||||
sopClassHandlers,
|
||||
StudyInstanceUIDsAsArray,
|
||||
dataSource,
|
||||
]);
|
||||
|
||||
const reducer = (state, action) => {
|
||||
console.log(state, action);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user