diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json
index 20a810b59..8759b1114 100644
--- a/extensions/cornerstone-dicom-sr/package.json
+++ b/extensions/cornerstone-dicom-sr/package.json
@@ -36,7 +36,7 @@
"@ohif/extension-cornerstone": "^3.0.0",
"@ohif/extension-measurement-tracking": "^3.0.0",
"@ohif/ui": "^2.0.0",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
@@ -45,7 +45,8 @@
"dependencies": {
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
- "@cornerstonejs/core": "0.30.1",
- "@cornerstonejs/tools": "0.39.0"
+ "@cornerstonejs/adapters": "^0.3.1",
+ "@cornerstonejs/core": "^0.30.1",
+ "@cornerstonejs/tools": "^0.44.0"
}
}
diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js
index 00431a3c8..7b685ed44 100644
--- a/extensions/cornerstone-dicom-sr/src/commandsModule.js
+++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js
@@ -30,14 +30,12 @@ const _generateReport = (
const report = MeasurementReport.generateReport(
filteredToolState,
metaData,
- utilities.worldToImageCoords
+ utilities.worldToImageCoords,
+ options
);
const { dataset } = report;
- // Add in top level series options
- Object.assign(dataset, options);
-
// Set the default character set as UTF-8
// https://dicom.innolitics.com/ciods/nm-image/sop-common/00080005
if (typeof dataset.SpecificCharacterSet === 'undefined') {
@@ -89,7 +87,7 @@ const commandsModule = ({}) => {
additionalFindingTypes,
options = {},
}) => {
- // TODO -> Eventually use the measurements directly and not the dcmjs adapter,
+ // Use the @cornerstonejs adapter for converting to/from DICOM
// But it is good enough for now whilst we only have cornerstone as a datasource.
log.info('[DICOMSR] storeMeasurements');
diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
similarity index 93%
rename from extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js
rename to extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
index 7bc1e1fdf..54a810a8d 100644
--- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js
+++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
@@ -51,13 +51,17 @@ const RELATIONSHIP_TYPE = {
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
/**
- * Basic SOPClassHandler:
- * - For all Image types that are stackable, create
- * a displaySet with a stack of images
+ * DICOM SR SOP Class Handler
+ * For all referenced images in the TID 1500/300 sections, add an image to the
+ * display (this is TODO - it is not the actual behaviour below unfortunately)
*
- * @param {Array} sopClassHandlerModules List of SOP Class Modules
- * @param {SeriesMetadata} series The series metadata object from which the display sets will be created
- * @returns {Array} The list of display sets created for the given series object
+ * This will only display and rehydrate the latest DICOM SR in the given series
+ * It would be possible to add the ability to view older series rehydrations
+ * in the future.
+ *
+ * @param instances is a set of instances all from the same series
+ * @param servicesManager is the services that can be used for creating
+ * @returns The list of display sets created for the given instances object
*/
function _getDisplaySetsFromSeries(
instances,
@@ -69,7 +73,8 @@ function _getDisplaySetsFromSeries(
throw new Error('No instances were provided');
}
- const instance = instances[0];
+ utils.sortStudyInstances(instances);
+ const instance = instances[instances.length - 1];
const {
StudyInstanceUID,
@@ -85,10 +90,10 @@ function _getDisplaySetsFromSeries(
if (
!ConceptNameCodeSequence ||
ConceptNameCodeSequence.CodeValue !==
- CodeNameCodeSequenceValues.ImagingMeasurementReport
+ CodeNameCodeSequenceValues.ImagingMeasurementReport
) {
- console.warn(
- 'Only support Imaging Measurement Report SRs (TID1500) for now'
+ console.log(
+ 'Only support Imaging Measurement Report SRs (TID1500) for this renderer.'
);
return [];
}
@@ -105,6 +110,9 @@ function _getDisplaySetsFromSeries(
StudyInstanceUID,
SOPClassHandlerId,
SOPClassUID,
+ instances,
+ // Others is a historical value used for instances which is deprecated and will be removed
+ others: instances,
referencedImages: null,
measurements: null,
isDerivedDisplaySet: true,
@@ -313,13 +321,13 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
MeasurementGroups
);
- let measurements = [];
+ const measurements = [];
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
trackingUniqueIdentifier => {
const mergedContentSequence =
mergedContentSequencesByTrackingUniqueIdentifiers[
- trackingUniqueIdentifier
+ trackingUniqueIdentifier
];
const measurement = _processMeasurement(mergedContentSequence);
@@ -359,7 +367,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(
if (
mergedContentSequencesByTrackingUniqueIdentifiers[
- trackingUniqueIdentifier
+ trackingUniqueIdentifier
] === undefined
) {
// Add the full ContentSequence
@@ -474,9 +482,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
const FindingSites = mergedContentSequence.filter(
item =>
item.ConceptNameCodeSequence.CodingSchemeDesignator ===
- CodingSchemeDesignators.SRT &&
+ CodingSchemeDesignators.SRT &&
item.ConceptNameCodeSequence.CodeValue ===
- CodeNameCodeSequenceValues.FindingSite
+ CodeNameCodeSequenceValues.FindingSite
);
const measurement = {
@@ -493,7 +501,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
Finding.ConceptCodeSequence.CodingSchemeDesignator
) &&
Finding.ConceptCodeSequence.CodeValue ===
- CodeNameCodeSequenceValues.CornerstoneFreeText
+ CodeNameCodeSequenceValues.CornerstoneFreeText
) {
measurement.labels.push({
label: CORNERSTONE_FREETEXT_CODE_VALUE,
@@ -509,7 +517,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
) &&
FindingSite.ConceptCodeSequence.CodeValue ===
- CodeNameCodeSequenceValues.CornerstoneFreeText
+ CodeNameCodeSequenceValues.CornerstoneFreeText
);
if (cornerstoneFreeTextFindingSite) {
diff --git a/extensions/cornerstone-dicom-sr/src/init.js b/extensions/cornerstone-dicom-sr/src/init.js
deleted file mode 100644
index 92a907ff8..000000000
--- a/extensions/cornerstone-dicom-sr/src/init.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { addTool, annotation } from '@cornerstonejs/tools';
-import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
-import SRLengthTool from './tools/tools/SRLength';
-import SRBidirectionalTool from './tools/tools/SRBidirectional';
-import SREllipticalROITool from './tools/tools/SREllipticalROI';
-import SRArrowAnnotateTool from './tools/tools/SRArrowAnnotate';
-
-/**
- * @param {object} configuration
- */
-export default function init({ configuration = {} }) {
- addTool(DICOMSRDisplayTool);
- addTool(SRLengthTool);
- addTool(SRBidirectionalTool);
- addTool(SREllipticalROITool);
- addTool(SRArrowAnnotateTool);
-
- // Modify annotation tools to use dashed lines on SR
- const dashedLine = {
- lineDash: '4,4',
- };
- annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
- [SRLengthTool.toolName]: dashedLine,
- [SRBidirectionalTool.toolName]: dashedLine,
- [SREllipticalROITool.toolName]: dashedLine,
- [SRArrowAnnotateTool.toolName]: dashedLine,
- global: {},
- });
-}
diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts
new file mode 100644
index 000000000..ec24b5c48
--- /dev/null
+++ b/extensions/cornerstone-dicom-sr/src/init.ts
@@ -0,0 +1,49 @@
+import {
+ addTool,
+ AngleTool,
+ annotation,
+ ArrowAnnotateTool,
+ BidirectionalTool,
+ CobbAngleTool,
+ EllipticalROITool,
+ LengthTool,
+ PlanarFreehandROITool,
+} from '@cornerstonejs/tools';
+import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
+import addToolInstance from './utils/addToolInstance';
+import { Types } from '@ohif/core';
+import toolNames from './tools/toolNames';
+
+/**
+ * @param {object} configuration
+ */
+export default function init({
+ configuration = {},
+}: Types.Extensions.ExtensionParams): void {
+ addTool(DICOMSRDisplayTool);
+ addToolInstance(toolNames.SRLength, LengthTool, {});
+ addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
+ addToolInstance(toolNames.SREllipticalROI, EllipticalROITool);
+ addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool);
+ addToolInstance(toolNames.SRAngle, AngleTool);
+ // TODO - fix the SR display of Cobb Angle, as it joins the two lines
+ addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
+ // TODO - fix the rehydration of Freehand, as it throws an exception
+ // on a missing polyline. The fix is probably in CS3D
+ addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
+
+ // Modify annotation tools to use dashed lines on SR
+ const dashedLine = {
+ lineDash: '4,4',
+ };
+ annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
+ SRLength: dashedLine,
+ SRBidirectional: dashedLine,
+ SREllipticalROI: dashedLine,
+ SRArrowAnnotate: dashedLine,
+ SRCobbAngle: dashedLine,
+ SRAngle: dashedLine,
+ SRPlanarFreehandROI: dashedLine,
+ global: {},
+ });
+}
diff --git a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
index 5e714f831..48375f4cb 100644
--- a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
+++ b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
@@ -1,15 +1,15 @@
import DICOMSRDisplayTool from './DICOMSRDisplayTool';
-import SRLengthTool from './tools/SRLength';
-import SRBidirectional from './tools/SRBidirectional';
-import SREllipticalROI from './tools/SREllipticalROI';
-import SRArrowAnnotate from './tools/SRArrowAnnotate';
const toolNames = {
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
- SRLength: SRLengthTool.toolName,
- SRBidirectional: SRBidirectional.toolName,
- SREllipticalROI: SREllipticalROI.toolName,
- SRArrowAnnotate: SRArrowAnnotate.toolName,
+ SRLength: 'SRLength',
+ SRBidirectional: 'SRBidirectional',
+ SREllipticalROI: 'SREllipticalROI',
+ SRArrowAnnotate: 'SRArrowAnnotate',
+ SRAngle: 'SRAngle',
+ SRCobbAngle: 'SRCobbAngle',
+ SRRectangleROI: 'SRRectangleROI',
+ SRPlanarFreehandROI: 'SRPlanarFreehandROI',
};
export default toolNames;
diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts
deleted file mode 100644
index 2500a2dc0..000000000
--- a/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ArrowAnnotateTool } from '@cornerstonejs/tools';
-
-/**
- * The reason we are extending ArrowAnnotateTool is to create a new tool for SR
- * viewport which basically has a different name. This is done since Cornerstone
- * has shifted from creating tool instances for each annotation, and we have ArrowAnnotate
- * mappers at the measurementService, so if we didn't do this, we would be mapping
- * the SR annotation to the measurementService (since there is a ArrowAnnotateTool mapper),
- * but with extending and renaming it, there is not mapper for SRArrowAnnotateTool; hence
- * no mapping; hence no new measurement, just temporary ones for the SR viewport.
- */
-class SRArrowAnnotateTool extends ArrowAnnotateTool {
- static toolName = 'SRArrowAnnotate';
-}
-
-export default SRArrowAnnotateTool;
diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts
deleted file mode 100644
index 2b121d0d0..000000000
--- a/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { BidirectionalTool } from '@cornerstonejs/tools';
-
-/**
- * The reason we are extending BidirectionalTool is to create a new tool for SR
- * viewport which basically has a different name. This is done since Cornerstone
- * has shifted from creating tool instances for each annotation, and we have Bidirectional
- * mappers at the measurementService, so if we didn't do this, we would be mapping
- * the SR annotation to the measurementService (since there is a BidirectionalTool mapper),
- * but with extending and renaming it, there is not mapper for SRBidirectionalTool; hence
- * no mapping; hence no new measurement, just temporary ones for the SR viewport.
- */
-class SRBidirectional extends BidirectionalTool {
- static toolName = 'SRBidirectional';
-}
-
-export default SRBidirectional;
diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts
deleted file mode 100644
index 66d97da94..000000000
--- a/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { EllipticalROITool } from '@cornerstonejs/tools';
-
-/**
- * The reason we are extending EllipticalROITool is to create a new tool for SR
- * viewport which basically has a different name. This is done since Cornerstone
- * has shifted from creating tool instances for each annotation, and we have EllipticalROI
- * mappers at the measurementService, so if we didn't do this, we would be mapping
- * the SR annotation to the measurementService (since there is a EllipticalROITool mapper),
- * but with extending and renaming it, there is not mapper for SREllipticalROITool; hence
- * no mapping; hence no new measurement, just temporary ones for the SR viewport.
- */
-class SREllipticalROI extends EllipticalROITool {
- static toolName = 'SREllipticalROI';
-}
-
-export default SREllipticalROI;
diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts
deleted file mode 100644
index cc3c44de9..000000000
--- a/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { LengthTool } from '@cornerstonejs/tools';
-
-/**
- * The reason we are extending LengthTool is to create a new tool for SR
- * viewport which basically has a different name. This is done since Cornerstone
- * has shifted from creating tool instances for each annotation, and we have Length
- * mappers at the measurementService, so if we didn't do this, we would be mapping
- * the SR annotation to the measurementService (since there is a LengthTool mapper),
- * but with extending and renaming it, there is not mapper for SRLengthTool; hence
- * no mapping; hence no new measurement, just temporary ones for the SR viewport.
- */
-class SRLengthTool extends LengthTool {
- static toolName = 'SRLength';
-}
-
-export default SRLengthTool;
diff --git a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts
new file mode 100644
index 000000000..889926d81
--- /dev/null
+++ b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts
@@ -0,0 +1,12 @@
+import { addTool } from '@cornerstonejs/tools';
+
+export default function addToolInstance(
+ name: string,
+ toolClass,
+ configuration?
+): void {
+ class InstanceClass extends toolClass {
+ static toolName = name;
+ }
+ addTool(InstanceClass);
+}
diff --git a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js
index 3474e3b11..41602dd25 100644
--- a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js
+++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js
@@ -1,6 +1,8 @@
import { adaptersSR } from '@cornerstonejs/adapters';
-const cornerstoneAdapters = adaptersSR.Cornerstone3D;
+const cornerstoneAdapters =
+ adaptersSR.Cornerstone3D.MeasurementReport
+ .CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
@@ -37,7 +39,7 @@ export default function isRehydratable(displaySet, mappings) {
});
for (let i = 0; i < measurements.length; i++) {
- const TrackingIdentifier = measurements[i].TrackingIdentifier;
+ const { TrackingIdentifier } = measurements[i] || {};
const hydratable = adapters.some(adapter => {
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
@@ -54,7 +56,13 @@ export default function isRehydratable(displaySet, mappings) {
if (hydratable) {
return true;
}
+ console.log(
+ 'Measurement is not rehydratable',
+ TrackingIdentifier,
+ measurements[i]
+ );
}
+ console.log('No measurements found which were rehydratable');
return false;
}
diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx
index 8c788e5b9..cb13803d7 100644
--- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx
+++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx
@@ -284,7 +284,7 @@ function OHIFCornerstoneSRViewport(props) {
* isHydratable check, the outcome for the isHydrated state here is always FALSE
* since we don't do the hydration here. Todo: can't we just set it as false? why
* we are changing the state here? isHydrated is always false at this stage, and
- * if it is hydrated we don't event use the SR viewport.
+ * if it is hydrated we don't even use the SR viewport.
*/
useEffect(() => {
if (!srDisplaySet.isLoaded) {
diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json
index 0518ba474..7049b4cde 100644
--- a/extensions/cornerstone/package.json
+++ b/extensions/cornerstone/package.json
@@ -30,7 +30,7 @@
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
"cornerstone-wado-image-loader": "^4.2.1",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
@@ -43,10 +43,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "0.3.0",
- "@cornerstonejs/core": "0.30.1",
- "@cornerstonejs/streaming-image-volume-loader": "^0.11.2",
- "@cornerstonejs/tools": "0.39.0",
+ "@cornerstonejs/adapters": "^0.3.1",
+ "@cornerstonejs/core": "^0.30.1",
+ "@cornerstonejs/streaming-image-volume-loader": "^0.8.2",
+ "@cornerstonejs/tools": "^0.44.0",
"@kitware/vtk.js": "26.4.0",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",
diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx
index a3ebc57c2..5259b267c 100644
--- a/extensions/cornerstone/src/index.tsx
+++ b/extensions/cornerstone/src/index.tsx
@@ -25,6 +25,7 @@ import { registerColormap } from './utils/colormap/transferFunctionHelpers';
import { id } from './id';
import * as csWADOImageLoader from './initWADOImageLoader.js';
+import { measurementMappingUtils } from './utils/measurementServiceMappings';
const Component = React.lazy(() => {
return import(
@@ -62,10 +63,9 @@ const cornerstoneExtension: Types.Extensions.Extension = {
},
/**
+ * Register the Cornerstone 3D services and set them up for use.
*
- *
- * @param {object} [configuration={}]
- * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
+ * @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools`
*/
async preRegistration({
servicesManager,
@@ -151,3 +151,4 @@ const cornerstoneExtension: Types.Extensions.Extension = {
};
export default cornerstoneExtension;
+export { measurementMappingUtils };
diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js
index 635b7b77e..80dc0678b 100644
--- a/extensions/cornerstone/src/initCornerstoneTools.js
+++ b/extensions/cornerstone/src/initCornerstoneTools.js
@@ -14,6 +14,8 @@ import {
DragProbeTool,
ProbeTool,
AngleTool,
+ CobbAngleTool,
+ PlanarFreehandROITool,
MagnifyTool,
CrosshairsTool,
SegmentationDisplayTool,
@@ -40,6 +42,8 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(ArrowAnnotateTool);
addTool(DragProbeTool);
addTool(AngleTool);
+ addTool(CobbAngleTool);
+ addTool(PlanarFreehandROITool);
addTool(MagnifyTool);
addTool(CrosshairsTool);
addTool(SegmentationDisplayTool);
@@ -76,6 +80,8 @@ const toolNames = {
EllipticalROI: EllipticalROITool.toolName,
Bidirectional: BidirectionalTool.toolName,
Angle: AngleTool.toolName,
+ CobbAngle: CobbAngleTool.toolName,
+ PlanarFreehandROI: PlanarFreehandROITool.toolName,
Magnify: MagnifyTool.toolName,
Crosshairs: CrosshairsTool.toolName,
SegmentationDisplay: SegmentationDisplayTool.toolName,
diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js
index 8a0d492bc..9ce6fc5c7 100644
--- a/extensions/cornerstone/src/initMeasurementService.js
+++ b/extensions/cornerstone/src/initMeasurementService.js
@@ -1,6 +1,6 @@
import { eventTarget } from '@cornerstonejs/core';
import { Enums, annotation } from '@cornerstonejs/tools';
-import { DicomMetadataStore } from '@ohif/core';
+import { DicomMetadataStore, MeasurementService } from '@ohif/core';
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
@@ -23,6 +23,10 @@ const initMeasurementService = (
Bidirectional,
EllipticalROI,
ArrowAnnotate,
+ Angle,
+ CobbAngle,
+ RectangleROI,
+ PlanarFreehandROI,
} = measurementServiceMappingsFactory(
measurementService,
displaySetService,
@@ -66,6 +70,38 @@ const initMeasurementService = (
ArrowAnnotate.toMeasurement
);
+ measurementService.addMapping(
+ csTools3DVer1MeasurementSource,
+ 'CobbAngle',
+ CobbAngle.matchingCriteria,
+ CobbAngle.toAnnotation,
+ CobbAngle.toMeasurement
+ );
+
+ measurementService.addMapping(
+ csTools3DVer1MeasurementSource,
+ 'Angle',
+ Angle.matchingCriteria,
+ Angle.toAnnotation,
+ Angle.toMeasurement
+ );
+
+ measurementService.addMapping(
+ csTools3DVer1MeasurementSource,
+ 'RectangleROI',
+ RectangleROI.matchingCriteria,
+ RectangleROI.toAnnotation,
+ RectangleROI.toMeasurement
+ );
+
+ measurementService.addMapping(
+ csTools3DVer1MeasurementSource,
+ 'PlanarFreehandROI',
+ PlanarFreehandROI.matchingCriteria,
+ PlanarFreehandROI.toAnnotation,
+ PlanarFreehandROI.toMeasurement
+ );
+
return csTools3DVer1MeasurementSource;
};
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts
new file mode 100644
index 000000000..dfbe9aa62
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts
@@ -0,0 +1,210 @@
+import SUPPORTED_TOOLS from './constants/supportedTools';
+import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
+import { utils } from '@ohif/core';
+
+const Angle = {
+ toAnnotation: measurement => { },
+
+ /**
+ * Maps cornerstone annotation event data to measurement service format.
+ *
+ * @param {Object} cornerstone Cornerstone event data
+ * @return {Measurement} Measurement instance
+ */
+ toMeasurement: (
+ csToolsEventDetail,
+ displaySetService,
+ CornerstoneViewportService,
+ getValueTypeFromToolType
+ ) => {
+ const { annotation, viewportId } = csToolsEventDetail;
+ const { metadata, data, annotationUID } = annotation;
+
+ if (!metadata || !data) {
+ console.warn('Length tool: Missing metadata or data');
+ return null;
+ }
+
+ const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
+ const validToolType = SUPPORTED_TOOLS.includes(toolName);
+
+ if (!validToolType) {
+ throw new Error('Tool not supported');
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
+ referencedImageId,
+ CornerstoneViewportService,
+ viewportId
+ );
+
+ let displaySet;
+
+ if (SOPInstanceUID) {
+ displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID
+ );
+ } else {
+ displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
+ }
+
+ const { points } = data.handles;
+
+ const mappedAnnotations = getMappedAnnotations(
+ annotation,
+ displaySetService
+ );
+
+ const displayText = getDisplayText(mappedAnnotations, displaySet);
+ const getReport = () =>
+ _getReport(mappedAnnotations, points, FrameOfReferenceUID);
+
+ return {
+ uid: annotationUID,
+ SOPInstanceUID,
+ FrameOfReferenceUID,
+ points,
+ metadata,
+ referenceSeriesUID: SeriesInstanceUID,
+ referenceStudyUID: StudyInstanceUID,
+ frameNumber: mappedAnnotations?.[0]?.frameNumber || 1,
+ toolName: metadata.toolName,
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
+ label: data.label,
+ displayText: displayText,
+ data: data.cachedStats,
+ type: getValueTypeFromToolType(toolName),
+ getReport,
+ };
+ },
+};
+
+function getMappedAnnotations(annotation, DisplaySetService) {
+ const { metadata, data } = annotation;
+ const { cachedStats } = data;
+ const { referencedImageId } = metadata;
+ const targets = Object.keys(cachedStats);
+
+ if (!targets.length) {
+ return;
+ }
+
+ const annotations = [];
+ Object.keys(cachedStats).forEach(targetId => {
+ const targetStats = cachedStats[targetId];
+
+ if (!referencedImageId) {
+ throw new Error(
+ 'Non-acquisition plane measurement mapping not supported'
+ );
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber,
+ } = getSOPInstanceAttributes(referencedImageId);
+
+ const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber
+ );
+
+ const { SeriesNumber } = displaySet;
+ const { angle } = targetStats;
+ const unit = '\u00B0';
+
+ annotations.push({
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SeriesNumber,
+ frameNumber,
+ unit,
+ angle,
+ });
+ });
+
+ return annotations;
+}
+
+/*
+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) {
+ const columns = [];
+ const values = [];
+
+ // Add Type
+ columns.push('AnnotationType');
+ values.push('Cornerstone:Angle');
+
+ mappedAnnotations.forEach(annotation => {
+ const { angle, unit } = annotation;
+ columns.push(`Angle (${unit})`);
+ values.push(angle);
+ });
+
+ if (FrameOfReferenceUID) {
+ columns.push('FrameOfReferenceUID');
+ values.push(FrameOfReferenceUID);
+ }
+
+ if (points) {
+ columns.push('points');
+ // 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
+ values.push(points.map(p => p.join(' ')).join(';'));
+ }
+
+ return {
+ columns,
+ values,
+ };
+}
+
+function getDisplayText(mappedAnnotations, displaySet) {
+ if (!mappedAnnotations || !mappedAnnotations.length) {
+ return '';
+ }
+
+ const displayText = [];
+
+ // Area is the same for all series
+ const {
+ angle,
+ unit,
+ SeriesNumber,
+ SOPInstanceUID,
+ frameNumber,
+ } = mappedAnnotations[0];
+
+ 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}` : '';
+ if (angle === undefined) return displayText;
+ const roundedAngle = utils.roundNumber(angle, 2);
+ displayText.push(
+ `${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
+ );
+
+ return displayText;
+}
+
+export default Angle;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts
new file mode 100644
index 000000000..53dace2b4
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts
@@ -0,0 +1,210 @@
+import SUPPORTED_TOOLS from './constants/supportedTools';
+import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
+import { utils } from '@ohif/core';
+
+const CobbAngle = {
+ toAnnotation: measurement => { },
+
+ /**
+ * Maps cornerstone annotation event data to measurement service format.
+ *
+ * @param {Object} cornerstone Cornerstone event data
+ * @return {Measurement} Measurement instance
+ */
+ toMeasurement: (
+ csToolsEventDetail,
+ displaySetService,
+ CornerstoneViewportService,
+ getValueTypeFromToolType
+ ) => {
+ const { annotation, viewportId } = csToolsEventDetail;
+ const { metadata, data, annotationUID } = annotation;
+
+ if (!metadata || !data) {
+ console.warn('Cobb Angle tool: Missing metadata or data');
+ return null;
+ }
+
+ const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
+ const validToolType = SUPPORTED_TOOLS.includes(toolName);
+
+ if (!validToolType) {
+ throw new Error('Tool not supported');
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
+ referencedImageId,
+ CornerstoneViewportService,
+ viewportId
+ );
+
+ let displaySet;
+
+ if (SOPInstanceUID) {
+ displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID
+ );
+ } else {
+ displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
+ }
+
+ const { points } = data.handles;
+
+ const mappedAnnotations = getMappedAnnotations(
+ annotation,
+ displaySetService
+ );
+
+ const displayText = getDisplayText(mappedAnnotations, displaySet);
+ const getReport = () =>
+ _getReport(mappedAnnotations, points, FrameOfReferenceUID);
+
+ return {
+ uid: annotationUID,
+ SOPInstanceUID,
+ FrameOfReferenceUID,
+ points,
+ metadata,
+ referenceSeriesUID: SeriesInstanceUID,
+ referenceStudyUID: StudyInstanceUID,
+ frameNumber: mappedAnnotations?.[0]?.frameNumber || 1,
+ toolName: metadata.toolName,
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
+ label: data.label,
+ displayText: displayText,
+ data: data.cachedStats,
+ type: getValueTypeFromToolType(toolName),
+ getReport,
+ };
+ },
+};
+
+function getMappedAnnotations(annotation, DisplaySetService) {
+ const { metadata, data } = annotation;
+ const { cachedStats } = data;
+ const { referencedImageId } = metadata;
+ const targets = Object.keys(cachedStats);
+
+ if (!targets.length) {
+ return;
+ }
+
+ const annotations = [];
+ Object.keys(cachedStats).forEach(targetId => {
+ const targetStats = cachedStats[targetId];
+
+ if (!referencedImageId) {
+ throw new Error(
+ 'Non-acquisition plane measurement mapping not supported'
+ );
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber,
+ } = getSOPInstanceAttributes(referencedImageId);
+
+ const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber
+ );
+
+ const { SeriesNumber } = displaySet;
+ const { angle } = targetStats;
+ const unit = '\u00B0';
+
+ annotations.push({
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SeriesNumber,
+ frameNumber,
+ unit,
+ angle,
+ });
+ });
+
+ return annotations;
+}
+
+/*
+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) {
+ const columns = [];
+ const values = [];
+
+ // Add Type
+ columns.push('AnnotationType');
+ values.push('Cornerstone:CobbAngle');
+
+ mappedAnnotations.forEach(annotation => {
+ const { angle, unit } = annotation;
+ columns.push(`Angle (${unit})`);
+ values.push(angle);
+ });
+
+ if (FrameOfReferenceUID) {
+ columns.push('FrameOfReferenceUID');
+ values.push(FrameOfReferenceUID);
+ }
+
+ if (points) {
+ columns.push('points');
+ // 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
+ values.push(points.map(p => p.join(' ')).join(';'));
+ }
+
+ return {
+ columns,
+ values,
+ };
+}
+
+function getDisplayText(mappedAnnotations, displaySet) {
+ if (!mappedAnnotations || !mappedAnnotations.length) {
+ return '';
+ }
+
+ const displayText = [];
+
+ // Area is the same for all series
+ const {
+ angle,
+ unit,
+ SeriesNumber,
+ SOPInstanceUID,
+ frameNumber,
+ } = mappedAnnotations[0];
+
+ 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}` : '';
+ if (angle === undefined) return displayText;
+ const roundedAngle = utils.roundNumber(angle, 2);
+ displayText.push(
+ `${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
+ );
+
+ return displayText;
+}
+
+export default CobbAngle;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts
new file mode 100644
index 000000000..71ded00c1
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts
@@ -0,0 +1,148 @@
+import SUPPORTED_TOOLS from './constants/supportedTools';
+import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
+
+const PlanarFreehandROI = {
+ toAnnotation: measurement => { },
+
+ /**
+ * Maps cornerstone annotation event data to measurement service format.
+ *
+ * @param {Object} cornerstone Cornerstone event data
+ * @return {Measurement} Measurement instance
+ */
+ toMeasurement: (
+ csToolsEventDetail,
+ DisplaySetService,
+ CornerstoneViewportService,
+ getValueTypeFromToolType
+ ) => {
+ const { annotation, viewportId } = csToolsEventDetail;
+ const { metadata, data, annotationUID } = annotation;
+
+ if (!metadata || !data) {
+ console.warn('PlanarFreehandROI tool: Missing metadata or data');
+ return null;
+ }
+
+ const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
+ const validToolType = SUPPORTED_TOOLS.includes(toolName);
+
+ if (!validToolType) {
+ throw new Error('Tool not supported');
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
+ referencedImageId,
+ CornerstoneViewportService,
+ viewportId
+ );
+
+ let displaySet;
+
+ if (SOPInstanceUID) {
+ displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID
+ );
+ } else {
+ displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
+ }
+
+ const { points } = data.handles;
+
+ const mappedAnnotations = getMappedAnnotations(
+ annotation,
+ DisplaySetService
+ );
+
+ const displayText = getDisplayText(mappedAnnotations);
+ const getReport = () =>
+ _getReport(mappedAnnotations, points, FrameOfReferenceUID);
+
+ return {
+ uid: annotationUID,
+ SOPInstanceUID,
+ FrameOfReferenceUID,
+ points,
+ metadata,
+ referenceSeriesUID: SeriesInstanceUID,
+ referenceStudyUID: StudyInstanceUID,
+ toolName: metadata.toolName,
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
+ label: data.label,
+ displayText: displayText,
+ data: { ...data, ...data.cachedStats },
+ type: getValueTypeFromToolType(toolName),
+ getReport,
+ };
+ },
+};
+
+/**
+ * It maps an imaging library annotation to a list of simplified annotation properties.
+ *
+ * @param {Object} annotationData
+ * @param {Object} DisplaySetService
+ * @returns
+ */
+function getMappedAnnotations(annotationData, DisplaySetService) {
+ const { metadata, data } = annotationData;
+ const { label } = data;
+ const { referencedImageId } = metadata;
+
+ const annotations = [];
+
+ const {
+ SOPInstanceUID: _SOPInstanceUID,
+ SeriesInstanceUID: _SeriesInstanceUID,
+ } = getSOPInstanceAttributes(referencedImageId) || {};
+
+ if (!_SOPInstanceUID || !_SeriesInstanceUID) {
+ return annotations;
+ }
+
+ const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ _SOPInstanceUID,
+ _SeriesInstanceUID
+ );
+
+ const { SeriesNumber, SeriesInstanceUID } = displaySet;
+
+ annotations.push({
+ SeriesInstanceUID,
+ SeriesNumber,
+ label,
+ data,
+ });
+
+ return annotations;
+}
+
+/**
+ * TBD
+ * 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.
+ * @param {*} mappedAnnotations
+ * @param {*} points
+ * @param {*} FrameOfReferenceUID
+ * @returns Object representing the report's content for this tool.
+ */
+function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
+ const columns = [];
+ const values = [];
+
+ return {
+ columns,
+ values,
+ };
+}
+
+function getDisplayText(mappedAnnotations) {
+ return '';
+}
+
+export default PlanarFreehandROI;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts
new file mode 100644
index 000000000..7560dc137
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts
@@ -0,0 +1,228 @@
+import SUPPORTED_TOOLS from './constants/supportedTools';
+import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
+import getModalityUnit from './utils/getModalityUnit';
+import { utils } from '@ohif/core';
+
+const RectangleROI = {
+ toAnnotation: measurement => { },
+ toMeasurement: (
+ csToolsEventDetail,
+ DisplaySetService,
+ CornerstoneViewportService,
+ getValueTypeFromToolType
+ ) => {
+ const { annotation, viewportId } = csToolsEventDetail;
+ const { metadata, data, annotationUID } = annotation;
+
+ if (!metadata || !data) {
+ console.warn('Rectangle ROI tool: Missing metadata or data');
+ return null;
+ }
+
+ const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
+ const validToolType = SUPPORTED_TOOLS.includes(toolName);
+
+ if (!validToolType) {
+ throw new Error('Tool not supported');
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
+ referencedImageId,
+ CornerstoneViewportService,
+ viewportId
+ );
+
+ let displaySet;
+
+ if (SOPInstanceUID) {
+ displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID
+ );
+ } else {
+ displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
+ }
+
+ const { points } = data.handles;
+
+ const mappedAnnotations = getMappedAnnotations(
+ annotation,
+ DisplaySetService
+ );
+
+ const displayText = getDisplayText(mappedAnnotations, displaySet);
+ const getReport = () =>
+ _getReport(mappedAnnotations, points, FrameOfReferenceUID);
+
+ return {
+ uid: annotationUID,
+ SOPInstanceUID,
+ FrameOfReferenceUID,
+ points,
+ metadata,
+ referenceSeriesUID: SeriesInstanceUID,
+ referenceStudyUID: StudyInstanceUID,
+ frameNumber: mappedAnnotations[0]?.frameNumber || 1,
+ toolName: metadata.toolName,
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
+ label: data.label,
+ displayText: displayText,
+ data: data.cachedStats,
+ type: getValueTypeFromToolType(toolName),
+ getReport,
+ };
+ },
+};
+
+function getMappedAnnotations(annotation, DisplaySetService) {
+ const { metadata, data } = annotation;
+ const { cachedStats } = data;
+ const { referencedImageId } = metadata;
+ const targets = Object.keys(cachedStats);
+
+ if (!targets.length) {
+ return [];
+ }
+
+ const annotations = [];
+ Object.keys(cachedStats).forEach(targetId => {
+ const targetStats = cachedStats[targetId];
+
+ if (!referencedImageId) {
+ // Todo: Non-acquisition plane measurement mapping not supported yet
+ throw new Error(
+ 'Non-acquisition plane measurement mapping not supported'
+ );
+ }
+
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber,
+ } = getSOPInstanceAttributes(referencedImageId);
+
+ const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ frameNumber
+ );
+
+ const { SeriesNumber } = displaySet;
+ const { mean, stdDev, max, area, Modality } = targetStats;
+ const unit = getModalityUnit(Modality);
+
+ annotations.push({
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SeriesNumber,
+ frameNumber,
+ Modality,
+ unit,
+ mean,
+ stdDev,
+ max,
+ area,
+ });
+ });
+
+ return annotations;
+}
+
+/*
+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) {
+ const columns = [];
+ const values = [];
+
+ // Add Type
+ columns.push('AnnotationType');
+ values.push('Cornerstone:EllipticalROI');
+
+ mappedAnnotations.forEach(annotation => {
+ const { mean, stdDev, max, area, unit } = annotation;
+
+ if (!mean || !unit || !max || !area) {
+ return;
+ }
+
+ columns.push(
+ `max (${unit})`,
+ `mean (${unit})`,
+ `std (${unit})`,
+ `area (mm2)`
+ );
+ values.push(max, mean, stdDev, area);
+ });
+
+ if (FrameOfReferenceUID) {
+ columns.push('FrameOfReferenceUID');
+ values.push(FrameOfReferenceUID);
+ }
+
+ if (points) {
+ columns.push('points');
+ // 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
+ values.push(points.map(p => p.join(' ')).join(';'));
+ }
+
+ return {
+ columns,
+ values,
+ };
+}
+
+function getDisplayText(mappedAnnotations, displaySet) {
+ if (!mappedAnnotations || !mappedAnnotations.length) {
+ return '';
+ }
+
+ const displayText = [];
+
+ // Area is the same for all series
+ const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+
+ 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}` : '';
+
+ // Area sometimes becomes undefined if `preventHandleOutsideImage` is off.
+ const roundedArea = utils.roundNumber(area || 0, 2);
+ displayText.push(`${roundedArea} mm2`);
+
+ // Todo: we need a better UI for displaying all these information
+ mappedAnnotations.forEach(mappedAnnotation => {
+ const { unit, max, SeriesNumber } = mappedAnnotation;
+
+ let maxStr = '';
+ if (max) {
+ const roundedMax = utils.roundNumber(max, 2);
+ maxStr = `Max: ${roundedMax} ${unit} `;
+ }
+
+ const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
+ if (!displayText.includes(str)) {
+ displayText.push(str);
+ }
+ });
+
+ return displayText;
+}
+
+export default RectangleROI;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
index ed45a00c9..00be9ae14 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js
@@ -1 +1,11 @@
-export default ['Length', 'EllipticalROI', 'Bidirectional', 'ArrowAnnotate'];
+export default [
+ 'Length',
+ 'EllipticalROI',
+ 'Bidirectional',
+ 'ArrowAnnotate',
+ 'Angle',
+ 'CobbAngle',
+ 'Probe',
+ 'RectangleROI',
+ 'PlanarFreehandROI',
+];
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/index.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/index.ts
new file mode 100644
index 000000000..795c744b8
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/index.ts
@@ -0,0 +1,3 @@
+import * as measurementMappingUtils from './utils';
+
+export { measurementMappingUtils };
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
similarity index 52%
rename from extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js
rename to extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
index 1e2f91daf..4d5c77f17 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts
@@ -1,19 +1,24 @@
+import { MeasurementService } from '@ohif/core';
import Length from './Length';
import Bidirectional from './Bidirectional';
import EllipticalROI from './EllipticalROI';
import ArrowAnnotate from './ArrowAnnotate';
+import CobbAngle from './CobbAngle';
+import Angle from './Angle';
+import PlanarFreehandROI from './PlanarFreehandROI';
+import RectangleROI from './RectangleROI';
const measurementServiceMappingsFactory = (
- measurementService,
+ measurementService: MeasurementService,
displaySetService,
cornerstoneViewportService
) => {
/**
* Maps measurement service format object to cornerstone annotation object.
*
- * @param {Measurement} measurement The measurement instance
- * @param {string} definition The source definition
- * @return {Object} Cornerstone annotation data
+ * @param measurement The measurement instance
+ * @param definition The source definition
+ * @return Cornerstone annotation data
*/
const _getValueTypeFromToolType = toolType => {
@@ -23,7 +28,8 @@ const measurementServiceMappingsFactory = (
RECTANGLE,
BIDIRECTIONAL,
POINT,
- } = measurementService.VALUE_TYPES;
+ ANGLE,
+ } = MeasurementService.VALUE_TYPES;
// TODO -> I get why this was attempted, but its not nearly flexible enough.
// A single measurement may have an ellipse + a bidirectional measurement, for instances.
@@ -32,14 +38,17 @@ const measurementServiceMappingsFactory = (
Length: POLYLINE,
EllipticalROI: ELLIPSE,
RectangleROI: RECTANGLE,
+ PlanarFreehandROI: POLYLINE,
Bidirectional: BIDIRECTIONAL,
ArrowAnnotate: POINT,
+ CobbAngle: ANGLE,
+ Angle: ANGLE,
};
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
};
- return {
+ const factories = {
Length: {
toAnnotation: Length.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -51,7 +60,7 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
- valueType: measurementService.VALUE_TYPES.POLYLINE,
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
],
@@ -69,15 +78,16 @@ const measurementServiceMappingsFactory = (
// TODO -> We should eventually do something like shortAxis + longAxis,
// But its still a little unclear how these automatic interpretations will work.
{
- valueType: measurementService.VALUE_TYPES.POLYLINE,
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
{
- valueType: measurementService.VALUE_TYPES.POLYLINE,
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
],
},
+
EllipticalROI: {
toAnnotation: EllipticalROI.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -89,10 +99,43 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
- valueType: measurementService.VALUE_TYPES.ELLIPSE,
+ valueType: MeasurementService.VALUE_TYPES.ELLIPSE,
},
],
},
+
+ RectangleROI: {
+ toAnnotation: RectangleROI.toAnnotation,
+ toMeasurement: csToolsAnnotation =>
+ RectangleROI.toMeasurement(
+ csToolsAnnotation,
+ displaySetService,
+ cornerstoneViewportService,
+ _getValueTypeFromToolType
+ ),
+ matchingCriteria: [
+ {
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
+ },
+ ],
+ },
+
+ PlanarFreehandROI: {
+ toAnnotation: PlanarFreehandROI.toAnnotation,
+ toMeasurement: csToolsAnnotation =>
+ PlanarFreehandROI.toMeasurement(
+ csToolsAnnotation,
+ displaySetService,
+ cornerstoneViewportService,
+ _getValueTypeFromToolType
+ ),
+ matchingCriteria: [
+ {
+ valueType: MeasurementService.VALUE_TYPES.POLYLINE,
+ },
+ ],
+ },
+
ArrowAnnotate: {
toAnnotation: ArrowAnnotate.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -104,12 +147,46 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
- valueType: measurementService.VALUE_TYPES.POINT,
+ valueType: MeasurementService.VALUE_TYPES.POINT,
points: 1,
},
],
},
+
+ CobbAngle: {
+ toAnnotation: CobbAngle.toAnnotation,
+ toMeasurement: csToolsAnnotation =>
+ CobbAngle.toMeasurement(
+ csToolsAnnotation,
+ displaySetService,
+ cornerstoneViewportService,
+ _getValueTypeFromToolType
+ ),
+ matchingCriteria: [
+ {
+ valueType: MeasurementService.VALUE_TYPES.ANGLE,
+ },
+ ],
+ },
+
+ Angle: {
+ toAnnotation: Angle.toAnnotation,
+ toMeasurement: csToolsAnnotation =>
+ Angle.toMeasurement(
+ csToolsAnnotation,
+ displaySetService,
+ cornerstoneViewportService,
+ _getValueTypeFromToolType
+ ),
+ matchingCriteria: [
+ {
+ valueType: MeasurementService.VALUE_TYPES.ANGLE,
+ },
+ ],
+ },
};
+
+ return factories;
};
export default measurementServiceMappingsFactory;
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts
new file mode 100644
index 000000000..c31392196
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts
@@ -0,0 +1,17 @@
+import getHandlesFromPoints from './getHandlesFromPoints';
+import {
+ isAnnotationSelected,
+ setAnnotationSelected,
+ getFirstAnnotationSelected,
+} from './selection';
+import getModalityUnit from './getModalityUnit';
+import getSOPInstanceAttributes from './getSOPInstanceAttributes';
+
+export {
+ getModalityUnit,
+ getHandlesFromPoints,
+ getSOPInstanceAttributes,
+ isAnnotationSelected,
+ setAnnotationSelected,
+ getFirstAnnotationSelected,
+};
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts
new file mode 100644
index 000000000..c70bf832e
--- /dev/null
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts
@@ -0,0 +1,44 @@
+import { annotation as cs3dToolAnnotationUtils } from '@cornerstonejs/tools';
+
+/**
+ * Check whether an annotation from imaging library is selected or not.
+ * @param {string} annotationUID uid of imaging library annotation
+ * @returns boolean
+ */
+function isAnnotationSelected(annotationUID: string): boolean {
+ return cs3dToolAnnotationUtils.selection.isAnnotationSelected(annotationUID);
+}
+
+/**
+ * Change an annotation from imaging library's selected property.
+ * @param annotationUID - uid of imaging library annotation
+ * @param selected - new value for selected
+ */
+function setAnnotationSelected(annotationUID: string, selected: boolean): void {
+ const isCurrentSelected = isAnnotationSelected(annotationUID);
+ // branch cut, avoid invoking imaging library unnecessarily.
+ if (isCurrentSelected !== selected) {
+ cs3dToolAnnotationUtils.selection.setAnnotationSelected(
+ annotationUID,
+ selected
+ );
+ }
+}
+
+function getFirstAnnotationSelected(element) {
+ const [selectedAnnotationUID] =
+ cs3dToolAnnotationUtils.selection.getAnnotationsSelected() || [];
+
+ if (selectedAnnotationUID) {
+ return cs3dToolAnnotationUtils.state.getAnnotation(
+ selectedAnnotationUID,
+ element
+ );
+ }
+}
+
+export {
+ isAnnotationSelected,
+ setAnnotationSelected,
+ getFirstAnnotationSelected,
+};
diff --git a/extensions/default/package.json b/extensions/default/package.json
index 8c15e721a..80332ef34 100644
--- a/extensions/default/package.json
+++ b/extensions/default/package.json
@@ -32,8 +32,8 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/i18n": "^1.0.0",
- "dcmjs": "0.29.4",
- "dicomweb-client": "^0.6.0",
+ "dcmjs": "^0.29.4",
+ "dicomweb-client": "^0.8.4",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx
index be95aa0b2..c2314bf08 100644
--- a/extensions/default/src/Panels/PanelMeasurementTable.tsx
+++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx
@@ -1,15 +1,15 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
+import { utils, ServicesManager } from '@ohif/core';
import { MeasurementTable, Dialog, Input, useViewportGrid } from '@ohif/ui';
import ActionButtons from './ActionButtons';
import debounce from 'lodash.debounce';
-import { utils } from '@ohif/core';
import createReportDialogPrompt, {
CREATE_REPORT_DIALOG_RESPONSE,
} from './createReportDialogPrompt';
import createReportAsync from '../Actions/createReportAsync';
-import getNextSRSeriesNumber from '../utils/getNextSRSeriesNumber';
+import findSRWithSameSeriesDescription from '../utils/findSRWithSameSeriesDescription';
const { downloadCSVReport } = utils;
@@ -17,7 +17,7 @@ export default function PanelMeasurementTable({
servicesManager,
commandsManager,
extensionManager,
-}) {
+}): React.FunctionComponent {
const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportIndex, viewports } = viewportGrid;
const {
@@ -25,7 +25,7 @@ export default function PanelMeasurementTable({
uiDialogService,
uiNotificationService,
displaySetService,
- } = servicesManager.services;
+ } = (servicesManager as ServicesManager).services;
const [displayMeasurements, setDisplayMeasurements] = useState([]);
useEffect(() => {
@@ -72,7 +72,7 @@ export default function PanelMeasurementTable({
measurementService.clearMeasurements();
}
- async function createReport() {
+ async function createReport(): Promise {
// filter measurements that are added to the active study
const activeViewport = viewports[activeViewportIndex];
const measurements = measurementService.getMeasurements();
@@ -109,17 +109,19 @@ export default function PanelMeasurementTable({
? 'Research Derived Series' // default
: promptResult.value; // provided value
- const SeriesNumber = getNextSRSeriesNumber(displaySetService);
+ // Re-use an existing series having the same series description to avoid
+ // creating too many series instances.
+ const options = findSRWithSameSeriesDescription(
+ SeriesDescription,
+ displaySetService
+ );
- const displaySetInstanceUIDs = await createReportAsync(
+ return createReportAsync(
servicesManager,
commandsManager,
dataSource,
trackedMeasurements,
- {
- SeriesDescription,
- SeriesNumber,
- }
+ options
);
}
}
@@ -233,16 +235,7 @@ export default function PanelMeasurementTable({
}
PanelMeasurementTable.propTypes = {
- servicesManager: PropTypes.shape({
- services: PropTypes.shape({
- measurementService: PropTypes.shape({
- getMeasurements: PropTypes.func.isRequired,
- subscribe: PropTypes.func.isRequired,
- EVENTS: PropTypes.object.isRequired,
- VALUE_TYPES: PropTypes.object.isRequired,
- }).isRequired,
- }).isRequired,
- }).isRequired,
+ servicesManager: PropTypes.instanceOf(ServicesManager).isRequired,
};
function _getMappedMeasurements(measurementService) {
diff --git a/extensions/default/src/utils/findSRWithSameSeriesDescription.ts b/extensions/default/src/utils/findSRWithSameSeriesDescription.ts
new file mode 100644
index 000000000..476343fb5
--- /dev/null
+++ b/extensions/default/src/utils/findSRWithSameSeriesDescription.ts
@@ -0,0 +1,49 @@
+import { DisplaySetService, Types } from '@ohif/core';
+
+import getNextSRSeriesNumber from './getNextSRSeriesNumber';
+
+/**
+ * Find an SR having the same series description.
+ * This is used by the store service in order to store DICOM SR's having the
+ * same Series Description into a single series under consecutive instance numbers
+ * That way, they are all organized as a set and could have tools to view
+ * "prior" SR instances.
+ *
+ * @param SeriesDescription - is the description to look for
+ * @param displaySetService - the display sets to search for DICOM SR in
+ * @returns SeriesMetadata from a DICOM SR having the same series description
+ */
+export default function findSRWithSameSeriesDescription(
+ SeriesDescription: string,
+ displaySetService: DisplaySetService
+): Types.SeriesMetadata {
+ const activeDisplaySets = displaySetService.getActiveDisplaySets();
+ const srDisplaySets = activeDisplaySets.filter(ds => ds.Modality === 'SR');
+ const sameSeries = srDisplaySets.find(
+ ds => ds.SeriesDescription === SeriesDescription
+ );
+ if (sameSeries) {
+ console.log('Storing to same series', sameSeries);
+ const { instance } = sameSeries;
+ const {
+ SeriesInstanceUID,
+ SeriesDescription,
+ SeriesDate,
+ SeriesTime,
+ SeriesNumber,
+ Modality,
+ } = instance;
+ return {
+ SeriesInstanceUID,
+ SeriesDescription,
+ SeriesDate,
+ SeriesTime,
+ SeriesNumber,
+ Modality,
+ InstanceNumber: sameSeries.others.length + 1,
+ };
+ }
+
+ const SeriesNumber = getNextSRSeriesNumber(displaySetService);
+ return { SeriesDescription, SeriesNumber };
+}
diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json
index 50aa40246..85684879c 100644
--- a/extensions/dicom-pdf/package.json
+++ b/extensions/dicom-pdf/package.json
@@ -30,7 +30,7 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json
index a03edeeeb..83f4e060b 100644
--- a/extensions/dicom-video/package.json
+++ b/extensions/dicom-video/package.json
@@ -30,7 +30,7 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json
index 89ef83c8a..86f91e615 100644
--- a/extensions/measurement-tracking/package.json
+++ b/extensions/measurement-tracking/package.json
@@ -32,10 +32,10 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"classnames": "^2.3.2",
- "@cornerstonejs/core": "0.30.1",
- "@cornerstonejs/tools": "0.39.0",
+ "@cornerstonejs/core": "^0.30.1",
+ "@cornerstonejs/tools": "^0.44.0",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json
index f5b5a0255..9210d7cfa 100644
--- a/extensions/tmtv/package.json
+++ b/extensions/tmtv/package.json
@@ -30,7 +30,7 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js
index 105b1a2f8..a5af64e77 100644
--- a/modes/longitudinal/src/initToolGroups.js
+++ b/modes/longitudinal/src/initToolGroups.js
@@ -35,6 +35,8 @@ function initDefaultToolGroup(
{ toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle },
+ { toolName: toolNames.CobbAngle },
+ { toolName: toolNames.PlanarFreehandROI },
{ toolName: toolNames.Magnify },
{ toolName: toolNames.SegmentationDisplay },
],
@@ -173,6 +175,8 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) {
{ toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle },
+ { toolName: toolNames.CobbAngle },
+ { toolName: toolNames.PlanarFreehandROI },
{ toolName: toolNames.SegmentationDisplay },
],
disabled: [
diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js
index a8c8a8535..e62fc2f2e 100644
--- a/modes/longitudinal/src/toolbarButtons.js
+++ b/modes/longitudinal/src/toolbarButtons.js
@@ -493,6 +493,38 @@ const toolbarButtons = [
],
'Angle'
),
+
+ // Next two tools can be added once icons are added
+ // _createToolButton(
+ // 'Cobb Angle',
+ // 'tool-cobb-angle',
+ // 'Cobb Angle',
+ // [
+ // {
+ // commandName: 'setToolActive',
+ // commandOptions: {
+ // toolName: 'CobbAngle',
+ // },
+ // context: 'CORNERSTONE',
+ // },
+ // ],
+ // 'Cobb Angle'
+ // ),
+ // _createToolButton(
+ // 'Planar Freehand ROI',
+ // 'tool-freehand',
+ // 'PlanarFreehandROI',
+ // [
+ // {
+ // commandName: 'setToolActive',
+ // commandOptions: {
+ // toolName: 'PlanarFreehandROI',
+ // },
+ // context: 'CORNERSTONE',
+ // },
+ // ],
+ // 'Planar Freehand ROI'
+ // ),
_createToolButton(
'Magnify',
'tool-magnify',
diff --git a/modes/tmtv/src/initToolGroups.js b/modes/tmtv/src/initToolGroups.js
index 21e14e126..990b70058 100644
--- a/modes/tmtv/src/initToolGroups.js
+++ b/modes/tmtv/src/initToolGroups.js
@@ -34,6 +34,7 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager) {
{ toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle },
+ { toolName: toolNames.CobbAngle },
{ toolName: toolNames.Magnify },
],
enabled: [{ toolName: toolNames.SegmentationDisplay }],
@@ -149,6 +150,7 @@ function initMPRToolGroup(toolNames, Enums, toolGroupService, commandsManager) {
{ toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle },
+ { toolName: toolNames.CobbAngle },
{ toolName: toolNames.SegmentationDisplay },
],
disabled: [{ toolName: toolNames.Crosshairs }],
diff --git a/platform/core/package.json b/platform/core/package.json
index 486fcc36d..f2b11412d 100644
--- a/platform/core/package.json
+++ b/platform/core/package.json
@@ -37,8 +37,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "dcmjs": "0.29.4",
- "dicomweb-client": "^0.6.0",
+ "dcmjs": "^0.29.4",
+ "dicomweb-client": "^0.8.4",
"isomorphic-base64": "^1.0.2",
"lodash.merge": "^4.6.1",
"lodash.clonedeep": "^4.5.0",
diff --git a/platform/core/src/measurements/tools/polygonRoi.js b/platform/core/src/measurements/tools/polygonRoi.js
index 4ebdc4d97..05876e581 100644
--- a/platform/core/src/measurements/tools/polygonRoi.js
+++ b/platform/core/src/measurements/tools/polygonRoi.js
@@ -11,7 +11,7 @@ export const polygonRoi = {
id: 'PolygonRoi',
name: 'Polygon',
toolGroup: 'allTools',
- cornerstoneToolType: 'FreehandRoiTool',
+ cornerstoneToolType: 'PlanarFreehandROITool',
options: {
measurementTable: {
displayFunction,
diff --git a/platform/core/src/services/MeasurementService/MeasurementService.ts b/platform/core/src/services/MeasurementService/MeasurementService.ts
index 64cb53561..1fb9550f2 100644
--- a/platform/core/src/services/MeasurementService/MeasurementService.ts
+++ b/platform/core/src/services/MeasurementService/MeasurementService.ts
@@ -71,6 +71,7 @@ const EVENTS = {
};
const VALUE_TYPES = {
+ ANGLE: 'value_type::polyline',
POLYLINE: 'value_type::polyline',
POINT: 'value_type::point',
BIDIRECTIONAL: 'value_type::shortAxisLongAxis', // TODO -> Discuss with Danny. => just using SCOORD values isn't enough here.
@@ -103,18 +104,15 @@ class MeasurementService extends PubSubService {
},
};
+ public static VALUE_TYPES = VALUE_TYPES;
+ public readonly VALUE_TYPES = VALUE_TYPES;
+
constructor() {
super(EVENTS);
this.sources = {};
this.mappings = {};
this.measurements = {};
this._jumpToMeasurementCache = {};
- Object.defineProperty(this, 'VALUE_TYPES', {
- value: VALUE_TYPES,
- writable: false,
- enumerable: true,
- configurable: false,
- });
}
/**
@@ -529,6 +527,7 @@ class MeasurementService extends PubSubService {
measurement = toMeasurementSchema(sourceAnnotationDetail);
measurement.source = source;
} catch (error) {
+ console.log('Failed to map', error);
throw new Error(
`Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}: ${error.message}`
);
diff --git a/platform/viewer/package.json b/platform/viewer/package.json
index 235a838db..b7fe69b1e 100644
--- a/platform/viewer/package.json
+++ b/platform/viewer/package.json
@@ -66,8 +66,7 @@
"core-js": "^3.16.1",
"cornerstone-math": "^0.1.9",
"cornerstone-wado-image-loader": "^4.2.1",
- "dayjs": "^1.11.6",
- "dcmjs": "0.29.4",
+ "dcmjs": "^0.29.4",
"detect-gpu": "^4.0.16",
"dicom-parser": "^1.8.9",
"dotenv-webpack": "^1.7.0",
diff --git a/testdata b/testdata
index 4d59660c2..1bca96aa2 160000
--- a/testdata
+++ b/testdata
@@ -1 +1 @@
-Subproject commit 4d59660c2883ed749a680e5fb6d4624ab54c9422
+Subproject commit 1bca96aa2aeae7d90b3c8db73845c85dd27b34d5
diff --git a/yarn.lock b/yarn.lock
index 8f2187364..434106df8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1407,10 +1407,10 @@
resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
-"@cornerstonejs/adapters@0.3.0":
- version "0.3.0"
- resolved "https://registry.npmjs.org/@cornerstonejs/adapters/-/adapters-0.3.0.tgz#c0a2a904c92ebfae77520eba4fc64c07ebd06db4"
- integrity sha512-o3GIrccNM0q/QF0//ueVIXeRltUmtTBkTR47qPgIY9gdi+3sYEFHEW6RTXe3OOtDDXOWgM88Jp3mWWUK4ZeXxg==
+"@cornerstonejs/adapters@^0.3.1":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-0.3.1.tgz#50cfaf3de3c5a55f22f7fae1a9f6584a1c343ffa"
+ integrity sha512-+tqMLVYeSZjzwtzbAPqRMq5iI47nrtXz4f3GQ1jKUSmZqQyvgWRzAizcfnl6OEIrgjeg/2MHdSGOEWu12H8M+w==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
dcmjs "^0.29.4"
@@ -1443,7 +1443,15 @@
resolved "https://registry.npmjs.org/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.2.tgz#e96721d56f6ec96f7f95c16321d88cc8467d8d81"
integrity sha512-lgdvBvvNezleY+4pIe2ceUsJzlZe/0PipdeubQ3vZZOz3xxtHHMR1XFCl4fgd8gosR8COHuD7h6q+MwgrwBsng==
-"@cornerstonejs/core@0.30.1", "@cornerstonejs/core@^0.30.1":
+"@cornerstonejs/core@^0.25.1":
+ version "0.25.1"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.25.1.tgz#7e5c70858018d99417b61965ac606754542f77ad"
+ integrity sha512-/Ve9qwGyRRK1uUCrefh9cVG3OIaaS9LQoTUk9YMUbsoWxd6JitXh31XVHjlvNtBYGMRVdz+uGPS2zSxq1vi4aA==
+ dependencies:
+ detect-gpu "^4.0.45"
+ lodash.clonedeep "4.5.0"
+
+"@cornerstonejs/core@^0.30.1":
version "0.30.1"
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.30.1.tgz#6cb7088260641e7e55de2edb11979b1a0c00c794"
integrity sha512-A7t6WtPut17uLGpv3cUfk1gSzEvWiFcCg1K3H0WGySdZsf4tIinb5xA+9oD2eaBPbvTXmIef4HWFIAsN3kAEgg==
@@ -1451,28 +1459,28 @@
detect-gpu "^4.0.45"
lodash.clonedeep "4.5.0"
-"@cornerstonejs/core@^0.28.0":
- version "0.28.0"
- resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.28.0.tgz#bc5114dfd332fcc5caf4ab049bcb08f6457e5f97"
- integrity sha512-DSAdH2+8h/rRCTR6WbbGkY+vqnAVVZH47+GgGQQHscEwDD/K3ZO62JMl4LgcgkDzI0LIIZAk6CgAtok31yG/3Q==
+"@cornerstonejs/core@^0.31.0":
+ version "0.31.0"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.31.0.tgz#087731cc9c1745f02e8936a55956c4ca5d52f3d9"
+ integrity sha512-qJmR2JpPPuSJsIDW7seH+bJrhE/e5QNwuk2T8EzlWtHR3QcB2lSq/o7cX6kGOJDwn4KrE1p/9QZAJ4GaFlBoyQ==
dependencies:
detect-gpu "^4.0.45"
lodash.clonedeep "4.5.0"
-"@cornerstonejs/streaming-image-volume-loader@^0.11.2":
- version "0.11.8"
- resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.11.8.tgz#79dc9bf7f00a3277d9880f919c324cdeda2c28d6"
- integrity sha512-fp+mmCUMWQQFzx9AzzmOJO1ntBStHQmxK3Zbi/sMs9TYcue+Zw14oA+qcuJ/7UZayb+c99unfWjhafjBiHWNeg==
+"@cornerstonejs/streaming-image-volume-loader@^0.8.2":
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.8.3.tgz#9370f1a609c5d83b3be59af2a0f44c57f68efdb0"
+ integrity sha512-p4kQisqQM/hD5yr/TZKSlq2Ux6eOJteuJrJRNaub46bGklU5M2J0TPShEkwpMDPIJeir9hNeQyg7q8fy5IoH+A==
dependencies:
- "@cornerstonejs/core" "^0.30.1"
- cornerstone-wado-image-loader "^4.8.0"
+ "@cornerstonejs/core" "^0.25.1"
+ cornerstone-wado-image-loader "^4.7.0"
-"@cornerstonejs/tools@0.39.0":
- version "0.39.0"
- resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.39.0.tgz#7d9bac4c55bc19145c2051f7c7150b34570849e4"
- integrity sha512-xDR51lOEuNIDaTJltZvxDK+BIdIbznXUsWt+OpYiiygCQYt1Lt2eHufO2wVFVPrMgvm0dryF+WQbRVwtWvx4yA==
+"@cornerstonejs/tools@^0.44.0":
+ version "0.44.0"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-0.44.0.tgz#7bec3dbcd25fce648cab49021c67ca2e3a80b1f1"
+ integrity sha512-/LfxjXwR4o1qzsByk4pf3vgRK+4BJGkth1G+dqYLj06iUsDYX724kR6AodEFZniwXUiSCZO7JDYOyns6fk7+GQ==
dependencies:
- "@cornerstonejs/core" "^0.28.0"
+ "@cornerstonejs/core" "^0.31.0"
lodash.clonedeep "4.5.0"
lodash.get "^4.4.2"
@@ -8544,7 +8552,7 @@ cornerstone-math@^0.1.9:
resolved "https://registry.npmjs.org/cornerstone-math/-/cornerstone-math-0.1.10.tgz#a3f99db64d73c5adee61ae0d570128eca1682d07"
integrity sha512-23XSAyP7t70ANvhFyqwvva+zFd1bQ2d5GL7tg9qKE932WmImjA2Y9tiy5n0iTtnf51W/78Png8Lia2o4dCdJaQ==
-cornerstone-wado-image-loader@^4.2.1, cornerstone-wado-image-loader@^4.8.0:
+cornerstone-wado-image-loader@^4.2.1, cornerstone-wado-image-loader@^4.7.0:
version "4.9.1"
resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.9.1.tgz#442eeca78ff0bcf9f9cbde04e8c429f35b31c55a"
integrity sha512-l0HRxGAupfufnHjT9uFpwAtafvuGMKKB9SCL8dHUwCK+7jwNeVigy8s6+Oki2nycaJUzy2TRQaauO78mIf3grg==
@@ -9259,12 +9267,12 @@ dateformat@^3.0.0:
resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
-dayjs@^1.10.4, dayjs@^1.11.6:
+dayjs@^1.10.4:
version "1.11.7"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
-dcmjs@0.29.4, dcmjs@^0.29.4:
+dcmjs@^0.29.4:
version "0.29.4"
resolved "https://registry.npmjs.org/dcmjs/-/dcmjs-0.29.4.tgz#3fb13945611979f756bc91b3f199fd87b4eabdb4"
integrity sha512-PzD6C4oB7v/AVTIPbkS6D6hjgVcSW7+T+DsxFpkD1xcEgU4qjGM16WGEHH3ZAPDBF0Xgm2d9FXUu/lXZIyfmow==
@@ -9649,10 +9657,10 @@ dicom-parser@^1.8.9:
resolved "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.20.tgz#e5ef817d80d2fbc093ffd8c2a73a1cb5b47f50bd"
integrity sha512-R8NXEcaqXu7Qe5exY662aXiJCbiE6fEF8+QpSkVcXTTecFzfIaW8KJ2tSwYvt0UMHP0AUBW13VxQSXfKv/0FUA==
-dicomweb-client@^0.6.0:
- version "0.6.0"
- resolved "https://registry.npmjs.org/dicomweb-client/-/dicomweb-client-0.6.0.tgz#5e35ada52fe0155af1cc1f0e84f9c7f76477f92d"
- integrity sha512-VAkBg4W6odIo2XsFxqjN/rptd7bQ8oHpRuKH5d46E9BUIPzRschazE8Dx1xg7/l3N3f1M70jB7yJ339arAXiDQ==
+dicomweb-client@^0.8.4:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.8.4.tgz#3da814cedb9415facb50bc5f43af8d961a991c74"
+ integrity sha512-/6oY3/Fg9JyAlbTWuJOYbVqici3+nlZt43+Z/Y47RNiqLc028JcxNlY28u4VQqksxfB59f1hhNbsqsHyDT4vhw==
didyoumean@^1.2.2:
version "1.2.2"