feat: Automate tracking prompts for a less disruptive annotation workflow (#4822)
Co-authored-by: Devu Jayalekshmi <devu.jayalekshmi@trenser.com>
This commit is contained in:
1 parent
c0391cea1b
commit
54fded554e
42 files changed
+948
-70
No files matched your search
@@ -13,13 +13,13 @@ function promptHydrateRT({
|
||||
preHydrateCallbacks,
|
||||
hydrateRTDisplaySet,
|
||||
}: withAppTypes) {
|
||||
const { uiViewportDialogService } = servicesManager.services;
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
const extensionManager = servicesManager._extensionManager;
|
||||
const appConfig = extensionManager._appConfig;
|
||||
return new Promise(async function (resolve, reject) {
|
||||
const promptResult = appConfig?.disableConfirmationPrompts
|
||||
? RESPONSE.HYDRATE_SEG
|
||||
: await _askHydrate(uiViewportDialogService, viewportId);
|
||||
: await _askHydrate(uiViewportDialogService, customizationService, viewportId);
|
||||
|
||||
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||
preHydrateCallbacks?.forEach(callback => {
|
||||
@@ -37,9 +37,13 @@ function promptHydrateRT({
|
||||
});
|
||||
}
|
||||
|
||||
function _askHydrate(uiViewportDialogService: AppTypes.UIViewportDialogService, viewportId) {
|
||||
function _askHydrate(
|
||||
uiViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = 'Do you want to open this Segmentation?';
|
||||
const message = customizationService.getCustomization('viewportNotification.hydrateRTMessage');
|
||||
const actions = [
|
||||
{
|
||||
id: 'no-hydrate',
|
||||
|
||||
@@ -13,14 +13,14 @@ function promptHydrateSEG({
|
||||
preHydrateCallbacks,
|
||||
hydrateCallback,
|
||||
}: withAppTypes) {
|
||||
const { uiViewportDialogService } = servicesManager.services;
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
const extensionManager = servicesManager._extensionManager;
|
||||
const appConfig = extensionManager._appConfig;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
const promptResult = appConfig?.disableConfirmationPrompts
|
||||
? RESPONSE.HYDRATE_SEG
|
||||
: await _askHydrate(uiViewportDialogService, viewportId);
|
||||
: await _askHydrate(uiViewportDialogService, customizationService, viewportId);
|
||||
|
||||
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||
preHydrateCallbacks?.forEach(callback => {
|
||||
@@ -39,9 +39,13 @@ function promptHydrateSEG({
|
||||
});
|
||||
}
|
||||
|
||||
function _askHydrate(uiViewportDialogService, viewportId) {
|
||||
function _askHydrate(
|
||||
uiViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = 'Do you want to open this Segmentation?';
|
||||
const message = customizationService.getCustomization('viewportNotification.hydrateSEGMessage');
|
||||
const actions = [
|
||||
{
|
||||
id: 'no-hydrate',
|
||||
|
||||
@@ -179,7 +179,10 @@ function Trigger(props) {
|
||||
>
|
||||
<div>
|
||||
{React.cloneElement(children, childProps)}
|
||||
<ChevronDownIcon key="chevronDown" className="text-primary h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
<ChevronDownIcon
|
||||
key="chevronDown"
|
||||
className="text-primary h-4 w-4 shrink-0 transition-transform duration-200"
|
||||
/>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ export const groupByNamedSets = (items, grouping) => {
|
||||
};
|
||||
|
||||
export function MeasurementsOrAdditionalFindings(props): React.ReactNode {
|
||||
const { items, children, grouping = {}, customHeader, group } = props;
|
||||
const { items, children, grouping = {}, customHeader, group, actions } = props;
|
||||
|
||||
return (
|
||||
<AccordionGroup
|
||||
@@ -72,6 +72,7 @@ export function MeasurementsOrAdditionalFindings(props): React.ReactNode {
|
||||
<MeasurementTableNested
|
||||
customHeader={customHeader}
|
||||
allItems={items}
|
||||
actions={actions}
|
||||
/>
|
||||
</AccordionGroup.Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -53,6 +53,10 @@ export function StudyMeasurementsActions({ items, StudyInstanceUID, measurementF
|
||||
className="pl-0.5"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (actions?.onDelete) {
|
||||
actions.onDelete();
|
||||
return;
|
||||
}
|
||||
commandsManager.runCommand('clearMeasurements', {
|
||||
measurementFilter,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,11 @@ import { toolNames } from './initCornerstoneTools';
|
||||
import { onCompletedCalibrationLine } from './tools/CalibrationLineTool';
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||
import { triggerAnnotationRenderForViewportIds } from '@cornerstonejs/tools/utilities';
|
||||
import {
|
||||
setAnnotationLabel,
|
||||
triggerAnnotationRenderForViewportIds,
|
||||
} from '@cornerstonejs/tools/utilities';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const { removeAnnotation } = annotation.state;
|
||||
@@ -190,6 +194,7 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
displaySetService,
|
||||
cornerstoneViewportService,
|
||||
customizationService,
|
||||
viewportGridService,
|
||||
} = servicesManager.services;
|
||||
const csTools3DVer1MeasurementSource = initMeasurementService(
|
||||
measurementService,
|
||||
@@ -197,7 +202,11 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
cornerstoneViewportService,
|
||||
customizationService
|
||||
);
|
||||
connectMeasurementServiceToTools(measurementService, cornerstoneViewportService);
|
||||
connectMeasurementServiceToTools(
|
||||
measurementService,
|
||||
cornerstoneViewportService,
|
||||
viewportGridService
|
||||
);
|
||||
const { annotationToMeasurement, remove } = csTools3DVer1MeasurementSource;
|
||||
|
||||
//
|
||||
@@ -323,7 +332,11 @@ const connectToolsToMeasurementService = (servicesManager: AppTypes.ServicesMana
|
||||
return csTools3DVer1MeasurementSource;
|
||||
};
|
||||
|
||||
const connectMeasurementServiceToTools = (measurementService, cornerstoneViewportService) => {
|
||||
const connectMeasurementServiceToTools = (
|
||||
measurementService,
|
||||
cornerstoneViewportService,
|
||||
viewportGridService
|
||||
) => {
|
||||
const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } =
|
||||
measurementService.EVENTS;
|
||||
|
||||
@@ -366,7 +379,8 @@ const connectMeasurementServiceToTools = (measurementService, cornerstoneViewpor
|
||||
}
|
||||
|
||||
if (data.label !== label) {
|
||||
data.label = label;
|
||||
const element = getActiveViewportEnabledElement(viewportGridService)?.viewport.element;
|
||||
setAnnotationLabel(sourceAnnotation, element, label);
|
||||
}
|
||||
|
||||
if (metadata.toolName === 'ArrowAnnotate') {
|
||||
|
||||
@@ -34,8 +34,9 @@ function PanelStudyBrowser({
|
||||
dataSource,
|
||||
customMapDisplaySets,
|
||||
onClickUntrack,
|
||||
onDoubleClickThumbnailHandlerCallBack,
|
||||
}) {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { servicesManager, commandsManager, extensionManager } = useSystem();
|
||||
const { displaySetService, customizationService } = servicesManager.services;
|
||||
const navigate = useNavigate();
|
||||
const studyMode = customizationService.getCustomization('studyBrowser.studyMode') || 'all';
|
||||
@@ -93,6 +94,7 @@ function PanelStudyBrowser({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
isHangingProtocolLayout,
|
||||
appConfig: extensionManager._appConfig,
|
||||
};
|
||||
|
||||
const handlers = customHandler?.callbacks.map(callback => callback(setupArgs));
|
||||
@@ -100,6 +102,7 @@ function PanelStudyBrowser({
|
||||
for (const handler of handlers) {
|
||||
await handler(displaySetInstanceUID);
|
||||
}
|
||||
onDoubleClickThumbnailHandlerCallBack?.(displaySetInstanceUID);
|
||||
},
|
||||
[
|
||||
activeViewportId,
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
import i18n from '@ohif/i18n';
|
||||
import { Notification } from '@ohif/ui';
|
||||
|
||||
const beginTrackingMessage = i18n.t('MeasurementTable:Track measurements for this series?');
|
||||
const trackNewSeriesMessage = i18n.t('Do you want to add this measurement to the existing report?');
|
||||
const discardSeriesMessage = i18n.t(
|
||||
'You have existing tracked measurements. What would you like to do with your existing tracked measurements?'
|
||||
);
|
||||
const trackNewStudyMessage = i18n.t('MeasurementTable:Track measurements for this series?');
|
||||
const discardStudyMessage = i18n.t(
|
||||
'Measurements cannot span across multiple studies. Do you want to save your tracked measurements?'
|
||||
);
|
||||
const hydrateSRMessage = i18n.t('Do you want to continue tracking measurements for this study?');
|
||||
const hydrateRTMessage = i18n.t('Do you want to open this Segmentation?');
|
||||
const hydrateSEGMessage = i18n.t('Do you want to open this Segmentation?');
|
||||
const discardDirtyMessage = i18n.t('There are unsaved measurements. Do you want to save it?');
|
||||
|
||||
export default {
|
||||
'ui.notificationComponent': Notification,
|
||||
'viewportNotification.beginTrackingMessage': beginTrackingMessage,
|
||||
'viewportNotification.trackNewSeriesMessage': trackNewSeriesMessage,
|
||||
'viewportNotification.discardSeriesMessage': discardSeriesMessage,
|
||||
'viewportNotification.trackNewStudyMessage': trackNewStudyMessage,
|
||||
'viewportNotification.discardStudyMessage': discardStudyMessage,
|
||||
'viewportNotification.hydrateSRMessage': hydrateSRMessage,
|
||||
'viewportNotification.hydrateRTMessage': hydrateRTMessage,
|
||||
'viewportNotification.hydrateSEGMessage': hydrateSEGMessage,
|
||||
'viewportNotification.discardDirtyMessage': discardDirtyMessage,
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import { setAnnotationLabel } from '@cornerstonejs/tools/utilities';
|
||||
import { annotation } from '@cornerstonejs/tools';
|
||||
import { LabellingFlow } from '@ohif/ui';
|
||||
import { InputDialog } from '@ohif/ui-next';
|
||||
|
||||
@@ -89,6 +91,7 @@ export async function callInputDialogAutoComplete({
|
||||
uiDialogService,
|
||||
labelConfig,
|
||||
renderContent = LabellingFlow,
|
||||
element,
|
||||
}) {
|
||||
const exclusive = labelConfig ? labelConfig.exclusive : false;
|
||||
const dropDownItems = labelConfig ? labelConfig.items : [];
|
||||
@@ -97,7 +100,8 @@ export async function callInputDialogAutoComplete({
|
||||
const labellingDoneCallback = newValue => {
|
||||
uiDialogService.hide('select-annotation');
|
||||
if (measurement && typeof newValue === 'string') {
|
||||
measurement.label = newValue;
|
||||
const sourceAnnotation = annotation.state.getAnnotation(measurement.uid);
|
||||
setAnnotationLabel(sourceAnnotation, element, newValue);
|
||||
}
|
||||
resolve(newValue);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ async function promptSaveReport({ servicesManager, commandsManager, extensionMan
|
||||
const isBackupSave = evt.isBackupSave === undefined ? evt.data.isBackupSave : evt.isBackupSave;
|
||||
const StudyInstanceUID = evt?.data?.StudyInstanceUID || ctx.trackedStudy;
|
||||
const SeriesInstanceUID = evt?.data?.SeriesInstanceUID;
|
||||
const { displaySetInstanceUID } = evt.data ?? evt;
|
||||
|
||||
const {
|
||||
trackedSeries,
|
||||
@@ -76,6 +77,7 @@ async function promptSaveReport({ servicesManager, commandsManager, extensionMan
|
||||
SeriesInstanceUID,
|
||||
viewportId,
|
||||
isBackupSave,
|
||||
displaySetInstanceUID,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Unable to save report', error);
|
||||
|
||||
+74
-14
@@ -3,14 +3,19 @@ import PropTypes from 'prop-types';
|
||||
import { Machine } from 'xstate';
|
||||
import { useMachine } from '@xstate/react';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import { promptLabelAnnotation, promptSaveReport } from '@ohif/extension-default';
|
||||
import { machineConfiguration, defaultOptions, RESPONSE } from './measurementTrackingMachine';
|
||||
import promptBeginTracking from './promptBeginTracking';
|
||||
import promptTrackNewSeries from './promptTrackNewSeries';
|
||||
import promptTrackNewStudy from './promptTrackNewStudy';
|
||||
import promptHydrateStructuredReport from './promptHydrateStructuredReport';
|
||||
import { measurementTrackingMode } from './promptBeginTracking';
|
||||
import hydrateStructuredReport from './hydrateStructuredReport';
|
||||
import { useAppConfig } from '@state';
|
||||
import {
|
||||
promptBeginTrackingWrapper,
|
||||
promptHydrateStructuredReportWrapper,
|
||||
promptTrackNewSeriesWrapper,
|
||||
promptTrackNewStudyWrapper,
|
||||
promptLabelAnnotationWrapper,
|
||||
promptSaveReportWrapper,
|
||||
promptHasDirtyAnnotationsWrapper,
|
||||
} from './promptWrapperFunctions';
|
||||
|
||||
const TrackedMeasurementsContext = React.createContext();
|
||||
TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext';
|
||||
@@ -35,7 +40,8 @@ function TrackedMeasurementsContextProvider(
|
||||
const machineOptions = Object.assign({}, defaultOptions);
|
||||
machineOptions.actions = Object.assign({}, machineOptions.actions, {
|
||||
jumpToFirstMeasurementInActiveViewport: (ctx, evt) => {
|
||||
const { trackedStudy, trackedSeries, activeViewportId } = ctx;
|
||||
const { trackedStudy, trackedSeries } = ctx;
|
||||
const { viewportId: activeViewportId } = evt.data;
|
||||
const measurements = measurementService.getMeasurements();
|
||||
const trackedMeasurements = measurements.filter(
|
||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
||||
@@ -82,7 +88,8 @@ function TrackedMeasurementsContextProvider(
|
||||
},
|
||||
|
||||
jumpToSameImageInActiveViewport: (ctx, evt) => {
|
||||
const { trackedStudy, trackedSeries, activeViewportId } = ctx;
|
||||
const { trackedStudy, trackedSeries } = ctx;
|
||||
const { viewportId: activeViewportId } = evt.data;
|
||||
const measurements = measurementService.getMeasurements();
|
||||
const trackedMeasurements = measurements.filter(
|
||||
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
|
||||
@@ -143,31 +150,64 @@ function TrackedMeasurementsContextProvider(
|
||||
for (let i = 0; i < measurementIds.length; i++) {
|
||||
measurementService.remove(measurementIds[i]);
|
||||
}
|
||||
measurementService.setIsMeasurementDeletedIndividually(false);
|
||||
},
|
||||
clearDisplaySetHydratedState: (ctx, evt) => {
|
||||
const { displaySetInstanceUID } = evt.data ?? evt;
|
||||
|
||||
const displaysets = displaySetService.getActiveDisplaySets();
|
||||
displaysets?.forEach(displayset => {
|
||||
if (
|
||||
displayset.Modality === 'SR' &&
|
||||
displayset.displaySetInstanceUID !== displaySetInstanceUID &&
|
||||
displayset.isHydrated
|
||||
) {
|
||||
displayset.isHydrated = false;
|
||||
displayset.isLoaded = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
updatedViewports: (ctx, evt) => {
|
||||
const { hangingProtocolService } = servicesManager.services;
|
||||
const { displaySetInstanceUID, viewportId } = evt.data ?? evt;
|
||||
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportId,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
||||
},
|
||||
});
|
||||
machineOptions.services = Object.assign({}, machineOptions.services, {
|
||||
promptBeginTracking: promptBeginTracking.bind(null, {
|
||||
promptBeginTracking: promptBeginTrackingWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptTrackNewSeries: promptTrackNewSeries.bind(null, {
|
||||
promptTrackNewSeries: promptTrackNewSeriesWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptTrackNewStudy: promptTrackNewStudy.bind(null, {
|
||||
promptTrackNewStudy: promptTrackNewStudyWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptSaveReport: promptSaveReport.bind(null, {
|
||||
promptSaveReport: promptSaveReportWrapper.bind(null, {
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, {
|
||||
promptHydrateStructuredReport: promptHydrateStructuredReportWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptHasDirtyAnnotations: promptHasDirtyAnnotationsWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
@@ -179,7 +219,7 @@ function TrackedMeasurementsContextProvider(
|
||||
commandsManager,
|
||||
appConfig,
|
||||
}),
|
||||
promptLabelAnnotation: promptLabelAnnotation.bind(null, {
|
||||
promptLabelAnnotation: promptLabelAnnotationWrapper.bind(null, {
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
@@ -194,6 +234,25 @@ function TrackedMeasurementsContextProvider(
|
||||
const labelConfig = customizationService.getCustomization('measurementLabels');
|
||||
return evt.data && evt.data.userResponse === RESPONSE.NO_NEVER && labelConfig?.labelOnMeasure;
|
||||
},
|
||||
isSimplifiedConfig: (ctx, evt, condMeta) => {
|
||||
return appConfig?.measurementTrackingMode === measurementTrackingMode.SIMPLIFIED;
|
||||
},
|
||||
simplifiedAndLoadSR: (ctx, evt, condMeta) => {
|
||||
return (
|
||||
appConfig?.measurementTrackingMode === measurementTrackingMode.SIMPLIFIED &&
|
||||
evt.data.isBackupSave === false
|
||||
);
|
||||
},
|
||||
hasDirtyAndSimplified: (ctx, evt, condMeta) => {
|
||||
const measurements = measurementService.getMeasurements();
|
||||
const hasDirtyMeasurements =
|
||||
measurements.some(measurement => measurement.isDirty) ||
|
||||
(measurements.length && measurementService.getIsMeasurementDeletedIndividually());
|
||||
return (
|
||||
appConfig?.measurementTrackingMode === measurementTrackingMode.SIMPLIFIED &&
|
||||
hasDirtyMeasurements
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: IMPROVE
|
||||
@@ -266,7 +325,8 @@ function TrackedMeasurementsContextProvider(
|
||||
// load function added by our sopClassHandler module
|
||||
if (
|
||||
displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&
|
||||
displaySet.isRehydratable === true
|
||||
displaySet.isRehydratable === true &&
|
||||
!displaySet.isHydrated
|
||||
) {
|
||||
const params = {
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
|
||||
+56
-1
@@ -122,13 +122,27 @@ const machineConfiguration = {
|
||||
UNTRACK_SERIES: [
|
||||
{
|
||||
target: 'tracking',
|
||||
actions: ['removeTrackedSeries', 'setIsDirty'],
|
||||
actions: ['removeTrackedSeries', 'setIsDirty', 'clearDisplaySetHydratedState'],
|
||||
cond: 'hasRemainingTrackedSeries',
|
||||
},
|
||||
{
|
||||
target: 'idle',
|
||||
},
|
||||
],
|
||||
UNTRACK_ALL: [
|
||||
{
|
||||
target: 'tracking',
|
||||
actions: [
|
||||
'clearContext',
|
||||
'setIsDirtyToClean',
|
||||
'clearDisplaySetHydratedState',
|
||||
'clearAllMeasurements',
|
||||
],
|
||||
},
|
||||
{
|
||||
target: 'idle',
|
||||
},
|
||||
],
|
||||
SET_TRACKED_SERIES: [
|
||||
{
|
||||
target: 'tracking',
|
||||
@@ -146,6 +160,15 @@ const machineConfiguration = {
|
||||
target: 'tracking',
|
||||
},
|
||||
],
|
||||
CHECK_DIRTY: {
|
||||
target: 'promptHasDirtyAnnotations',
|
||||
cond: 'hasDirtyAndSimplified',
|
||||
},
|
||||
PROMPT_HYDRATE_SR: {
|
||||
target: 'promptHydrateStructuredReport',
|
||||
cond: 'isSimplifiedConfig',
|
||||
actions: ['clearAllMeasurements', 'clearDisplaySetHydratedState'],
|
||||
},
|
||||
},
|
||||
},
|
||||
promptTrackNewSeries: {
|
||||
@@ -214,6 +237,16 @@ const machineConfiguration = {
|
||||
invoke: {
|
||||
src: 'promptSaveReport',
|
||||
onDone: [
|
||||
{
|
||||
target: 'tracking',
|
||||
actions: [
|
||||
'clearAllMeasurements',
|
||||
'clearDisplaySetHydratedState',
|
||||
'setIsDirty',
|
||||
'updatedViewports',
|
||||
],
|
||||
cond: 'simplifiedAndLoadSR',
|
||||
},
|
||||
// "clicked the save button"
|
||||
// - should clear all measurements
|
||||
// - show DICOM SR
|
||||
@@ -312,6 +345,28 @@ const machineConfiguration = {
|
||||
],
|
||||
},
|
||||
},
|
||||
promptHasDirtyAnnotations: {
|
||||
invoke: {
|
||||
src: 'promptHasDirtyAnnotations',
|
||||
onDone: [
|
||||
{
|
||||
target: 'tracking',
|
||||
actions: [
|
||||
'clearAllMeasurements',
|
||||
'clearDisplaySetHydratedState',
|
||||
'setIsDirty',
|
||||
'updatedViewports',
|
||||
],
|
||||
cond: 'shouldSetStudyAndSeries',
|
||||
},
|
||||
{
|
||||
target: 'promptSaveReport',
|
||||
cond: 'shouldPromptSaveReport',
|
||||
},
|
||||
{ target: 'tracking' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
strict: true,
|
||||
};
|
||||
|
||||
+20
-6
@@ -9,17 +9,29 @@ const RESPONSE = {
|
||||
SET_STUDY_AND_SERIES: 3,
|
||||
};
|
||||
|
||||
export const measurementTrackingMode = {
|
||||
STANDARD: 'standard',
|
||||
SIMPLIFIED: 'simplified',
|
||||
NONE: 'none',
|
||||
};
|
||||
|
||||
function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { uiViewportDialogService } = servicesManager.services;
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
const appConfig = extensionManager._appConfig;
|
||||
// When the state change happens after a promise, the state machine sends the retult in evt.data;
|
||||
// In case of direct transition to the state, the state machine sends the data in evt;
|
||||
const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
let promptResult = appConfig?.disableConfirmationPrompts
|
||||
? RESPONSE.SET_STUDY_AND_SERIES
|
||||
: await _askTrackMeasurements(uiViewportDialogService, viewportId);
|
||||
const standardMode = appConfig?.measurementTrackingMode === measurementTrackingMode.STANDARD;
|
||||
const noTrackingMode = appConfig?.measurementTrackingMode === measurementTrackingMode.NONE;
|
||||
let promptResult;
|
||||
|
||||
promptResult = noTrackingMode
|
||||
? RESPONSE.NO_NEVER
|
||||
: standardMode
|
||||
? await _askTrackMeasurements(uiViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.SET_STUDY_AND_SERIES;
|
||||
|
||||
resolve({
|
||||
userResponse: promptResult,
|
||||
@@ -30,9 +42,11 @@ function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) {
|
||||
});
|
||||
}
|
||||
|
||||
function _askTrackMeasurements(uiViewportDialogService, viewportId) {
|
||||
function _askTrackMeasurements(uiViewportDialogService, customizationService, viewportId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = i18n.t('MeasurementTable:Track measurements for this series?');
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.beginTrackingMessage'
|
||||
);
|
||||
const actions = [
|
||||
{
|
||||
id: 'prompt-begin-tracking-cancel',
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
const RESPONSE = {
|
||||
NO_NEVER: -1,
|
||||
CANCEL: 0,
|
||||
CREATE_REPORT: 1,
|
||||
ADD_SERIES: 2,
|
||||
SET_STUDY_AND_SERIES: 3,
|
||||
NO_NOT_FOR_SERIES: 4,
|
||||
};
|
||||
|
||||
function promptHasDirtyAnnotations({ servicesManager }: withAppTypes, ctx, evt) {
|
||||
const { viewportId, displaySetInstanceUID } = evt.data || evt;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
|
||||
const promptResult = await _askSaveDiscardOrCancel(
|
||||
uiViewportDialogService,
|
||||
customizationService,
|
||||
viewportId
|
||||
);
|
||||
|
||||
resolve({
|
||||
displaySetInstanceUID,
|
||||
userResponse: promptResult,
|
||||
viewportId,
|
||||
isBackupSave: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _askSaveDiscardOrCancel(
|
||||
UIViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.discardDirtyMessage'
|
||||
);
|
||||
const actions = [
|
||||
{ id: 'cancel', type: 'cancel', text: 'Cancel', value: RESPONSE.CANCEL },
|
||||
{
|
||||
id: 'discard-existing',
|
||||
type: 'secondary',
|
||||
text: 'No, discard existing',
|
||||
value: RESPONSE.SET_STUDY_AND_SERIES,
|
||||
},
|
||||
{
|
||||
id: 'save-existing',
|
||||
type: 'primary',
|
||||
text: 'Yes',
|
||||
value: RESPONSE.CREATE_REPORT,
|
||||
},
|
||||
];
|
||||
const onSubmit = result => {
|
||||
UIViewportDialogService.hide();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
UIViewportDialogService.show({
|
||||
viewportId,
|
||||
id: 'measurement-tracking-prompt-dirty-measurement',
|
||||
type: 'info',
|
||||
message,
|
||||
actions,
|
||||
onSubmit,
|
||||
onOutsideClick: () => {
|
||||
UIViewportDialogService.hide();
|
||||
resolve(RESPONSE.CANCEL);
|
||||
},
|
||||
onKeyPress: event => {
|
||||
if (event.key === 'Enter') {
|
||||
const action = actions.find(action => action.id === 'save-existing');
|
||||
onSubmit(action.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default promptHasDirtyAnnotations;
|
||||
+10
-6
@@ -1,5 +1,6 @@
|
||||
import { hydrateStructuredReport } from '@ohif/extension-cornerstone-dicom-sr';
|
||||
import { ButtonEnums } from '@ohif/ui';
|
||||
import { measurementTrackingMode } from './promptBeginTracking';
|
||||
|
||||
const RESPONSE = {
|
||||
NO_NEVER: -1,
|
||||
@@ -16,13 +17,16 @@ function promptHydrateStructuredReport(
|
||||
ctx,
|
||||
evt
|
||||
) {
|
||||
const { uiViewportDialogService, displaySetService } = servicesManager.services;
|
||||
const { uiViewportDialogService, displaySetService, customizationService } =
|
||||
servicesManager.services;
|
||||
const { viewportId, displaySetInstanceUID } = evt;
|
||||
const srDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
return new Promise(async function (resolve, reject) {
|
||||
const promptResult = appConfig?.disableConfirmationPrompts
|
||||
? RESPONSE.HYDRATE_REPORT
|
||||
: await _askTrackMeasurements(uiViewportDialogService, viewportId);
|
||||
const standardMode = appConfig?.measurementTrackingMode === measurementTrackingMode.STANDARD;
|
||||
|
||||
const promptResult = standardMode
|
||||
? await _askTrackMeasurements(uiViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.HYDRATE_REPORT;
|
||||
|
||||
// Need to do action here... So we can set state...
|
||||
let StudyInstanceUID, SeriesInstanceUIDs;
|
||||
@@ -49,9 +53,9 @@ function promptHydrateStructuredReport(
|
||||
});
|
||||
}
|
||||
|
||||
function _askTrackMeasurements(uiViewportDialogService, viewportId) {
|
||||
function _askTrackMeasurements(uiViewportDialogService, customizationService, viewportId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = 'Do you want to continue tracking measurements for this study?';
|
||||
const message = customizationService.getCustomization('viewportNotification.hydrateSRMessage');
|
||||
const actions = [
|
||||
{
|
||||
id: 'no-hydrate',
|
||||
|
||||
+18
-8
@@ -1,4 +1,5 @@
|
||||
import { ButtonEnums } from '@ohif/ui';
|
||||
import { measurementTrackingMode } from './promptBeginTracking';
|
||||
|
||||
const RESPONSE = {
|
||||
NO_NEVER: -1,
|
||||
@@ -10,17 +11,22 @@ const RESPONSE = {
|
||||
};
|
||||
|
||||
function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { UIViewportDialogService } = servicesManager.services;
|
||||
const { UIViewportDialogService, customizationService } = servicesManager.services;
|
||||
// When the state change happens after a promise, the state machine sends the retult in evt.data;
|
||||
// In case of direct transition to the state, the state machine sends the data in evt;
|
||||
const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
let promptResult = await _askShouldAddMeasurements(UIViewportDialogService, viewportId);
|
||||
const appConfig = extensionManager._appConfig;
|
||||
|
||||
const showPrompt = appConfig?.measurementTrackingMode === measurementTrackingMode.STANDARD;
|
||||
let promptResult = showPrompt
|
||||
? await _askShouldAddMeasurements(UIViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.ADD_SERIES;
|
||||
|
||||
if (promptResult === RESPONSE.CREATE_REPORT) {
|
||||
promptResult = ctx.isDirty
|
||||
? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportId)
|
||||
? await _askSaveDiscardOrCancel(UIViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.SET_STUDY_AND_SERIES;
|
||||
}
|
||||
|
||||
@@ -34,9 +40,11 @@ function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) {
|
||||
});
|
||||
}
|
||||
|
||||
function _askShouldAddMeasurements(uiViewportDialogService, viewportId) {
|
||||
function _askShouldAddMeasurements(uiViewportDialogService, customizationService, viewportId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = 'Do you want to add this measurement to the existing report?';
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.trackNewSeriesMessage'
|
||||
);
|
||||
const actions = [
|
||||
{
|
||||
type: ButtonEnums.type.secondary,
|
||||
@@ -73,10 +81,12 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportId) {
|
||||
});
|
||||
}
|
||||
|
||||
function _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) {
|
||||
function _askSaveDiscardOrCancel(UIViewportDialogService, customizationService, viewportId) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message =
|
||||
'You have existing tracked measurements. What would you like to do with your existing tracked measurements?';
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.discardSeriesMessage'
|
||||
);
|
||||
|
||||
const actions = [
|
||||
{ type: 'cancel', text: 'Cancel', value: RESPONSE.CANCEL },
|
||||
{
|
||||
|
||||
+22
-8
@@ -1,4 +1,5 @@
|
||||
import i18n from 'i18next';
|
||||
import { measurementTrackingMode } from './promptBeginTracking';
|
||||
|
||||
const RESPONSE = {
|
||||
NO_NEVER: -1,
|
||||
@@ -10,18 +11,26 @@ const RESPONSE = {
|
||||
};
|
||||
|
||||
function promptTrackNewStudy({ servicesManager, extensionManager }: withAppTypes, ctx, evt) {
|
||||
const { uiViewportDialogService } = servicesManager.services;
|
||||
const { uiViewportDialogService, customizationService } = servicesManager.services;
|
||||
// When the state change happens after a promise, the state machine sends the retult in evt.data;
|
||||
// In case of direct transition to the state, the state machine sends the data in evt;
|
||||
const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt;
|
||||
|
||||
return new Promise(async function (resolve, reject) {
|
||||
let promptResult = await _askTrackMeasurements(uiViewportDialogService, viewportId);
|
||||
const appConfig = extensionManager._appConfig;
|
||||
|
||||
const standardMode = appConfig?.measurementTrackingMode === measurementTrackingMode.STANDARD;
|
||||
const simplifiedMode =
|
||||
appConfig?.measurementTrackingMode === measurementTrackingMode.SIMPLIFIED;
|
||||
let promptResult = standardMode
|
||||
? await _askTrackMeasurements(uiViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.SET_STUDY_AND_SERIES;
|
||||
|
||||
if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) {
|
||||
promptResult = ctx.isDirty
|
||||
? await _askSaveDiscardOrCancel(uiViewportDialogService, viewportId)
|
||||
: RESPONSE.SET_STUDY_AND_SERIES;
|
||||
promptResult =
|
||||
ctx.isDirty && (standardMode || simplifiedMode)
|
||||
? await _askSaveDiscardOrCancel(uiViewportDialogService, customizationService, viewportId)
|
||||
: RESPONSE.SET_STUDY_AND_SERIES;
|
||||
}
|
||||
|
||||
resolve({
|
||||
@@ -36,10 +45,13 @@ function promptTrackNewStudy({ servicesManager, extensionManager }: withAppTypes
|
||||
|
||||
function _askTrackMeasurements(
|
||||
UIViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message = i18n.t('MeasurementTable:Track measurements for this series?');
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.trackNewStudyMessage'
|
||||
);
|
||||
const actions = [
|
||||
{ type: 'cancel', text: i18n.t('MeasurementTable:No'), value: RESPONSE.CANCEL },
|
||||
{
|
||||
@@ -80,11 +92,13 @@ function _askTrackMeasurements(
|
||||
|
||||
function _askSaveDiscardOrCancel(
|
||||
UIViewportDialogService: AppTypes.UIViewportDialogService,
|
||||
customizationService: AppTypes.CustomizationService,
|
||||
viewportId
|
||||
) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const message =
|
||||
'Measurements cannot span across multiple studies. Do you want to save your tracked measurements?';
|
||||
const message = customizationService.getCustomization(
|
||||
'viewportNotification.discardStudyMessage'
|
||||
);
|
||||
const actions = [
|
||||
{ type: 'cancel', text: 'Cancel', value: RESPONSE.CANCEL },
|
||||
{
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
const promptBeginTrackingWrapper = ({ servicesManager, extensionManager }, ctx, evt) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptBeginTracking = customizationService.getCustomization(
|
||||
'measurement.promptBeginTracking'
|
||||
);
|
||||
return promptBeginTracking({ servicesManager, extensionManager }, ctx, evt);
|
||||
};
|
||||
|
||||
const promptHydrateStructuredReportWrapper = (
|
||||
{ servicesManager, extensionManager, commandsManager, appConfig },
|
||||
ctx,
|
||||
evt
|
||||
) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptHydrateStructuredReport = customizationService.getCustomization(
|
||||
'measurement.promptHydrateStructuredReport'
|
||||
);
|
||||
return promptHydrateStructuredReport(
|
||||
{ servicesManager, extensionManager, commandsManager, appConfig },
|
||||
ctx,
|
||||
evt
|
||||
);
|
||||
};
|
||||
|
||||
const promptTrackNewSeriesWrapper = ({ servicesManager, extensionManager }, ctx, evt) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptTrackNewSeries = customizationService.getCustomization(
|
||||
'measurement.promptTrackNewSeries'
|
||||
);
|
||||
return promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt);
|
||||
};
|
||||
|
||||
const promptTrackNewStudyWrapper = ({ servicesManager, extensionManager }, ctx, evt) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptTrackNewStudy = customizationService.getCustomization(
|
||||
'measurement.promptTrackNewStudy'
|
||||
);
|
||||
return promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt);
|
||||
};
|
||||
|
||||
const promptLabelAnnotationWrapper = ({ servicesManager }, ctx, evt) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptLabelAnnotation = customizationService.getCustomization(
|
||||
'measurement.promptLabelAnnotation'
|
||||
);
|
||||
return promptLabelAnnotation({ servicesManager }, ctx, evt);
|
||||
};
|
||||
|
||||
const promptSaveReportWrapper = (
|
||||
{ servicesManager, commandsManager, extensionManager },
|
||||
ctx,
|
||||
evt
|
||||
) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptSaveReport = customizationService.getCustomization('measurement.promptSaveReport');
|
||||
return promptSaveReport({ servicesManager, commandsManager, extensionManager }, ctx, evt);
|
||||
};
|
||||
|
||||
const promptHasDirtyAnnotationsWrapper = (
|
||||
{ servicesManager, commandsManager, extensionManager },
|
||||
ctx,
|
||||
evt
|
||||
) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
const promptHasDirtyAnnotations = customizationService.getCustomization(
|
||||
'measurement.promptHasDirtyAnnotations'
|
||||
);
|
||||
return promptHasDirtyAnnotations(
|
||||
{ servicesManager, commandsManager, extensionManager },
|
||||
ctx,
|
||||
evt
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
promptBeginTrackingWrapper,
|
||||
promptHydrateStructuredReportWrapper,
|
||||
promptTrackNewSeriesWrapper,
|
||||
promptTrackNewStudyWrapper,
|
||||
promptLabelAnnotationWrapper,
|
||||
promptSaveReportWrapper,
|
||||
promptHasDirtyAnnotationsWrapper,
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import promptBeginTracking from '../contexts/TrackedMeasurementsContext/promptBeginTracking';
|
||||
import promptHasDirtyAnnotations from '../contexts/TrackedMeasurementsContext/promptHasDirtyAnnotations';
|
||||
import promptHydrateStructuredReport from '../contexts/TrackedMeasurementsContext/promptHydrateStructuredReport';
|
||||
import promptTrackNewSeries from '../contexts/TrackedMeasurementsContext/promptTrackNewSeries';
|
||||
import promptTrackNewStudy from '../contexts/TrackedMeasurementsContext/promptTrackNewStudy';
|
||||
import { promptLabelAnnotation, promptSaveReport } from '@ohif/extension-default';
|
||||
|
||||
export default {
|
||||
'measurement.promptBeginTracking': promptBeginTracking,
|
||||
'measurement.promptHydrateStructuredReport': promptHydrateStructuredReport,
|
||||
'measurement.promptTrackNewSeries': promptTrackNewSeries,
|
||||
'measurement.promptTrackNewStudy': promptTrackNewStudy,
|
||||
'measurement.promptLabelAnnotation': promptLabelAnnotation,
|
||||
'measurement.promptSaveReport': promptSaveReport,
|
||||
'measurement.promptHasDirtyAnnotations': promptHasDirtyAnnotations,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { measurementTrackingMode } from '../contexts/TrackedMeasurementsContext/promptBeginTracking';
|
||||
|
||||
type CheckHasDirtyAndSimplifiedModeProps = {
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
appConfig: AppTypes.Config;
|
||||
displaySetInstanceUID: string;
|
||||
};
|
||||
|
||||
const onDoubleClickHandler = {
|
||||
callbacks: [
|
||||
({ activeViewportId, servicesManager, isHangingProtocolLayout, appConfig }) =>
|
||||
async displaySetInstanceUID => {
|
||||
const { hangingProtocolService, viewportGridService, uiNotificationService } =
|
||||
servicesManager.services;
|
||||
let updatedViewports = [];
|
||||
const viewportId = activeViewportId;
|
||||
const haveDirtyMeasurementsInSimplifiedMode = checkHasDirtyAndSimplifiedMode({
|
||||
servicesManager,
|
||||
appConfig,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
|
||||
try {
|
||||
if (!haveDirtyMeasurementsInSimplifiedMode) {
|
||||
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
isHangingProtocolLayout
|
||||
);
|
||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
uiNotificationService.show({
|
||||
title: 'Thumbnail Double Click',
|
||||
message: 'The selected display sets could not be added to the viewport.',
|
||||
type: 'error',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const customOnDropHandlerCallback = async props => {
|
||||
const handled = checkHasDirtyAndSimplifiedMode(props);
|
||||
return Promise.resolve({ handled });
|
||||
};
|
||||
|
||||
const checkHasDirtyAndSimplifiedMode = (props: CheckHasDirtyAndSimplifiedModeProps) => {
|
||||
const { servicesManager, appConfig, displaySetInstanceUID } = props;
|
||||
const simplifiedMode = appConfig.measurementTrackingMode === measurementTrackingMode.SIMPLIFIED;
|
||||
const { measurementService, displaySetService } = servicesManager.services;
|
||||
const measurements = measurementService.getMeasurements();
|
||||
const haveDirtyMeasurements =
|
||||
measurements.some(m => m.isDirty) ||
|
||||
(measurements.length && measurementService.getIsMeasurementDeletedIndividually());
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
const hasDirtyAndSimplifiedMode =
|
||||
displaySet.Modality === 'SR' && simplifiedMode && haveDirtyMeasurements;
|
||||
return hasDirtyAndSimplifiedMode;
|
||||
};
|
||||
|
||||
export { onDoubleClickHandler, customOnDropHandlerCallback };
|
||||
@@ -0,0 +1,10 @@
|
||||
import measurementTrackingPrompts from './customizations/measurementTrackingPrompts';
|
||||
|
||||
export default function getCustomizationModule() {
|
||||
return [
|
||||
{
|
||||
name: 'default',
|
||||
value: { ...measurementTrackingPrompts },
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -6,6 +6,12 @@ import getViewportModule from './getViewportModule';
|
||||
import { id } from './id.js';
|
||||
import { ViewportActionButton } from '@ohif/ui';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { measurementTrackingMode } from './contexts/TrackedMeasurementsContext/promptBeginTracking';
|
||||
import getCustomizationModule from './getCustomizationModule';
|
||||
import {
|
||||
onDoubleClickHandler,
|
||||
customOnDropHandlerCallback,
|
||||
} from './customizations/studyBrowserCustomization';
|
||||
|
||||
const measurementTrackingExtension = {
|
||||
/**
|
||||
@@ -18,8 +24,15 @@ const measurementTrackingExtension = {
|
||||
getViewportModule,
|
||||
|
||||
onModeEnter({ servicesManager }) {
|
||||
const { toolbarService } = servicesManager.services;
|
||||
|
||||
const { toolbarService, customizationService } = servicesManager.services;
|
||||
customizationService.setCustomizations({
|
||||
'studyBrowser.thumbnailDoubleClickCallback': {
|
||||
$set: onDoubleClickHandler,
|
||||
},
|
||||
customOnDropHandler: {
|
||||
$set: customOnDropHandlerCallback,
|
||||
},
|
||||
});
|
||||
toolbarService.addButtons(
|
||||
[
|
||||
{
|
||||
@@ -38,6 +51,9 @@ const measurementTrackingExtension = {
|
||||
true // replace the button if it is already defined
|
||||
);
|
||||
},
|
||||
getCustomizationModule,
|
||||
};
|
||||
|
||||
export default measurementTrackingExtension;
|
||||
|
||||
export { measurementTrackingMode };
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Dialog, ButtonEnums } from '@ohif/ui';
|
||||
import { utils } from '@ohif/core';
|
||||
import { AccordionTrigger, MeasurementTable, useViewportGrid } from '@ohif/ui-next';
|
||||
import {
|
||||
@@ -11,18 +12,40 @@ import {
|
||||
} from '@ohif/extension-cornerstone';
|
||||
|
||||
import { useTrackedMeasurements } from '../getContextModule';
|
||||
import { UntrackSeriesModal } from './PanelStudyBrowserTracking/untrackSeriesModal';
|
||||
|
||||
const { filterAnd, filterPlanarMeasurement, filterMeasurementsBySeriesUID } =
|
||||
utils.MeasurementFilters;
|
||||
|
||||
function PanelMeasurementTableTracking(props) {
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
const { measurementService, uiModalService } = props.servicesManager.services;
|
||||
const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements();
|
||||
const { trackedStudy, trackedSeries } = trackedMeasurements.context;
|
||||
const measurementFilter = trackedStudy
|
||||
? filterAnd(filterPlanarMeasurement, filterMeasurementsBySeriesUID(trackedSeries))
|
||||
: filterPlanarMeasurement;
|
||||
|
||||
const onUntrackConfirm = () => {
|
||||
sendTrackedMeasurementsEvent('UNTRACK_ALL', {});
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
const hasDirtyMeasurements = measurementService
|
||||
.getMeasurements()
|
||||
.some(measurement => measurement.isDirty);
|
||||
hasDirtyMeasurements
|
||||
? uiModalService.show({
|
||||
title: 'Untrack Study',
|
||||
content: UntrackSeriesModal,
|
||||
contentProps: {
|
||||
onConfirm: onUntrackConfirm,
|
||||
message: 'Are you sure you want to untrack study and delete all measurements?',
|
||||
},
|
||||
})
|
||||
: onUntrackConfirm();
|
||||
};
|
||||
|
||||
const EmptyComponent = () => (
|
||||
<div data-cy="trackedMeasurements-panel">
|
||||
<MeasurementTable
|
||||
@@ -43,6 +66,7 @@ function PanelMeasurementTableTracking(props) {
|
||||
measurementFilter,
|
||||
});
|
||||
},
|
||||
onDelete,
|
||||
};
|
||||
|
||||
const Header = props => (
|
||||
@@ -51,7 +75,10 @@ function PanelMeasurementTableTracking(props) {
|
||||
className="px-0"
|
||||
>
|
||||
<div data-cy="TrackingHeader">
|
||||
<StudySummaryFromMetadata {...props} />
|
||||
<StudySummaryFromMetadata
|
||||
{...props}
|
||||
actions={actions}
|
||||
/>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
);
|
||||
@@ -68,7 +95,7 @@ function PanelMeasurementTableTracking(props) {
|
||||
key="trackingMeasurementsHeader"
|
||||
asChild={true}
|
||||
>
|
||||
<Header key="trackingHeadChild" />
|
||||
<Header key="trackingHeadChild" />
|
||||
</AccordionGroup.Trigger>
|
||||
<MeasurementsOrAdditionalFindings
|
||||
key="measurementsOrAdditionalFindings"
|
||||
|
||||
+29
-2
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import PanelStudyBrowser from '@ohif/extension-default/src/Panels/StudyBrowser/PanelStudyBrowser';
|
||||
import { UntrackSeriesModal } from './untrackSeriesModal';
|
||||
import { useTrackedMeasurements } from '../../getContextModule';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
|
||||
const thumbnailNoImageModalities = [
|
||||
'SR',
|
||||
@@ -27,10 +28,34 @@ export default function PanelStudyBrowserTracking({
|
||||
dataSource,
|
||||
}) {
|
||||
const { servicesManager } = useSystem();
|
||||
const { displaySetService, uiModalService, measurementService } = servicesManager.services;
|
||||
const { displaySetService, uiModalService, measurementService, viewportGridService } =
|
||||
servicesManager.services;
|
||||
const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements();
|
||||
const { trackedSeries } = trackedMeasurements.context;
|
||||
|
||||
const checkDirtyMeasurements = displaySetInstanceUID => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (displaySet.Modality === 'SR') {
|
||||
const activeViewportId = viewportGridService.getActiveViewportId();
|
||||
sendTrackedMeasurementsEvent('CHECK_DIRTY', {
|
||||
viewportId: activeViewportId,
|
||||
displaySetInstanceUID: displaySetInstanceUID,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptionOndropFired = viewportGridService.subscribe(
|
||||
viewportGridService.EVENTS.VIEWPORT_ONDROP_HANDLED,
|
||||
({ eventData }) => {
|
||||
checkDirtyMeasurements(eventData.displaySetInstanceUID);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
subscriptionOndropFired.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
const onClickUntrack = displaySetInstanceUID => {
|
||||
const onConfirm = () => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
@@ -50,6 +75,7 @@ export default function PanelStudyBrowserTracking({
|
||||
content: UntrackSeriesModal,
|
||||
contentProps: {
|
||||
onConfirm,
|
||||
message: 'Are you sure you want to untrack this series?',
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -115,6 +141,7 @@ export default function PanelStudyBrowserTracking({
|
||||
dataSource={dataSource}
|
||||
customMapDisplaySets={mapDisplaySetsWithTracking}
|
||||
onClickUntrack={onClickUntrack}
|
||||
onDoubleClickThumbnailHandlerCallBack={checkDirtyMeasurements}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { FooterAction } from '@ohif/ui-next';
|
||||
|
||||
export function UntrackSeriesModal({ hide, onConfirm }) {
|
||||
export function UntrackSeriesModal({ hide, onConfirm, message }) {
|
||||
return (
|
||||
<div className="text-foreground">
|
||||
<div>
|
||||
<p>Are you sure you want to untrack this series?</p>
|
||||
<p>{message}</p>
|
||||
<p className="mt-2">
|
||||
This action cannot be undone and will delete all your existing measurements.
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user