diff --git a/extensions/cornerstone/src/tools/CalibrationLineTool.ts b/extensions/cornerstone/src/tools/CalibrationLineTool.ts
index ae914019bae..3b4a9bede0b 100644
--- a/extensions/cornerstone/src/tools/CalibrationLineTool.ts
+++ b/extensions/cornerstone/src/tools/CalibrationLineTool.ts
@@ -76,11 +76,12 @@ export function onCompletedCalibrationLine(servicesManager, csToolsEvent) {
const adjustCalibration = newLength => {
const spacingScale = newLength / length;
- const rowSpacing = spacingScale * currentRowPixelSpacing;
- const colSpacing = spacingScale * currentColumnPixelSpacing;
// trigger resize of the viewport to adjust the world/pixel mapping
- calibrateImageSpacing(imageId, viewport.getRenderingEngine(), rowSpacing, colSpacing);
+ calibrateImageSpacing(imageId, viewport.getRenderingEngine(), {
+ type: 'User',
+ scale: 1 / spacingScale,
+ });
};
return new Promise((resolve, reject) => {
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts
index a1c214e1c76..8652c7c8a2e 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts
@@ -99,8 +99,7 @@ function getMappedAnnotations(annotation, displaySetService) {
);
const { SeriesNumber } = displaySet;
- const { length, width } = targetStats;
- const unit = 'mm';
+ const { length, width, unit } = targetStats;
annotations.push({
SeriesInstanceUID,
@@ -130,9 +129,9 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
values.push('Cornerstone:Bidirectional');
mappedAnnotations.forEach(annotation => {
- const { length, width } = annotation;
- columns.push(`Length (mm)`, `Width (mm)`);
- values.push(length, width);
+ const { length, width, unit } = annotation;
+ columns.push(`Length`, `Width`, 'Unit');
+ values.push(length, width, unit);
});
if (FrameOfReferenceUID) {
@@ -162,7 +161,14 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
- const { length, width, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const {
+ length,
+ width,
+ unit,
+ SeriesNumber,
+ SOPInstanceUID,
+ frameNumber,
+ } = mappedAnnotations[0];
const roundedLength = utils.roundNumber(length, 2);
const roundedWidth = utils.roundNumber(width, 2);
@@ -176,8 +182,10 @@ function getDisplayText(mappedAnnotations, displaySet) {
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
- displayText.push(`L: ${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})`);
- displayText.push(`W: ${roundedWidth} mm`);
+ displayText.push(
+ `L: ${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
+ );
+ displayText.push(`W: ${roundedWidth} ${unit}`);
return displayText;
}
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts
index ac784878b86..7c8f8f7cb0b 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts
@@ -98,7 +98,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
);
const { SeriesNumber } = displaySet;
- const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats;
+ const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit } = targetStats;
annotations.push({
SeriesInstanceUID,
@@ -111,6 +111,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
stdDev,
max,
area,
+ areaUnit,
});
});
@@ -131,14 +132,20 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
values.push('Cornerstone:CircleROI');
mappedAnnotations.forEach(annotation => {
- const { mean, stdDev, max, area, unit } = annotation;
+ const { mean, stdDev, max, area, unit, areaUnit } = annotation;
if (!mean || !unit || !max || !area) {
return;
}
- columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, `area (mm2)`);
- values.push(max, mean, stdDev, area);
+ columns.push(
+ `max (${unit})`,
+ `mean (${unit})`,
+ `std (${unit})`,
+ 'Area',
+ 'Unit'
+ );
+ values.push(max, mean, stdDev, area, areaUnit);
});
if (FrameOfReferenceUID) {
@@ -168,7 +175,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
- const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
@@ -182,7 +189,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
// Area sometimes becomes undefined if `preventHandleOutsideImage` is off.
const roundedArea = utils.roundNumber(area || 0, 2);
- displayText.push(`${roundedArea} mm2`);
+ displayText.push(`${roundedArea} ${areaUnit}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts
index aa7e5f12977..9bcf67d5dd4 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts
@@ -98,7 +98,7 @@ function getMappedAnnotations(annotation, displaySetService) {
);
const { SeriesNumber } = displaySet;
- const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats;
+ const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit } = targetStats;
annotations.push({
SeriesInstanceUID,
@@ -107,6 +107,7 @@ function getMappedAnnotations(annotation, displaySetService) {
frameNumber,
Modality,
unit: modalityUnit,
+ areaUnit,
mean,
stdDev,
max,
@@ -131,14 +132,20 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
values.push('Cornerstone:EllipticalROI');
mappedAnnotations.forEach(annotation => {
- const { mean, stdDev, max, area, unit } = annotation;
+ const { mean, stdDev, max, area, unit, areaUnit } = annotation;
if (!mean || !unit || !max || !area) {
return;
}
- columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, `area (mm2)`);
- values.push(max, mean, stdDev, area);
+ columns.push(
+ `max (${unit})`,
+ `mean (${unit})`,
+ `std (${unit})`,
+ 'Area',
+ 'Unit'
+ );
+ values.push(max, mean, stdDev, area, areaUnit);
});
if (FrameOfReferenceUID) {
@@ -168,7 +175,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
- const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
@@ -180,9 +187,8 @@ function getDisplayText(mappedAnnotations, displaySet) {
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`);
+ const roundedArea = utils.roundNumber(area, 2);
+ displayText.push(`${roundedArea} ${areaUnit}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts
index 249522e847a..5c5072d2c19 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts
@@ -32,7 +32,11 @@ const Length = {
throw new Error('Tool not supported');
}
- const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
referencedImageId,
cornerstoneViewportService,
viewportId
@@ -94,8 +98,11 @@ 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,
@@ -104,8 +111,7 @@ function getMappedAnnotations(annotation, displaySetService) {
);
const { SeriesNumber } = displaySet;
- const { length } = targetStats;
- const unit = 'mm';
+ const { length, unit = 'mm' } = targetStats;
annotations.push({
SeriesInstanceUID,
@@ -134,9 +140,11 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
values.push('Cornerstone:Length');
mappedAnnotations.forEach(annotation => {
- const { length } = annotation;
- columns.push(`Length (mm)`);
+ const { length, unit } = annotation;
+ columns.push(`Length`);
values.push(length);
+ columns.push('Unit');
+ values.push(unit);
});
if (FrameOfReferenceUID) {
@@ -166,7 +174,13 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
- const { length, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const {
+ length,
+ SeriesNumber,
+ SOPInstanceUID,
+ frameNumber,
+ unit,
+ } = mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
@@ -182,7 +196,9 @@ function getDisplayText(mappedAnnotations, displaySet) {
return displayText;
}
const roundedLength = utils.roundNumber(length, 2);
- displayText.push(`${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})`);
+ displayText.push(
+ `${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
+ );
return displayText;
}
diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts
index 035b87457c6..567badd1d2d 100644
--- a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts
+++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts
@@ -25,7 +25,11 @@ const RectangleROI = {
throw new Error('Tool not supported');
}
- const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
+ const {
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ } = getSOPInstanceAttributes(
referencedImageId,
CornerstoneViewportService,
viewportId
@@ -88,8 +92,11 @@ 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,
@@ -98,7 +105,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
);
const { SeriesNumber } = displaySet;
- const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats;
+ const { mean, stdDev, max, area, Modality, modalityUnit, areaUnit } = targetStats;
annotations.push({
SeriesInstanceUID,
@@ -111,6 +118,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
stdDev,
max,
area,
+ areaUnit,
});
});
@@ -131,14 +139,14 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
values.push('Cornerstone:RectangleROI');
mappedAnnotations.forEach(annotation => {
- const { mean, stdDev, max, area, unit } = annotation;
+ const { mean, stdDev, max, area, unit, areaUnit } = annotation;
if (!mean || !unit || !max || !area) {
return;
}
- columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, `area (mm2)`);
- values.push(max, mean, stdDev, area);
+ columns.push(`Maximum`, `Mean`, `Std Dev`, 'Pixel Unit', `Area`, 'Unit');
+ values.push(max, mean, stdDev, unit, area, areaUnit);
});
if (FrameOfReferenceUID) {
@@ -168,7 +176,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
- const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
+ const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
@@ -182,7 +190,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
// Area sometimes becomes undefined if `preventHandleOutsideImage` is off.
const roundedArea = utils.roundNumber(area || 0, 2);
- displayText.push(`${roundedArea} mm2`);
+ displayText.push(`${roundedArea} ${areaUnit}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.ts
similarity index 89%
rename from platform/core/src/classes/MetadataProvider.js
rename to platform/core/src/classes/MetadataProvider.ts
index 41e6cdbc6db..7b59c3ebd68 100644
--- a/platform/core/src/classes/MetadataProvider.js
+++ b/platform/core/src/classes/MetadataProvider.ts
@@ -68,7 +68,7 @@ class MetadataProvider {
if (!instance) {
return;
- }
+ }
return (frameNumber && combineFrameInstance(frameNumber, instance)) || instance;
}
@@ -114,8 +114,20 @@ class MetadataProvider {
return this._getCornerstoneDICOMImageLoaderTag(naturalizedTagOrWADOImageLoaderTag, instance);
}
+ /**
+ * Adds a new handler for the given tag. The handler will be provided an
+ * instance object that it can read values from.
+ */
+ public addHandler(
+ wadoImageLoaderTag: string,
+ handler
+ ) {
+ WADO_IMAGE_LOADER[wadoImageLoaderTag] = handler;
+ }
+
_getCornerstoneDICOMImageLoaderTag(wadoImageLoaderTag, instance) {
- let metadata;
+ let metadata = WADO_IMAGE_LOADER[wadoImageLoaderTag]?.(instance);
+ if (metadata) return metadata;
switch (wadoImageLoaderTag) {
case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE:
@@ -153,45 +165,6 @@ class MetadataProvider {
patientSex: instance.PatientSex,
};
break;
- case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE:
- const { ImageOrientationPatient } = instance;
-
- // Fallback for DX images.
- // TODO: We should use the rest of the results of this function
- // to update the UI somehow
- const { PixelSpacing } = getPixelSpacingInformation(instance);
-
- let rowPixelSpacing;
- let columnPixelSpacing;
-
- let rowCosines;
- let columnCosines;
-
- if (PixelSpacing) {
- rowPixelSpacing = PixelSpacing[0];
- columnPixelSpacing = PixelSpacing[1];
- }
-
- if (ImageOrientationPatient) {
- rowCosines = ImageOrientationPatient.slice(0, 3);
- columnCosines = ImageOrientationPatient.slice(3, 6);
- }
-
- metadata = {
- frameOfReferenceUID: instance.FrameOfReferenceUID,
- rows: toNumber(instance.Rows),
- columns: toNumber(instance.Columns),
- imageOrientationPatient: toNumber(ImageOrientationPatient),
- rowCosines: toNumber(rowCosines || [0, 1, 0]),
- columnCosines: toNumber(columnCosines || [0, 0, -1]),
- imagePositionPatient: toNumber(instance.ImagePositionPatient || [0, 0, 0]),
- sliceThickness: toNumber(instance.SliceThickness),
- sliceLocation: toNumber(instance.SliceLocation),
- pixelSpacing: toNumber(PixelSpacing || 1),
- rowPixelSpacing: toNumber(rowPixelSpacing || 1),
- columnPixelSpacing: toNumber(columnPixelSpacing || 1),
- };
- break;
case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE:
metadata = {
samplesPerPixel: toNumber(instance.SamplesPerPixel),
@@ -484,11 +457,54 @@ const metadataProvider = new MetadataProvider();
export default metadataProvider;
+const WADO_IMAGE_LOADER = {
+ imagePlaneModule: instance => {
+ const { ImageOrientationPatient } = instance;
+
+ // Fallback for DX images.
+ // TODO: We should use the rest of the results of this function
+ // to update the UI somehow
+ const { PixelSpacing } = getPixelSpacingInformation(instance);
+
+ let rowPixelSpacing;
+ let columnPixelSpacing;
+
+ let rowCosines;
+ let columnCosines;
+
+ if (PixelSpacing) {
+ rowPixelSpacing = PixelSpacing[0];
+ columnPixelSpacing = PixelSpacing[1];
+ }
+
+ if (ImageOrientationPatient) {
+ rowCosines = ImageOrientationPatient.slice(0, 3);
+ columnCosines = ImageOrientationPatient.slice(3, 6);
+ }
+
+ return {
+ frameOfReferenceUID: instance.FrameOfReferenceUID,
+ rows: toNumber(instance.Rows),
+ columns: toNumber(instance.Columns),
+ imageOrientationPatient: toNumber(ImageOrientationPatient),
+ rowCosines: toNumber(rowCosines || [0, 1, 0]),
+ columnCosines: toNumber(columnCosines || [0, 0, -1]),
+ imagePositionPatient: toNumber(
+ instance.ImagePositionPatient || [0, 0, 0]
+ ),
+ sliceThickness: toNumber(instance.SliceThickness),
+ sliceLocation: toNumber(instance.SliceLocation),
+ pixelSpacing: toNumber(PixelSpacing || 1),
+ rowPixelSpacing: rowPixelSpacing ? toNumber(rowPixelSpacing) : null,
+ columnPixelSpacing: columnPixelSpacing ? toNumber(columnPixelSpacing) : null,
+ };
+ },
+};
+
const WADO_IMAGE_LOADER_TAGS = {
// dicomImageLoader specific
GENERAL_SERIES_MODULE: 'generalSeriesModule',
PATIENT_STUDY_MODULE: 'patientStudyModule',
- IMAGE_PLANE_MODULE: 'imagePlaneModule',
IMAGE_PIXEL_MODULE: 'imagePixelModule',
VOI_LUT_MODULE: 'voiLutModule',
MODALITY_LUT_MODULE: 'modalityLutModule',
diff --git a/platform/core/src/utils/roundNumber.js b/platform/core/src/utils/roundNumber.js
index a441225da50..be76c7edea3 100644
--- a/platform/core/src/utils/roundNumber.js
+++ b/platform/core/src/utils/roundNumber.js
@@ -1,9 +1,34 @@
/**
- * @param {string | number} value
- * @param {number} decimals
+ * Truncates decimal points to that there is at least 1+precision significant
+ * digits.
+ *
+ * For example, with the default precision 2 (3 significant digits)
+ * * Values larger than 100 show no information after the decimal point
+ * * Values between 10 and 99 show 1 decimal point
+ * * Values between 1 and 9 show 2 decimal points
+ *
+ * @param value - to return a fixed measurement value from
+ * @param precision - defining how many digits after 1..9 are desired
*/
-function _round(value, decimals) {
- return Number(value).toFixed(decimals);
+function roundNumber(value: string | number, precision = 2): string {
+ if (value === undefined || value === null || value === '') return 'NaN';
+ value = Number(value);
+ if (value < 0.0001) return `${value}`;
+ const fixedPrecision =
+ value >= 100
+ ? precision - 2
+ : value >= 10
+ ? precision - 1
+ : value >= 1
+ ? precision
+ : value >= 0.1
+ ? precision + 1
+ : value >= 0.01
+ ? precision + 2
+ : value >= 0.001
+ ? precision + 3
+ : precision + 4;
+ return value.toFixed(fixedPrecision);
}
-export default _round;
+export default roundNumber;