[OHIF-193 + OHIF-313] (#1955)
* create context menu * OHIF-193: Add context menu integration with measurement service * OHIF-193: Update comments * OHIF-CR Update: Add requested updates * CR Update: Update measurement service remove function * CR Update: Update casing * CR Updates * Update cornerstone measurments when the label is edited in the measurement service. * Implement click outside behavior * Address reviewer comments. Co-authored-by: Rodrigo Antinarelli <rodrigoantinarelli@gmail.com> Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
This commit is contained in:
20 files changed
+575
-196
No files matched your search
@@ -30,8 +30,8 @@ export default {
|
||||
* @param {object} [configuration={}]
|
||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
preRegistration({ servicesManager, commandsManager, configuration = {} }) {
|
||||
init({ servicesManager, commandsManager, configuration });
|
||||
},
|
||||
getViewportModule({ commandsManager }) {
|
||||
const ExtendedOHIFCornerstoneViewport = props => {
|
||||
|
||||
@@ -1,32 +1,239 @@
|
||||
import React from 'react';
|
||||
import OHIF from '@ohif/core';
|
||||
import { Dialog, Input } from '@ohif/ui';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { Input, Dialog, ContextMenuMeasurements } from '@ohif/ui';
|
||||
import cs 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 './initWADOImageLoader.js';
|
||||
import getCornerstoneMeasurementById from './utils/getCornerstoneMeasurementById';
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
//
|
||||
import { setEnabledElement } from './state';
|
||||
|
||||
// TODO -> Global "context menu open state", or lots of expensive searches on drag?
|
||||
|
||||
let CONTEXT_MENU_OPEN = false;
|
||||
|
||||
const { globalImageIdSpecificToolStateManager } = csTools;
|
||||
|
||||
const TOOL_TYPES_WITH_CONTEXT_MENU = [
|
||||
'Angle',
|
||||
'ArrowAnnotate',
|
||||
'Bidirectional',
|
||||
'Length',
|
||||
'FreehandMouse',
|
||||
'EllipticalRoi',
|
||||
'CircleRoi',
|
||||
'RectangleRoi',
|
||||
];
|
||||
|
||||
const _refreshViewports = () =>
|
||||
cs.getEnabledElements().forEach(({ element }) => cs.updateImage(element));
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} servicesManager
|
||||
* @param {Object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ servicesManager, configuration }) {
|
||||
export default function init({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
configuration,
|
||||
}) {
|
||||
const {
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
/* Measurement Service */
|
||||
const measurementServiceSource = _connectToolsToMeasurementService(
|
||||
MeasurementService,
|
||||
DisplaySetService
|
||||
);
|
||||
|
||||
const onRightClick = event => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
const onGetMenuItems = defaultMenuItems => {
|
||||
const { element, currentPoints } = event.detail;
|
||||
const nearbyToolData = commandsManager.runCommand('getNearbyToolData', {
|
||||
element,
|
||||
canvasCoordinates: currentPoints.canvas,
|
||||
availableToolTypes: TOOL_TYPES_WITH_CONTEXT_MENU,
|
||||
});
|
||||
|
||||
let menuItems = [];
|
||||
if (nearbyToolData) {
|
||||
defaultMenuItems.forEach(item => {
|
||||
item.value = nearbyToolData;
|
||||
menuItems.push(item);
|
||||
});
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
};
|
||||
|
||||
CONTEXT_MENU_OPEN = true;
|
||||
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
UIDialogService.create({
|
||||
id: 'context-menu',
|
||||
isDraggable: false,
|
||||
preservePosition: false,
|
||||
defaultPosition: _getDefaultPosition(event.detail),
|
||||
content: ContextMenuMeasurements,
|
||||
onClickOutside: () => {
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
CONTEXT_MENU_OPEN = false;
|
||||
},
|
||||
contentProps: {
|
||||
onGetMenuItems,
|
||||
eventData: event.detail,
|
||||
onDelete: item => {
|
||||
const { tool: measurementData, toolType } = item.value;
|
||||
measurementServiceSource.remove(measurementData.id);
|
||||
_refreshViewports();
|
||||
CONTEXT_MENU_OPEN = false;
|
||||
},
|
||||
onClose: () => {
|
||||
CONTEXT_MENU_OPEN = false;
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
},
|
||||
onSetLabel: item => {
|
||||
const { tool: measurementData } = item.value;
|
||||
|
||||
const measurement = MeasurementService.getMeasurement(
|
||||
measurementData.id
|
||||
);
|
||||
|
||||
callInputDialog(
|
||||
measurement,
|
||||
(label, actionId) => {
|
||||
if (actionId === 'cancel') {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedMeasurement = Object.assign({}, measurement, {
|
||||
label,
|
||||
});
|
||||
|
||||
MeasurementService.update(
|
||||
updatedMeasurement.id,
|
||||
updatedMeasurement,
|
||||
true
|
||||
);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
CONTEXT_MENU_OPEN = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTouchPress = event => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
UIDialogService.create({
|
||||
eventData: event.detail,
|
||||
content: ContextMenuMeasurements,
|
||||
contentProps: { isTouchEvent: true },
|
||||
});
|
||||
};
|
||||
|
||||
const resetContextMenu = () => {
|
||||
if (!UIDialogService) {
|
||||
console.warn('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
CONTEXT_MENU_OPEN = false;
|
||||
|
||||
UIDialogService.dismiss({ id: 'context-menu' });
|
||||
};
|
||||
|
||||
/*
|
||||
* Because click gives us the native "mouse up", buttons will always be `0`
|
||||
* Need to fallback to event.which;
|
||||
*
|
||||
*/
|
||||
const contextMenuHandleClick = evt => {
|
||||
const mouseUpEvent = evt.detail.event;
|
||||
const isRightClick = mouseUpEvent.which === 3;
|
||||
|
||||
const clickMethodHandler = isRightClick ? onRightClick : resetContextMenu;
|
||||
clickMethodHandler(evt);
|
||||
};
|
||||
|
||||
const cancelContextMenuIfOpen = evt => {
|
||||
if (CONTEXT_MENU_OPEN) {
|
||||
resetContextMenu();
|
||||
}
|
||||
};
|
||||
|
||||
function elementEnabledHandler(evt) {
|
||||
const element = evt.detail.element;
|
||||
element.addEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.addEventListener(
|
||||
csTools.EVENTS.MOUSE_CLICK,
|
||||
contextMenuHandleClick
|
||||
);
|
||||
element.addEventListener(cs.EVENTS.NEW_IMAGE, cancelContextMenuIfOpen);
|
||||
}
|
||||
|
||||
function elementDisabledHandler(evt) {
|
||||
const element = evt.detail.element;
|
||||
element.removeEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress);
|
||||
element.removeEventListener(
|
||||
csTools.EVENTS.MOUSE_CLICK,
|
||||
contextMenuHandleClick
|
||||
);
|
||||
element.removeEventListener(cs.EVENTS.NEW_IMAGE, cancelContextMenuIfOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} data
|
||||
* @param {*} event
|
||||
* @param {*} callback
|
||||
* @param {*} isArrowAnnotateInputDialog
|
||||
*/
|
||||
const callInputDialog = (
|
||||
data,
|
||||
callback,
|
||||
isArrowAnnotateInputDialog = true
|
||||
) => {
|
||||
const dialogId = 'enter-annotation';
|
||||
const label = data
|
||||
? isArrowAnnotateInputDialog
|
||||
? data.text
|
||||
: data.label
|
||||
: '';
|
||||
|
||||
const onSubmitHandler = ({ action, value }) => {
|
||||
switch (action.id) {
|
||||
case 'save':
|
||||
callback(value.label, action.id);
|
||||
break;
|
||||
case 'cancel':
|
||||
callback('', action.id);
|
||||
break;
|
||||
}
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
};
|
||||
|
||||
if (UIDialogService) {
|
||||
let dialogId = UIDialogService.create({
|
||||
UIDialogService.create({
|
||||
id: dialogId,
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: Dialog,
|
||||
@@ -34,34 +241,26 @@ export default function init({ servicesManager, configuration }) {
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
value: { label: data ? data.text : '' },
|
||||
value: { label },
|
||||
noCloseButton: true,
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
actions: [
|
||||
{ id: 'cancel', text: 'Cancel', type: 'secondary' },
|
||||
{ id: 'save', text: 'Save', type: 'primary' },
|
||||
],
|
||||
onSubmit: ({ action, value }) => {
|
||||
switch (action.id) {
|
||||
case 'save':
|
||||
callback(value.label);
|
||||
break;
|
||||
case 'cancel':
|
||||
callback();
|
||||
break;
|
||||
}
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
},
|
||||
onSubmit: onSubmitHandler,
|
||||
body: ({ value, setValue }) => {
|
||||
const onChangeHandler = event => {
|
||||
event.persist();
|
||||
setValue(value => ({ ...value, label: event.target.value }));
|
||||
};
|
||||
|
||||
const onKeyPressHandler = event => {
|
||||
if (event.key === 'Enter') {
|
||||
onSubmitHandler({ value, action: { id: 'save' } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-primary-dark">
|
||||
<Input
|
||||
@@ -84,10 +283,7 @@ export default function init({ servicesManager, configuration }) {
|
||||
const { csToolsConfig } = configuration;
|
||||
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.get.bind(metadataProvider),
|
||||
9999
|
||||
);
|
||||
cs.metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999);
|
||||
|
||||
// ~~
|
||||
const defaultCsToolsConfig = csToolsConfig || {
|
||||
@@ -143,17 +339,14 @@ export default function init({ servicesManager, configuration }) {
|
||||
tools.push(...toolsGroupedByType[toolsGroup])
|
||||
);
|
||||
|
||||
/* Measurement Service */
|
||||
_connectToolsToMeasurementService(MeasurementService, DisplaySetService);
|
||||
|
||||
/* Add extension tools configuration here. */
|
||||
const internalToolsConfig = {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
callInputDialog(null, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
callInputDialog(data, callback),
|
||||
allowEmptyLabel: true,
|
||||
},
|
||||
},
|
||||
@@ -229,6 +422,12 @@ export default function init({ servicesManager, configuration }) {
|
||||
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
||||
csTools.setToolActive('ZoomTouchPinch', {});
|
||||
csTools.setToolEnabled('Overlay', {});
|
||||
|
||||
cs.events.addEventListener(cs.EVENTS.ELEMENT_ENABLED, elementEnabledHandler);
|
||||
cs.events.addEventListener(
|
||||
cs.EVENTS.ELEMENT_DISABLED,
|
||||
elementDisabledHandler
|
||||
);
|
||||
}
|
||||
|
||||
const _initMeasurementService = (MeasurementService, DisplaySetService) => {
|
||||
@@ -293,12 +492,11 @@ const _connectToolsToMeasurementService = (
|
||||
csToolsVer4MeasurementSource
|
||||
);
|
||||
const { addOrUpdate, remove } = csToolsVer4MeasurementSource;
|
||||
const elementEnabledEvt = cornerstone.EVENTS.ELEMENT_ENABLED;
|
||||
const elementEnabledEvt = cs.EVENTS.ELEMENT_ENABLED;
|
||||
|
||||
/* Measurement Service Events */
|
||||
cornerstone.events.addEventListener(elementEnabledEvt, evt => {
|
||||
cs.events.addEventListener(elementEnabledEvt, evt => {
|
||||
// TODO: Debounced update of measurements that are modified
|
||||
|
||||
function addMeasurement(csToolsEvent) {
|
||||
console.log('CSTOOLS::addOrUpdate', csToolsEvent, csToolsEvent.detail);
|
||||
|
||||
@@ -322,13 +520,13 @@ const _connectToolsToMeasurementService = (
|
||||
if (!csToolsEvent.detail.measurementData.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const evtDetail = csToolsEvent.detail;
|
||||
const { toolName, toolType, measurementData } = evtDetail;
|
||||
const csToolName = toolName || measurementData.toolType || toolType;
|
||||
|
||||
evtDetail.id = csToolsEvent.detail.measurementData.id;
|
||||
addOrUpdate(csToolName, evtDetail);
|
||||
//
|
||||
} catch (error) {
|
||||
console.warn('Failed to update measurement:', error);
|
||||
}
|
||||
@@ -351,14 +549,42 @@ const _connectToolsToMeasurementService = (
|
||||
}
|
||||
}
|
||||
|
||||
const { MEASUREMENTS_CLEARED } = MeasurementService.EVENTS;
|
||||
const {
|
||||
MEASUREMENTS_CLEARED,
|
||||
MEASUREMENT_UPDATED,
|
||||
} = MeasurementService.EVENTS;
|
||||
|
||||
MeasurementService.subscribe(MEASUREMENTS_CLEARED, () => {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
{}
|
||||
);
|
||||
globalImageIdSpecificToolStateManager.restoreToolState({});
|
||||
_refreshViewports();
|
||||
});
|
||||
|
||||
MeasurementService.subscribe(
|
||||
MEASUREMENT_UPDATED,
|
||||
({ source, measurement, notYetUpdatedAtSource }) => {
|
||||
const { id, label } = measurement;
|
||||
|
||||
if (
|
||||
source.name == 'CornerstoneTools' &&
|
||||
notYetUpdatedAtSource === false
|
||||
) {
|
||||
// This event was fired by cornerstone telling the measurement service to sync. Already in sync.
|
||||
return;
|
||||
}
|
||||
const cornerstoneMeasurement = getCornerstoneMeasurementById(id);
|
||||
|
||||
if (cornerstoneMeasurement) {
|
||||
cornerstoneMeasurement.label = label;
|
||||
if (cornerstoneMeasurement.hasOwnProperty('text')) {
|
||||
// Deal with the weird case of ArrowAnnotate.
|
||||
cornerstoneMeasurement.text = label;
|
||||
}
|
||||
|
||||
_refreshViewports();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const enabledElement = evt.detail.element;
|
||||
const completedEvt = csTools.EVENTS.MEASUREMENT_COMPLETED;
|
||||
const updatedEvt = csTools.EVENTS.MEASUREMENT_MODIFIED;
|
||||
@@ -368,25 +594,17 @@ const _connectToolsToMeasurementService = (
|
||||
enabledElement.addEventListener(updatedEvt, updateMeasurement);
|
||||
enabledElement.addEventListener(removedEvt, removeMeasurement);
|
||||
});
|
||||
|
||||
return csToolsVer4MeasurementSource;
|
||||
};
|
||||
|
||||
const _connectMeasurementServiceToTools = (
|
||||
MeasurementService,
|
||||
measurementSource
|
||||
) => {
|
||||
const {
|
||||
MEASUREMENTS_CLEARED,
|
||||
MEASUREMENT_REMOVED,
|
||||
} = MeasurementService.EVENTS;
|
||||
const { MEASUREMENT_REMOVED } = MeasurementService.EVENTS;
|
||||
const sourceId = measurementSource.id;
|
||||
|
||||
MeasurementService.subscribe(MEASUREMENTS_CLEARED, () => {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState({});
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: This is an unsafe delete
|
||||
// Cornerstone-tools should probably expose a more generic "delete by id"
|
||||
// And have toolState managers expose a method to find any of their toolState by ID
|
||||
@@ -401,7 +619,7 @@ const _connectMeasurementServiceToTools = (
|
||||
MEASUREMENT_REMOVED,
|
||||
({ source, measurement: removedMeasurementId }) => {
|
||||
// THIS POINTS TO ORIGINAL; Not a copy
|
||||
const imageIdSpecificToolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
const imageIdSpecificToolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
// ImageId -->
|
||||
Object.keys(imageIdSpecificToolState).forEach(imageId => {
|
||||
@@ -425,37 +643,7 @@ const _connectMeasurementServiceToTools = (
|
||||
);
|
||||
};
|
||||
|
||||
// const {
|
||||
// MEASUREMENT_ADDED,
|
||||
// MEASUREMENT_UPDATED,
|
||||
// } = MeasurementService.EVENTS;
|
||||
|
||||
// MeasurementService.subscribe(
|
||||
// MEASUREMENT_ADDED,
|
||||
// ({ source, measurement }) => {
|
||||
// if (![sourceId].includes(source.id)) {
|
||||
// const annotation = getAnnotation('Length', measurement.id);
|
||||
|
||||
// console.log(
|
||||
// 'Measurement Service [Cornerstone]: Measurement added',
|
||||
// measurement
|
||||
// );
|
||||
// console.log('Mapped annotation:', annotation);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// MeasurementService.subscribe(
|
||||
// MEASUREMENT_UPDATED,
|
||||
// ({ source, measurement }) => {
|
||||
// if (![sourceId].includes(source.id)) {
|
||||
// const annotation = getAnnotation('Length', measurement.id);
|
||||
|
||||
// console.log(
|
||||
// 'Measurement Service [Cornerstone]: Measurement updated',
|
||||
// measurement
|
||||
// );
|
||||
// console.log('Mapped annotation:', annotation);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
const _getDefaultPosition = event => ({
|
||||
x: (event && event.currentPoints.client.x) || 0,
|
||||
y: (event && event.currentPoints.client.y) || 0,
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { globalImageIdSpecificToolStateManager } = cornerstoneTools;
|
||||
|
||||
export default function getCornerstoneMeasurementById(id) {
|
||||
const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
const imageIds = Object.keys(globalToolState);
|
||||
|
||||
for (let i = 0; i < imageIds.length; i++) {
|
||||
const imageId = imageIds[i];
|
||||
const imageIdSpecificToolState = globalToolState[imageId];
|
||||
|
||||
const toolTypes = Object.keys(imageIdSpecificToolState);
|
||||
|
||||
for (let j = 0; j < toolTypes.length; j++) {
|
||||
const toolType = toolTypes[j];
|
||||
const toolData = imageIdSpecificToolState[toolType].data;
|
||||
|
||||
if (toolData) {
|
||||
for (let k = 0; k < toolData.length; k++) {
|
||||
const toolDataK = toolData[k];
|
||||
|
||||
if (toolDataK.id === id) {
|
||||
return toolDataK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||
import getPointsFromHandles from './utils/getPointsFromHandles';
|
||||
import getHandlesFromPoints from './utils/getHandlesFromPoints';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
|
||||
const ArrowAnnotate = {
|
||||
toAnnotation: (measurement, definition) => {
|
||||
// TODO -> Implement when this is needed.
|
||||
},
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
toMeasurement: (
|
||||
csToolsAnnotation,
|
||||
DisplaySetService,
|
||||
@@ -40,7 +39,7 @@ const ArrowAnnotate = {
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
import getHandlesFromPoints from './utils/getHandlesFromPoints';
|
||||
|
||||
const Bidirectional = {
|
||||
toAnnotation: (measurement, definition) => {
|
||||
// TODO -> Implement when this is needed.
|
||||
},
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
toMeasurement: (
|
||||
csToolsAnnotation,
|
||||
DisplaySetService,
|
||||
@@ -41,12 +40,12 @@ const Bidirectional = {
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
label: measurementData.text,
|
||||
label: measurementData.label,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
shortestDiameter: measurementData.shortestDiameter,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
import getHandlesFromPoints from './utils/getHandlesFromPoints';
|
||||
|
||||
const EllipticalRoi = {
|
||||
toAnnotation: (measurement, definition) => {
|
||||
// TODO -> Implement when this is needed.
|
||||
},
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
toMeasurement: (
|
||||
csToolsAnnotation,
|
||||
DisplaySetService,
|
||||
@@ -63,12 +62,12 @@ const EllipticalRoi = {
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
label: measurementData.text,
|
||||
label: measurementData.label,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
area:
|
||||
|
||||
@@ -4,32 +4,7 @@ import getPointsFromHandles from './utils/getPointsFromHandles';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
|
||||
const Length = {
|
||||
toAnnotation: (measurement, definition) => {
|
||||
const {
|
||||
id,
|
||||
label,
|
||||
description,
|
||||
points,
|
||||
unit,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID,
|
||||
} = measurement;
|
||||
|
||||
return {
|
||||
toolName: definition,
|
||||
measurementData: {
|
||||
sopInstanceUid: SOPInstanceUID,
|
||||
frameOfReferenceUID: FrameOfReferenceUID,
|
||||
SeriesInstanceUID: referenceSeriesUID,
|
||||
unit,
|
||||
text: label,
|
||||
description,
|
||||
handles: getHandlesFromPoints(points),
|
||||
id,
|
||||
},
|
||||
};
|
||||
},
|
||||
toAnnotation: (measurement, definition) => {},
|
||||
|
||||
/**
|
||||
* Maps cornerstone annotation event data to measurement service format.
|
||||
@@ -68,12 +43,12 @@ const Length = {
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
label: measurementData.text,
|
||||
label: measurementData.label,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
length: measurementData.length,
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ describe('measurementServiceMappings.js', () => {
|
||||
toolName: definition,
|
||||
measurementData: {
|
||||
_measurementServiceId: 1,
|
||||
sopInstanceUid: '123',
|
||||
frameOfReferenceUID: '123',
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
handles,
|
||||
text: 'Test',
|
||||
|
||||
+9
@@ -1,4 +1,13 @@
|
||||
export default function getHandlesFromPoints(points) {
|
||||
if (points.longAxis && points.shortAxis) {
|
||||
const handles = {};
|
||||
handles.start = points.longAxis[0];
|
||||
handles.end = points.longAxis[1];
|
||||
handles.perpendicularStart = points.longAxis[0];
|
||||
handles.perpendicularEnd = points.longAxis[1];
|
||||
return handles;
|
||||
}
|
||||
|
||||
return points
|
||||
.map((p, i) => (i % 10 === 0 ? { start: p } : { end: p }))
|
||||
.reduce((obj, item) => Object.assign(obj, { ...item }), {});
|
||||
|
||||
Reference in new issue
Block a user