From 2b83393f91cb16ea06821d79d14ff60f80c29c90 Mon Sep 17 00:00:00 2001 From: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:34:13 -0400 Subject: [PATCH] fix(general): enhancements and bug fixes (#4018) --- .../src/Viewport/OHIFCornerstoneViewport.tsx | 1 + .../cornerstone/src/getCustomizationModule.ts | 130 ++++++++++++ .../cornerstone/src/initMeasurementService.js | 35 +++- .../cornerstone/src/utils/callInputDialog.tsx | 5 +- .../utils/measurementServiceMappings/Angle.ts | 12 +- .../ArrowAnnotate.ts | 7 +- .../Bidirectional.ts | 12 +- .../measurementServiceMappings/CircleROI.ts | 12 +- .../measurementServiceMappings/CobbAngle.ts | 12 +- .../EllipticalROI.ts | 12 +- .../measurementServiceMappings/Length.ts | 37 ++-- .../LivewireContour.ts | 161 +++++++++++++++ .../PlanarFreehandROI.ts | 104 ++++------ .../RectangleROI.ts | 12 +- .../measurementServiceMappings/SplineROI.ts | 188 ++++++++++++++++++ .../constants/supportedTools.js | 2 + .../measurementServiceMappingsFactory.ts | 68 ++++++- .../promptBeginTracking.js | 6 + .../promptTrackNewStudy.js | 6 + modes/basic-test-mode/src/initToolGroups.ts | 2 + modes/basic-test-mode/src/moreTools.ts | 2 +- modes/longitudinal/src/initToolGroups.js | 2 + modes/longitudinal/src/moreTools.ts | 2 +- modes/longitudinal/src/toolbarButtons.ts | 8 - modes/segmentation/src/toolbarButtons.ts | 2 +- platform/app/src/routes/WorkList/WorkList.tsx | 2 +- .../services/UIViewportDialogService/index.js | 3 +- platform/ui/src/assets/styles/styles.css | 4 + .../components/Notification/Notification.tsx | 9 +- .../ViewportDialogProvider.tsx | 3 + 30 files changed, 710 insertions(+), 151 deletions(-) create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index 11b4fbe07..b9e58a71f 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -444,6 +444,7 @@ const OHIFCornerstoneViewport = React.memo(props => { actions={viewportDialogState.actions} onSubmit={viewportDialogState.onSubmit} onOutsideClick={viewportDialogState.onOutsideClick} + onKeyPress={viewportDialogState.onKeyPress} /> )} diff --git a/extensions/cornerstone/src/getCustomizationModule.ts b/extensions/cornerstone/src/getCustomizationModule.ts index 90bf67f6c..ea07e9694 100644 --- a/extensions/cornerstone/src/getCustomizationModule.ts +++ b/extensions/cornerstone/src/getCustomizationModule.ts @@ -64,6 +64,136 @@ function getCustomizationModule() { step: 1, }, }, + { + id: 'cornerstone.measurements', + Angle: { + displayText: [], + report: [], + }, + CobbAngle: { + displayText: [], + report: [], + }, + ArrowAnnotate: { + displayText: [], + report: [], + }, + RectangleROi: { + displayText: [], + report: [], + }, + CircleROI: { + displayText: [], + report: [], + }, + EllipticalROI: { + displayText: [], + report: [], + }, + Bidirectional: { + displayText: [], + report: [], + }, + Length: { + displayText: [], + report: [], + }, + LivewireContour: { + displayText: [], + report: [], + }, + SplineROI: { + displayText: [ + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + value: 'areaUnit', + for: ['area'], + type: 'unit', + }, + /** + { + displayName: 'Modality', + value: 'Modality', + type: 'value', + }, + */ + ], + report: [ + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + displayName: 'Unit', + value: 'areaUnit', + type: 'value', + }, + ], + }, + PlanarFreehandROI: { + displayText: [ + { + displayName: 'Mean', + value: 'mean', + type: 'value', + }, + { + displayName: 'Max', + value: 'max', + type: 'value', + }, + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + value: 'modalityUnit', + for: ['mean', 'max' /** 'stdDev **/], + type: 'unit', + }, + { + value: 'areaUnit', + for: ['area'], + type: 'unit', + }, + /** + { + displayName: 'Std Dev', + value: 'stdDev', + type: 'value', + }, + */ + ], + report: [ + { + displayName: 'Mean', + value: 'mean', + type: 'value', + }, + { + displayName: 'Max', + value: 'max', + type: 'value', + }, + { + displayName: 'Area', + value: 'area', + type: 'value', + }, + { + displayName: 'Unit', + value: 'unit', + type: 'value', + }, + ], + }, + }, ], }, ]; diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js index c19e46c71..61145aa18 100644 --- a/extensions/cornerstone/src/initMeasurementService.js +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -17,7 +17,8 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; const initMeasurementService = ( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ) => { /* Initialization */ const { @@ -30,10 +31,13 @@ const initMeasurementService = ( CobbAngle, RectangleROI, PlanarFreehandROI, + SplineROI, + LivewireContour, } = measurementServiceMappingsFactory( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ); const csTools3DVer1MeasurementSource = measurementService.createSource( CORNERSTONE_3D_TOOLS_SOURCE_NAME, @@ -127,6 +131,14 @@ const initMeasurementService = ( PlanarFreehandROI.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'SplineROI', + SplineROI.matchingCriteria, + SplineROI.toAnnotation, + SplineROI.toMeasurement + ); + // On the UI side, the Calibration Line tool will work almost the same as the // Length tool measurementService.addMapping( @@ -137,16 +149,29 @@ const initMeasurementService = ( Length.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'LivewireContour', + LivewireContour.matchingCriteria, + LivewireContour.toAnnotation, + LivewireContour.toMeasurement + ); + return csTools3DVer1MeasurementSource; }; const connectToolsToMeasurementService = servicesManager => { - const { measurementService, displaySetService, cornerstoneViewportService } = - servicesManager.services; + const { + measurementService, + displaySetService, + cornerstoneViewportService, + customizationService, + } = servicesManager.services; const csTools3DVer1MeasurementSource = initMeasurementService( measurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ); connectMeasurementServiceToTools( measurementService, diff --git a/extensions/cornerstone/src/utils/callInputDialog.tsx b/extensions/cornerstone/src/utils/callInputDialog.tsx index e450536ea..05946b87f 100644 --- a/extensions/cornerstone/src/utils/callInputDialog.tsx +++ b/extensions/cornerstone/src/utils/callInputDialog.tsx @@ -109,13 +109,10 @@ export function callLabelAutocompleteDialog(uiDialogService, callback, dialogCon uiDialogService.create({ id: 'select-annotation', + centralize: true, isDraggable: false, showOverlay: true, content: LabellingFlow, - defaultPosition: { - x: window.innerWidth / 2, - y: window.innerHeight / 2, - }, contentProps: { labellingDoneCallback: labellingDoneCallback, measurementData: { label: '' }, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts index 7173e1344..2e178f2d5 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts @@ -16,7 +16,8 @@ const Angle = { csToolsEventDetail, displaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -54,8 +55,9 @@ const Angle = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -127,7 +129,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -160,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts index 3d5904171..373bd115c 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts @@ -14,7 +14,8 @@ const Length = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -52,7 +53,7 @@ const Length = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); return { uid: annotationUID, @@ -106,7 +107,7 @@ function getMappedAnnotations(annotation, displaySetService) { return annotations; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts index 958e22ed6..6517445d2 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts @@ -11,7 +11,8 @@ const Bidirectional = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -49,8 +50,9 @@ const Bidirectional = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -122,7 +124,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -155,7 +157,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts index e65564c81..c995fb171 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts @@ -9,7 +9,8 @@ const CircleROI = { csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -47,8 +48,9 @@ const CircleROI = { const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -125,7 +127,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -163,7 +165,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts index dbde82860..631f0d029 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts @@ -16,7 +16,8 @@ const CobbAngle = { csToolsEventDetail, displaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -54,8 +55,9 @@ const CobbAngle = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -127,7 +129,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -160,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts index 1882d9ba0..30a925dd1 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts @@ -9,7 +9,8 @@ const EllipticalROI = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -47,8 +48,9 @@ const EllipticalROI = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -125,7 +127,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -163,7 +165,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts index e5e9886b3..bb1319b24 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts @@ -15,7 +15,8 @@ const Length = { csToolsEventDetail, displaySetService, cornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -32,11 +33,7 @@ const Length = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId @@ -57,8 +54,9 @@ const Length = { const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -99,11 +97,8 @@ function getMappedAnnotations(annotation, displaySetService) { throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -132,7 +127,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -167,7 +162,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } @@ -175,13 +170,7 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - length, - SeriesNumber, - SOPInstanceUID, - frameNumber, - unit, - } = mappedAnnotations[0]; + const { length, SeriesNumber, SOPInstanceUID, frameNumber, unit } = mappedAnnotations[0]; const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); @@ -197,9 +186,7 @@ function getDisplayText(mappedAnnotations, displaySet) { return displayText; } const roundedLength = utils.roundNumber(length, 2); - displayText.push( - `${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})` - ); + displayText.push(`${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`); return displayText; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts new file mode 100644 index 000000000..4c2d3f56f --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/LivewireContour.ts @@ -0,0 +1,161 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { getDisplayUnit } from './utils'; +import { utils } from '@ohif/core'; + +/** + * Represents a mapping utility for Livewire measurements. + */ +const LivewireContour = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} csToolsEventDetail Cornerstone event data + * @param {DisplaySetService} DisplaySetService Service for managing display sets + * @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports + * @param {Function} getValueTypeFromToolType Function to get value type from tool type + * @returns {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Livewire tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + if (!validToolType) { + throw new Error(`Tool ${toolName} not supported`); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points: data.contour.polyline, + textBox: data.handles.textBox, + metadata, + frameNumber, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: getDisplayText(annotation, displaySet, customizationService), + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport: () => getColumnValueReport(annotation, customizationService), + }; + }, +}; + +/** + * This function is used to convert the measurement data to a + * format that is suitable for report generation (e.g. for the csv report). + * The report returns a list of columns and corresponding values. + * + * @param {object} annotation + * @returns {object} Report's content from this tool + */ +function getColumnValueReport(annotation, customizationService) { + const columns = []; + const values = []; + + /** Add type */ + columns.push('AnnotationType'); + values.push('Cornerstone:Livewire'); + + /** Add cachedStats */ + const { metadata, data } = annotation; + + /** Add FOR */ + if (metadata.FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(metadata.FrameOfReferenceUID); + } + + /** Add points */ + if (data.contour.polyline) { + /** + * Points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + * convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + * so that it can be used in the CSV report + */ + columns.push('points'); + values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); + } + + return { columns, values }; +} + +/** + * Retrieves the display text for an annotation in a display set. + * + * @param {Object} annotation - The annotation object. + * @param {Object} displaySet - The display set object. + * @returns {string[]} - An array of display text. + */ +function getDisplayText(annotation, displaySet, customizationService) { + const { metadata, data } = annotation; + + if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { + return []; + } + + const { area, areaUnit } = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); + + const displayText = []; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + const { SeriesNumber } = displaySet; + if (SeriesNumber) { + displayText.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + } + + if (area) { + /** + * Add Area + * Area sometimes becomes undefined if `preventHandleOutsideImage` is off + */ + const roundedArea = utils.roundNumber(area || 0, 2); + displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`); + } + + return displayText; +} + +export default LivewireContour; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts index 3820522b1..d3c837c2a 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts @@ -22,7 +22,8 @@ const PlanarFreehandROI = { csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -64,10 +65,10 @@ const PlanarFreehandROI = { toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.label, - displayText: getDisplayText(annotation, displaySet), + displayText: getDisplayText(annotation, displaySet, customizationService), data: data.cachedStats, type: getValueTypeFromToolType(toolName), - getReport: () => getColumnValueReport(annotation), + getReport: () => getColumnValueReport(annotation, customizationService), }; }, }; @@ -80,7 +81,9 @@ const PlanarFreehandROI = { * @param {object} annotation * @returns {object} Report's content from this tool */ -function getColumnValueReport(annotation) { +function getColumnValueReport(annotation, customizationService) { + const { PlanarFreehandROI } = customizationService.get('cornerstone.measurements'); + const { report } = PlanarFreehandROI; const columns = []; const values = []; @@ -90,10 +93,12 @@ function getColumnValueReport(annotation) { /** Add cachedStats */ const { metadata, data } = annotation; - const { mean, stdDev, max, area, unit, areaUnit, perimeter } = - data.cachedStats[`imageId:${metadata.referencedImageId}`]; - columns.push(`Maximum`, `Mean`, `Std Dev`, 'Pixel Unit', `Area`, 'Unit', 'Perimeter'); - values.push(max, mean, stdDev, unit, area, areaUnit, perimeter); + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + report.forEach(({ name, value }) => { + columns.push(name); + stats[value] ? values.push(stats[value]) : values.push('not available'); + }); /** Add FOR */ if (metadata.FrameOfReferenceUID) { @@ -103,11 +108,6 @@ function getColumnValueReport(annotation) { /** Add points */ if (data.contour.polyline) { - /** - * Points has the form of [[x1, y1, z1], [x2, y2, z2], ...] - * convert it to string of [[x1 y1 z1];[x2 y2 z2];...] - * so that it can be used in the CSV report - */ columns.push('points'); values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); } @@ -122,19 +122,19 @@ function getColumnValueReport(annotation) { * @param {Object} displaySet - The display set object. * @returns {string[]} - An array of display text. */ -function getDisplayText(annotation, displaySet) { +function getDisplayText(annotation, displaySet, customizationService) { + const { PlanarFreehandROI } = customizationService.get('cornerstone.measurements'); + const { displayText } = PlanarFreehandROI; + const { metadata, data } = annotation; if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { return []; } - const { mean, stdDev, max, area, modalityUnit, areaUnit, perimeter } = - data.cachedStats[`imageId:${metadata.referencedImageId}`]; - const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); - const displayText = []; + const displayTextArray = []; const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; @@ -147,55 +147,39 @@ function getDisplayText(annotation, displaySet) { const { SeriesNumber } = displaySet; if (SeriesNumber) { - displayText.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + displayTextArray.push(`S: ${SeriesNumber}${instanceText}${frameText}`); } - if (area) { - /** - * Add Area - * Area sometimes becomes undefined if `preventHandleOutsideImage` is off - */ - const roundedArea = utils.roundNumber(area || 0, 2); - displayText.push(`Area: ${roundedArea} ${getDisplayUnit(areaUnit)}`); - } + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; - if (mean) { - if (Array.isArray(mean)) { - const meanValues = mean.map(value => utils.roundNumber(value)); - displayText.push(`Mean: ${meanValues.join(', ')} ${modalityUnit}`); - } else { - displayText.push(`Mean: ${utils.roundNumber(mean)} ${modalityUnit}`); + const roundValues = values => { + if (Array.isArray(values)) { + return values.map(value => { + if (isNaN(value)) { + return value; + } + return utils.roundNumber(value); + }); } - } + return isNaN(values) ? values : utils.roundNumber(values); + }; - if (max) { - if (Array.isArray(max)) { - const maxValues = max.map(value => utils.roundNumber(value, 2)); - displayText.push(`Max: ${maxValues.join(', ')} ${modalityUnit}`); - } else { - displayText.push(`Max: ${utils.roundNumber(max, 2)} ${modalityUnit}`); + const findUnitForValue = (displayTextItems, value) => + displayTextItems.find(({ type, for: filter }) => type === 'unit' && filter.includes(value)) + ?.value; + + const formatDisplayText = (displayName, result, unit) => + `${displayName}: ${Array.isArray(result) ? roundValues(result).join(', ') : roundValues(result)} ${unit}`; + + displayText.forEach(({ displayName, value, type }) => { + if (type === 'value') { + const result = stats[value]; + const unit = stats[findUnitForValue(displayText, value)] || ''; + displayTextArray.push(formatDisplayText(displayName, result, unit)); } - } + }); - if (stdDev) { - if (Array.isArray(stdDev)) { - const stdDevValues = stdDev.map(value => utils.roundNumber(value)); - displayText.push(`Std Dev: ${stdDevValues.join(', ')} ${modalityUnit}`); - } else { - displayText.push(`Std Dev: ${utils.roundNumber(stdDev)} ${modalityUnit}`); - } - } - - if (perimeter) { - if (Array.isArray(perimeter)) { - const perimeterValues = perimeter.map(value => utils.roundNumber(value)); - displayText.push(`Perimeter: ${perimeterValues.join(', ')} ${modalityUnit}`); - } else { - displayText.push(`Perimeter: ${utils.roundNumber(perimeter)} ${modalityUnit}`); - } - } - - return displayText; + return displayTextArray; } export default PlanarFreehandROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts index 6c7e4b4ca..943822de7 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts @@ -9,7 +9,8 @@ const RectangleROI = { csToolsEventDetail, DisplaySetService, CornerstoneViewportService, - getValueTypeFromToolType + getValueTypeFromToolType, + customizationService ) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -47,8 +48,9 @@ const RectangleROI = { const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); - const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService); return { uid: annotationUID, @@ -125,7 +127,7 @@ This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report). The report returns a list of columns and corresponding values. */ -function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { +function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) { const columns = []; const values = []; @@ -163,7 +165,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { }; } -function getDisplayText(mappedAnnotations, displaySet) { +function getDisplayText(mappedAnnotations, displaySet, customizationService) { if (!mappedAnnotations || !mappedAnnotations.length) { return ''; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts new file mode 100644 index 000000000..05ed5b1dc --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/SplineROI.ts @@ -0,0 +1,188 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { getDisplayUnit } from './utils'; +import { utils } from '@ohif/core'; + +/** + * Represents a mapping utility for Spline ROI measurements. + */ +const SplineROI = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} csToolsEventDetail Cornerstone event data + * @param {DisplaySetService} DisplaySetService Service for managing display sets + * @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports + * @param {Function} getValueTypeFromToolType Function to get value type from tool type + * @returns {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType, + customizationService + ) => { + const { annotation } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('SplineROI tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + if (!validToolType) { + throw new Error(`Tool ${toolName} not supported`); + } + + const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } = + getSOPInstanceAttributes(referencedImageId); + + let displaySet; + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points: data.contour.polyline, + textBox: data.handles.textBox, + metadata, + frameNumber, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: getDisplayText(annotation, displaySet, customizationService), + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport: () => getColumnValueReport(annotation, customizationService), + }; + }, +}; + +/** + * This function is used to convert the measurement data to a + * format that is suitable for report generation (e.g. for the csv report). + * The report returns a list of columns and corresponding values. + * + * @param {object} annotation + * @returns {object} Report's content from this tool + */ +function getColumnValueReport(annotation, customizationService) { + const { SplineROI } = customizationService.get('cornerstone.measurements'); + const { report } = SplineROI; + const columns = []; + const values = []; + + /** Add type */ + columns.push('AnnotationType'); + values.push('Cornerstone:SplineROI'); + + /** Add cachedStats */ + const { metadata, data } = annotation; + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + report.forEach(({ name, value }) => { + columns.push(name); + stats[value] ? values.push(stats[value]) : values.push('not available'); + }); + + /** Add FOR */ + if (metadata.FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(metadata.FrameOfReferenceUID); + } + + /** Add points */ + if (data.contour.polyline) { + /** + * Points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + * convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + * so that it can be used in the CSV report + */ + columns.push('points'); + values.push(data.contour.polyline.map(p => p.join(' ')).join(';')); + } + + return { columns, values }; +} + +/** + * Retrieves the display text for an annotation in a display set. + * + * @param {Object} annotation - The annotation object. + * @param {Object} displaySet - The display set object. + * @returns {string[]} - An array of display text. + */ +function getDisplayText(annotation, displaySet, customizationService) { + const { SplineROI } = customizationService.get('cornerstone.measurements'); + const { displayText } = SplineROI; + const { metadata, data } = annotation; + + if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) { + return []; + } + const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId); + + const displayTextArray = []; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + const { SeriesNumber } = displaySet; + if (SeriesNumber) { + displayTextArray.push(`S: ${SeriesNumber}${instanceText}${frameText}`); + } + + const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`]; + + const roundValues = values => { + if (Array.isArray(values)) { + return values.map(value => { + if (isNaN(value)) { + return value; + } + return utils.roundNumber(value); + }); + } + return isNaN(values) ? values : utils.roundNumber(values); + }; + + const findUnitForValue = (displayTextItems, value) => + displayTextItems.find(({ type, for: filter }) => type === 'unit' && filter.includes(value)) + ?.value; + + const formatDisplayText = (displayName, result, unit) => + `${displayName}: ${Array.isArray(result) ? roundValues(result).join(', ') : roundValues(result)} ${unit}`; + + displayText.forEach(({ displayName, value, type }) => { + if (type === 'value') { + const result = stats[value]; + const unit = stats[findUnitForValue(displayText, value)] || ''; + displayTextArray.push(formatDisplayText(displayName, result, unit)); + } + }); + + return displayTextArray; +} + +export default SplineROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index e1e8a1755..8f69319da 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -9,4 +9,6 @@ export default [ 'Probe', 'RectangleROI', 'PlanarFreehandROI', + 'SplineROI', + 'LivewireContour', ]; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts index e3ccf4e51..19de0308f 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts @@ -8,11 +8,14 @@ import CobbAngle from './CobbAngle'; import Angle from './Angle'; import PlanarFreehandROI from './PlanarFreehandROI'; import RectangleROI from './RectangleROI'; +import SplineROI from './SplineROI'; +import LivewireContour from './LivewireContour'; const measurementServiceMappingsFactory = ( measurementService: MeasurementService, displaySetService, - cornerstoneViewportService + cornerstoneViewportService, + customizationService ) => { /** * Maps measurement service format object to cornerstone annotation object. @@ -39,6 +42,8 @@ const measurementServiceMappingsFactory = ( ArrowAnnotate: POINT, CobbAngle: ANGLE, Angle: ANGLE, + SplineROI: POLYLINE, + LivewireContour: POLYLINE, }; return TOOL_TYPE_TO_VALUE_TYPE[toolType]; @@ -52,7 +57,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -68,7 +74,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ // TODO -> We should eventually do something like shortAxis + longAxis, @@ -91,7 +98,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -107,7 +115,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -123,7 +132,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -139,7 +149,42 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + }, + ], + }, + + SplineROI: { + toAnnotation: SplineROI.toAnnotation, + toMeasurement: csToolsAnnotation => + SplineROI.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + }, + ], + }, + + LivewireContour: { + toAnnotation: LivewireContour.toAnnotation, + toMeasurement: csToolsAnnotation => + LivewireContour.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -155,7 +200,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -172,7 +218,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { @@ -188,7 +235,8 @@ const measurementServiceMappingsFactory = ( csToolsAnnotation, displaySetService, cornerstoneViewportService, - _getValueTypeFromToolType + _getValueTypeFromToolType, + customizationService ), matchingCriteria: [ { diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js index acf298146..d8bb02b99 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js @@ -66,6 +66,12 @@ function _askTrackMeasurements(uiViewportDialogService, viewportId) { uiViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + const action = actions.find(action => action.id === 'prompt-begin-tracking-yes'); + onSubmit(action.value); + } + }, }); }); } diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js index 2837de7cb..b590af3f6 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js @@ -65,6 +65,12 @@ function _askTrackMeasurements(UIViewportDialogService, viewportId) { UIViewportDialogService.hide(); resolve(RESPONSE.CANCEL); }, + onKeyPress: event => { + if (event.key === 'Enter') { + const action = actions.find(action => action.value === RESPONSE.SET_STUDY_AND_SERIES); + onSubmit(action.value); + } + }, }); }); } diff --git a/modes/basic-test-mode/src/initToolGroups.ts b/modes/basic-test-mode/src/initToolGroups.ts index a791d6ab0..5feb3214b 100644 --- a/modes/basic-test-mode/src/initToolGroups.ts +++ b/modes/basic-test-mode/src/initToolGroups.ts @@ -42,6 +42,7 @@ function initDefaultToolGroup(extensionManager, toolGroupService, commandsManage }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, @@ -170,6 +171,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, diff --git a/modes/basic-test-mode/src/moreTools.ts b/modes/basic-test-mode/src/moreTools.ts index 5f49e08ad..5708c2a17 100644 --- a/modes/basic-test-mode/src/moreTools.ts +++ b/modes/basic-test-mode/src/moreTools.ts @@ -119,7 +119,7 @@ const moreTools = [ evaluate: 'evaluate.viewportProperties.toggle', }), createButton({ - id: 'DragProbe', + id: 'Probe', icon: 'tool-probe', label: 'Probe', tooltip: 'Probe', diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index f5bae8d86..329d047b5 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -55,6 +55,7 @@ function initDefaultToolGroup( }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, @@ -201,6 +202,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager, m }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, diff --git a/modes/longitudinal/src/moreTools.ts b/modes/longitudinal/src/moreTools.ts index 267114f73..70ead4860 100644 --- a/modes/longitudinal/src/moreTools.ts +++ b/modes/longitudinal/src/moreTools.ts @@ -126,7 +126,7 @@ const moreTools = [ evaluate: 'evaluate.viewportProperties.toggle', }), createButton({ - id: 'DragProbe', + id: 'Probe', icon: 'tool-probe', label: 'Probe', tooltip: 'Probe', diff --git a/modes/longitudinal/src/toolbarButtons.ts b/modes/longitudinal/src/toolbarButtons.ts index dc5700fe6..62e33a679 100644 --- a/modes/longitudinal/src/toolbarButtons.ts +++ b/modes/longitudinal/src/toolbarButtons.ts @@ -65,14 +65,6 @@ const toolbarButtons: Button[] = [ commands: setToolActiveToolbar, evaluate: 'evaluate.cornerstoneTool', }), - createButton({ - id: 'PlanarFreehandROI', - icon: 'tool-freehand-polygon', - label: 'Freehand', - tooltip: 'Freehand ROI', - commands: setToolActiveToolbar, - evaluate: 'evaluate.cornerstoneTool', - }), createButton({ id: 'RectangleROI', icon: 'tool-rectangle', diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts index 58e12eca8..9d53484f7 100644 --- a/modes/segmentation/src/toolbarButtons.ts +++ b/modes/segmentation/src/toolbarButtons.ts @@ -195,7 +195,7 @@ const toolbarButtons: Button[] = [ evaluate: 'evaluate.viewportProperties.toggle', }), createButton({ - id: 'DragProbe', + id: 'Probe', icon: 'tool-probe', label: 'Probe', tooltip: 'Probe', diff --git a/platform/app/src/routes/WorkList/WorkList.tsx b/platform/app/src/routes/WorkList/WorkList.tsx index bfa4ecd70..a5e522c86 100644 --- a/platform/app/src/routes/WorkList/WorkList.tsx +++ b/platform/app/src/routes/WorkList/WorkList.tsx @@ -534,7 +534,7 @@ function WorkList({ showPatientInfo={PatientInfoVisibility.DISABLED} /> -
+
100 ? 101 : numOfStudies} filtersMeta={filtersMeta} diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js index 5c5053648..20e962a58 100644 --- a/platform/core/src/services/UIViewportDialogService/index.js +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -26,7 +26,7 @@ const serviceImplementation = { * * @param {ViewportDialogProps} props { content, contentProps, viewportId } */ -function _show({ viewportId, id, type, message, actions, onSubmit, onOutsideClick }) { +function _show({ viewportId, id, type, message, actions, onSubmit, onOutsideClick, onKeyPress }) { return serviceImplementation._show({ viewportId, id, @@ -35,6 +35,7 @@ function _show({ viewportId, id, type, message, actions, onSubmit, onOutsideClic actions, onSubmit, onOutsideClick, + onKeyPress, }); } diff --git a/platform/ui/src/assets/styles/styles.css b/platform/ui/src/assets/styles/styles.css index 0dc42514f..357f8a58b 100644 --- a/platform/ui/src/assets/styles/styles.css +++ b/platform/ui/src/assets/styles/styles.css @@ -4,6 +4,10 @@ overflow-y: auto; } +.ohif-scrollbar-stable-gutter { + scrollbar-gutter: stable; +} + .study-min-height { min-height: 450px; } diff --git a/platform/ui/src/components/Notification/Notification.tsx b/platform/ui/src/components/Notification/Notification.tsx index 1f603a49f..bfdf6d6f0 100644 --- a/platform/ui/src/components/Notification/Notification.tsx +++ b/platform/ui/src/components/Notification/Notification.tsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import Button, { ButtonEnums } from '../Button'; import Icon from '../Icon'; -const Notification = ({ id, type, message, actions, onSubmit, onOutsideClick }) => { +const Notification = ({ id, type, message, actions, onSubmit, onOutsideClick, onKeyPress }) => { const notificationRef = useRef(null); useEffect(() => { @@ -29,6 +29,10 @@ const Notification = ({ id, type, message, actions, onSubmit, onOutsideClick }) }; }, [onOutsideClick]); + useEffect(() => { + notificationRef.current.focus(); + }, []); + const iconsByType = { error: { icon: 'info', @@ -64,6 +68,8 @@ const Notification = ({ id, type, message, actions, onSubmit, onOutsideClick }) ref={notificationRef} className="border-customblue-10 bg-customblue-400 mx-2 mt-2 flex flex-col rounded-md border-2 p-2" data-cy={id} + onKeyDown={onKeyPress} + tabIndex={0} >
{ console.log('dismiss? -1'); }, + onKeyPress: () => { + console.log('key pressed?'); + }, }; const ViewportDialogContext = createContext(null);