[OHIF-261 & OHIF-262] (#1862)
* WIP * Reset viewport grid on mode switch. * Make lifecyclehooks distinct from modules. Add needed SR hydration state on creation. * Update extensions/cornerstone/src/init.js * Remove unused REMOVE_MEASUREMENT listener. Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
parent
4dfe144bf8
commit
14797a8973
@ -5,6 +5,7 @@ import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import merge from 'lodash.merge';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import initWADOImageLoader from './initWADOImageLoader.js';
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
//
|
||||
@ -329,6 +330,14 @@ const _connectToolsToMeasurementService = measurementService => {
|
||||
}
|
||||
}
|
||||
|
||||
const { MEASUREMENTS_CLEARED } = measurementService.EVENTS;
|
||||
|
||||
measurementService.subscribe(MEASUREMENTS_CLEARED, () => {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
const enabledElement = evt.detail.element;
|
||||
const completedEvt = csTools.EVENTS.MEASUREMENT_COMPLETED;
|
||||
const updatedEvt = csTools.EVENTS.MEASUREMENT_MODIFIED;
|
||||
|
||||
@ -109,8 +109,8 @@ function OHIFCornerstoneSRViewport({
|
||||
useEffect(() => {
|
||||
if (!displaySet.isLoaded) {
|
||||
displaySet.load();
|
||||
setIsHydrated(displaySet.isHydrated);
|
||||
}
|
||||
setIsHydrated(displaySet.isHydrated);
|
||||
}, [displaySet]);
|
||||
|
||||
const setTrackingUniqueIdentifiersForElement = useCallback(targetElement => {
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import id from './id';
|
||||
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
||||
import { utils, classes } from '@ohif/core';
|
||||
import addMeasurement from './utils/addMeasurement';
|
||||
import isRehydratable from './utils/isRehydratable';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
|
||||
const sopClassHandlerName = 'dicom-sr';
|
||||
|
||||
// TODO ->
|
||||
// Add SR thumbnail
|
||||
// Make viewport
|
||||
@ -70,7 +68,7 @@ function _getDisplaySetsFromSeries(
|
||||
if (
|
||||
!ConceptNameCodeSequence ||
|
||||
ConceptNameCodeSequence.CodeValue !==
|
||||
CodeNameCodeSequenceValues.ImagingMeasurementReport
|
||||
CodeNameCodeSequenceValues.ImagingMeasurementReport
|
||||
) {
|
||||
console.warn(
|
||||
'Only support Imaging Measurement Report SRs (TID1500) for now'
|
||||
@ -79,7 +77,7 @@ function _getDisplaySetsFromSeries(
|
||||
}
|
||||
|
||||
const displaySet = {
|
||||
plugin: id,
|
||||
//plugin: id,
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
SeriesDescription,
|
||||
@ -88,7 +86,7 @@ function _getDisplaySetsFromSeries(
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,
|
||||
SOPClassHandlerId,
|
||||
referencedImages: null,
|
||||
measurements: null,
|
||||
isDerivedDisplaySet: true,
|
||||
@ -119,6 +117,7 @@ function _load(displaySet, servicesManager, extensionManager) {
|
||||
|
||||
displaySet.isHydrated = false;
|
||||
displaySet.isLocked = isRehydratable(displaySet, mappings) ? false : true;
|
||||
displaySet.isLoaded = true;
|
||||
|
||||
// Check currently added displaySets and add measurements if the sources exist.
|
||||
DisplaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
@ -250,7 +249,7 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
|
||||
return [
|
||||
{
|
||||
name: sopClassHandlerName,
|
||||
name: SOPClassHandlerName,
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
@ -282,7 +281,7 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
trackingUniqueIdentifier => {
|
||||
const mergedContentSequence =
|
||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||
trackingUniqueIdentifier
|
||||
trackingUniqueIdentifier
|
||||
];
|
||||
|
||||
const measurement = _processMeasurement(mergedContentSequence);
|
||||
@ -322,7 +321,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
||||
|
||||
if (
|
||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||
trackingUniqueIdentifier
|
||||
trackingUniqueIdentifier
|
||||
] === undefined
|
||||
) {
|
||||
// Add the full ContentSequence
|
||||
@ -448,9 +447,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const cornerstoneFreeTextFinding = Findings.find(
|
||||
Finding =>
|
||||
Finding.ConceptCodeSequence.CodingSchemeDesignator ===
|
||||
CORNERSTONE_CODING_SCHEME_DESIGNATOR &&
|
||||
CORNERSTONE_CODING_SCHEME_DESIGNATOR &&
|
||||
Finding.ConceptCodeSequence.CodeValue ===
|
||||
CORNERSTONE_FREETEXT_CODE_VALUE
|
||||
CORNERSTONE_FREETEXT_CODE_VALUE
|
||||
);
|
||||
if (cornerstoneFreeTextFinding) {
|
||||
measurement.labels.push({
|
||||
|
||||
@ -1 +1,7 @@
|
||||
export default 'org.ohif.dicom-sr';
|
||||
const id = 'org.ohif.dicom-sr';
|
||||
|
||||
export default id;
|
||||
|
||||
const SOPClassHandlerName = 'dicom-sr';
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||
export { SOPClassHandlerName, SOPClassHandlerId };
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import onModeEnter from './onModeEnter';
|
||||
import id from './id.js';
|
||||
import init from './init';
|
||||
|
||||
@ -60,4 +61,5 @@ export default {
|
||||
return [{ name: 'dicom-sr', component: ExtendedOHIFCornerstoneSRViewport }];
|
||||
},
|
||||
getSopClassHandlerModule,
|
||||
onModeEnter,
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@ const defaultConfig = {
|
||||
/**
|
||||
* @param {object} configuration
|
||||
*/
|
||||
export default function init({ configuration = {} }) {
|
||||
export default function init({ configuration = {}, servicesManager }) {
|
||||
const conifg = Object.assign({}, defaultConfig, configuration);
|
||||
|
||||
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
|
||||
@ -22,4 +22,22 @@ export default function init({ configuration = {} }) {
|
||||
cornerstoneTools.register('module', id, dicomSRModule);
|
||||
cornerstoneTools.addTool(DICOMSRDisplayTool);
|
||||
cornerstoneTools.setToolEnabled(TOOL_NAMES.DICOM_SR_DISPLAY_TOOL);
|
||||
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
|
||||
DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
data => {
|
||||
const { displaySetsAdded, options } = data;
|
||||
displaySetsAdded.forEach(dSet => {
|
||||
if (options.madeInClient) {
|
||||
// Set just made displaySets as hydrated.
|
||||
if (!dSet.isLoaded) {
|
||||
dSet.load();
|
||||
}
|
||||
dSet.isHydrated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
15
extensions/dicom-sr/src/onModeEnter.js
Normal file
15
extensions/dicom-sr/src/onModeEnter.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { SOPClassHandlerId } from './id';
|
||||
|
||||
export default function onModeEnter({ servicesManager }) {
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
const displaySetCache = DisplaySetService.getDisplaySetCache();
|
||||
|
||||
const srDisplaySets = displaySetCache.filter(
|
||||
ds => ds.SOPClassHandlerId === SOPClassHandlerId
|
||||
);
|
||||
|
||||
srDisplaySets.forEach(ds => {
|
||||
// New mode route, allow SRs to be hydrated again
|
||||
ds.isHydrated = false;
|
||||
});
|
||||
}
|
||||
@ -16,6 +16,7 @@ export default class ExtensionManager {
|
||||
this.moduleTypeNames.forEach(moduleType => {
|
||||
this.modules[moduleType] = [];
|
||||
});
|
||||
this._extensionLifeCycleHooks = { onModeEnter: {} };
|
||||
this.dataSourceMap = {};
|
||||
this.defaultDataSourceName = appConfig.defaultDataSourceName;
|
||||
this.activeDataSource = undefined;
|
||||
@ -25,6 +26,27 @@ export default class ExtensionManager {
|
||||
this.activeDataSource = dataSourceName;
|
||||
}
|
||||
|
||||
onModeEnter() {
|
||||
const {
|
||||
registeredExtensionIds,
|
||||
getModuleEntry,
|
||||
_servicesManager,
|
||||
_commandsManager,
|
||||
_extensionLifeCycleHooks,
|
||||
} = this;
|
||||
|
||||
registeredExtensionIds.forEach(extensionId => {
|
||||
const onModeEnter = _extensionLifeCycleHooks.onModeEnter[extensionId];
|
||||
|
||||
if (typeof onModeEnter === 'function') {
|
||||
onModeEnter({
|
||||
servicesManager: _servicesManager,
|
||||
commandsManager: _commandsManager,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of extensions, or an array of arrays that contains extension
|
||||
* configuration pairs.
|
||||
@ -83,6 +105,11 @@ export default class ExtensionManager {
|
||||
});
|
||||
}
|
||||
|
||||
if (extension.onModeEnter) {
|
||||
this._extensionLifeCycleHooks.onModeEnter[extensionId] =
|
||||
extension.onModeEnter;
|
||||
}
|
||||
|
||||
// Register Modules
|
||||
this.moduleTypeNames.forEach(moduleType => {
|
||||
const extensionModule = this._getExtensionModule(
|
||||
@ -146,7 +173,7 @@ export default class ExtensionManager {
|
||||
};
|
||||
|
||||
getActiveDataSource = () => {
|
||||
return this.activeDataSource;
|
||||
return this.dataSourceMap[this.activeDataSource];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -5,7 +5,6 @@ const displaySetCache = [];
|
||||
|
||||
export default class DisplaySetService {
|
||||
constructor() {
|
||||
this.displaySets = {};
|
||||
this.activeDisplaySets = [];
|
||||
this.listeners = {};
|
||||
this.EVENTS = EVENTS;
|
||||
@ -33,6 +32,10 @@ export default class DisplaySetService {
|
||||
});
|
||||
}
|
||||
|
||||
getDisplaySetCache() {
|
||||
return displaySetCache;
|
||||
}
|
||||
|
||||
getActiveDisplaySets() {
|
||||
return this.activeDisplaySets;
|
||||
}
|
||||
|
||||
@ -287,7 +287,7 @@ class MeasurementService {
|
||||
if (this.measurements[id]) {
|
||||
const updatedMeasurement = {
|
||||
...measurement,
|
||||
modifiedTimestamp: Math.floor(Date.now() / 1000)
|
||||
modifiedTimestamp: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
|
||||
log.info(`Updating measurement...`, updatedMeasurement);
|
||||
@ -492,6 +492,12 @@ class MeasurementService {
|
||||
this._broadcastChange(this.EVENTS.MEASUREMENT_REMOVED, source, id);
|
||||
}
|
||||
|
||||
clearMeasurements() {
|
||||
this.measurements = {};
|
||||
|
||||
this._broadcastChange(this.EVENTS.MEASUREMENTS_CLEARED);
|
||||
}
|
||||
|
||||
_getMappingByMeasurementSource(measurementId, definition) {
|
||||
const measurement = this.getMeasurement(measurementId);
|
||||
if (this._isValidSource(measurement.source)) {
|
||||
|
||||
@ -8,6 +8,7 @@ const publicAPI = {
|
||||
setLayout: _setLayout,
|
||||
setCachedLayout: _setCachedLayout,
|
||||
setServiceImplementation,
|
||||
reset: _reset,
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
@ -17,6 +18,7 @@ const serviceImplementation = {
|
||||
_setDisplaysetForViewport: () =>
|
||||
console.warn('setDisplaysetForViewport() NOT IMPLEMENTED'),
|
||||
_setLayout: () => console.warn('setLayout() NOT IMPLEMENTED'),
|
||||
_reset: () => console.warn('reset() NOT IMPLEMENTED'),
|
||||
_setCachedLayout: () => console.warn('setCachedLayout() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
@ -39,6 +41,9 @@ function _setLayout({ numCols, numRows }) {
|
||||
return serviceImplementation._setLayout({ numCols, numRows });
|
||||
}
|
||||
|
||||
function _reset() {
|
||||
return serviceImplementation._reset({});
|
||||
}
|
||||
function _setCachedLayout({ numCols, numRows, viewports }) {
|
||||
return serviceImplementation._setLayout({ numCols, numRows, viewports });
|
||||
}
|
||||
@ -49,6 +54,7 @@ function setServiceImplementation({
|
||||
setDisplaysetForViewport: setDisplaysetForViewportImplementation,
|
||||
setCachedLayout: setCachedLayoutImplementation,
|
||||
setLayout: setLayoutImplementation,
|
||||
reset: resetImplementation,
|
||||
}) {
|
||||
if (getStateImplementation) {
|
||||
serviceImplementation._getState = getStateImplementation;
|
||||
@ -62,6 +68,9 @@ function setServiceImplementation({
|
||||
if (setLayoutImplementation) {
|
||||
serviceImplementation._setLayout = setLayoutImplementation;
|
||||
}
|
||||
if (resetImplementation) {
|
||||
serviceImplementation._reset = resetImplementation;
|
||||
}
|
||||
if (setCachedLayoutImplementation) {
|
||||
serviceImplementation._setCachedLayout = setCachedLayoutImplementation;
|
||||
}
|
||||
|
||||
@ -27,7 +27,11 @@ export function ViewportGridProvider({ children, service }) {
|
||||
return { ...state, ...{ activeViewportIndex: action.payload } };
|
||||
}
|
||||
case 'SET_DISPLAYSET_FOR_VIEWPORT': {
|
||||
const { viewportIndex, displaySetInstanceUID, imageIndex } = action.payload;
|
||||
const {
|
||||
viewportIndex,
|
||||
displaySetInstanceUID,
|
||||
imageIndex,
|
||||
} = action.payload;
|
||||
const viewports = state.viewports.slice();
|
||||
|
||||
viewports[viewportIndex] = { displaySetInstanceUID, imageIndex };
|
||||
@ -54,6 +58,17 @@ export function ViewportGridProvider({ children, service }) {
|
||||
cachedLayout: undefined,
|
||||
};
|
||||
}
|
||||
case 'RESET': {
|
||||
return {
|
||||
numCols: 1,
|
||||
numRows: 1,
|
||||
activeViewportIndex: 0,
|
||||
viewports: [
|
||||
{ displaySetInstanceUID: undefined, imageIndex: undefined },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'SET_CACHED_LAYOUT': {
|
||||
return { ...state, cachedLayout: action.payload };
|
||||
}
|
||||
@ -79,7 +94,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
payload: {
|
||||
viewportIndex,
|
||||
displaySetInstanceUID,
|
||||
imageIndex
|
||||
imageIndex,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
@ -97,6 +112,14 @@ export function ViewportGridProvider({ children, service }) {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const reset = useCallback(
|
||||
() =>
|
||||
dispatch({
|
||||
type: 'RESET',
|
||||
payload: {},
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
const setCachedLayout = useCallback(
|
||||
payload =>
|
||||
dispatch({
|
||||
@ -118,6 +141,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setActiveViewportIndex,
|
||||
setDisplaysetForViewport,
|
||||
setLayout,
|
||||
reset,
|
||||
setCachedLayout,
|
||||
});
|
||||
}
|
||||
@ -127,6 +151,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setActiveViewportIndex,
|
||||
setDisplaysetForViewport,
|
||||
setLayout,
|
||||
reset,
|
||||
setCachedLayout,
|
||||
]);
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export default function ModeRoute({
|
||||
dataSourceName,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
hotkeysManager
|
||||
hotkeysManager,
|
||||
}) {
|
||||
// Parse route params/querystring
|
||||
const query = useQuery();
|
||||
@ -35,15 +35,19 @@ export default function ModeRoute({
|
||||
|
||||
extensionManager.setActiveDataSource(dataSourceName);
|
||||
|
||||
const dataSources = extensionManager.getDataSources();
|
||||
const dataSources = extensionManager.getActiveDataSource();
|
||||
|
||||
// Only handling one instance of the datasource type (E.g. one DICOMWeb server)
|
||||
const dataSource = dataSources[0];
|
||||
// Only handling one route per mode for now
|
||||
const route = mode.routes[0];
|
||||
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
const {
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
ViewportGridService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// Only handling one route per mode for now
|
||||
// You can test via http://localhost:3000/example-mode/dicomweb
|
||||
const layoutTemplateData = route.layoutTemplate({ location });
|
||||
const layoutTemplateModuleEntry = extensionManager.getModuleEntry(
|
||||
layoutTemplateData.id
|
||||
@ -92,6 +96,14 @@ export default function ModeRoute({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Core
|
||||
|
||||
// TOOD -> iterate through services.
|
||||
MeasurementService.clearMeasurements();
|
||||
ViewportGridService.reset();
|
||||
// Extension
|
||||
extensionManager.onModeEnter();
|
||||
// Mode
|
||||
route.init({ servicesManager, extensionManager });
|
||||
}, [
|
||||
mode,
|
||||
@ -100,7 +112,7 @@ export default function ModeRoute({
|
||||
route,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
hotkeysManager
|
||||
hotkeysManager,
|
||||
]);
|
||||
|
||||
// This queries for series, but... What does it do with them?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user