From f1cc30f566ed21d1dee5edd769794ec3aac4c660 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 2 Jun 2023 16:17:40 -0400 Subject: [PATCH 01/37] feat(measurement): Add calibration type labels (ERMF, PROJ, USER) --- .../core/src/RenderingEngine/StackViewport.ts | 5 ++++ packages/core/src/RenderingEngine/Viewport.ts | 2 ++ packages/core/src/enums/CalibrationTypes.ts | 9 +++++++ packages/core/src/enums/index.ts | 2 ++ packages/core/src/types/CPUIImageData.ts | 3 +++ packages/core/src/types/IImageCalibration.ts | 15 ++++++++++++ packages/core/src/types/IImageData.ts | 4 ++++ packages/core/src/types/index.ts | 2 ++ packages/tools/src/enums/index.js | 3 +++ .../tools/src/tools/annotation/LengthTool.ts | 24 ++++++++++++++++--- 10 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/enums/CalibrationTypes.ts create mode 100644 packages/core/src/types/IImageCalibration.ts diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 2bf34a4263..212ced1683 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -410,6 +410,7 @@ class StackViewport extends Viewport implements IStackViewport { metadata: { Modality: this.modality }, scaling: this.scaling, hasPixelSpacing: this.hasPixelSpacing, + calibration: this.calibration, preScale: { ...this.csImage.preScale, }, @@ -451,6 +452,7 @@ class StackViewport extends Viewport implements IStackViewport { }, scalarData: this.cpuImagePixelData, hasPixelSpacing: this.hasPixelSpacing, + calibration: this.calibration, preScale: { ...this.csImage.preScale, }, @@ -2698,6 +2700,9 @@ class StackViewport extends Viewport implements IStackViewport { imageId ); + this.calibration = imagePlaneModule.calibration; + console.log('this.calibration', this.calibration, imagePlaneModule); + const newImagePlaneModule: ImagePlaneModule = { ...imagePlaneModule, }; diff --git a/packages/core/src/RenderingEngine/Viewport.ts b/packages/core/src/RenderingEngine/Viewport.ts index 9352c92907..cdb0a196fd 100644 --- a/packages/core/src/RenderingEngine/Viewport.ts +++ b/packages/core/src/RenderingEngine/Viewport.ts @@ -26,6 +26,7 @@ import type { import type { ViewportInput, IViewport } from '../types/IViewport'; import type { vtkSlabCamera } from './vtkClasses/vtkSlabCamera'; import { getConfiguration } from '../init'; +import IImageCalibration from '../types/IImageCalibration'; /** * An object representing a single viewport, which is a camera @@ -69,6 +70,7 @@ class Viewport implements IViewport { /** A flag representing if viewport methods should fire events or not */ readonly suppressEvents: boolean; protected hasPixelSpacing = true; + protected calibration: IImageCalibration; /** The camera that is initially defined on the reset for * the relative pan/zoom */ diff --git a/packages/core/src/enums/CalibrationTypes.ts b/packages/core/src/enums/CalibrationTypes.ts new file mode 100644 index 0000000000..4117357559 --- /dev/null +++ b/packages/core/src/enums/CalibrationTypes.ts @@ -0,0 +1,9 @@ +export enum CalibrationTypes { + NOT_APPLICABLE, + UNKNOWN, + ERMF, + USER, + PROJECTION, +} + +export default CalibrationTypes; diff --git a/packages/core/src/enums/index.ts b/packages/core/src/enums/index.ts index 76cc95dd93..f43024bc5f 100644 --- a/packages/core/src/enums/index.ts +++ b/packages/core/src/enums/index.ts @@ -9,10 +9,12 @@ import GeometryType from './GeometryType'; import ContourType from './ContourType'; import VOILUTFunctionType from './VOILUTFunctionType'; import DynamicOperatorType from './DynamicOperatorType'; +import CalibrationTypes from './CalibrationTypes'; export { Events, BlendModes, + CalibrationTypes, InterpolationType, RequestType, ViewportType, diff --git a/packages/core/src/types/CPUIImageData.ts b/packages/core/src/types/CPUIImageData.ts index 8615dc7d2c..61248ce237 100644 --- a/packages/core/src/types/CPUIImageData.ts +++ b/packages/core/src/types/CPUIImageData.ts @@ -1,4 +1,5 @@ import { Point3, Scaling, Mat3, PixelDataTypedArray } from '../types'; +import IImageCalibration from './IImageCalibration'; type CPUImageData = { worldToIndex?: (point: Point3) => Point3; @@ -24,6 +25,8 @@ type CPUIImageData = { scaling: Scaling; /** whether the image has pixel spacing and it is not undefined */ hasPixelSpacing?: boolean; + calibration?: IImageCalibration; + /** preScale object */ preScale?: { /** boolean flag to indicate whether the image has been scaled */ diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts new file mode 100644 index 0000000000..3bc6ef55af --- /dev/null +++ b/packages/core/src/types/IImageCalibration.ts @@ -0,0 +1,15 @@ +import CalibrationTypes from '../enums/CalibrationTypes'; + +/** + * IImageCalibration is an object that stores information about the type + * of image calibration. + */ +export interface IImageCalibration { + PixelSpacing: [number, number]; + type: CalibrationTypes; + hasPixelSpacing?: boolean; + isProjection?: boolean; + SequenceOfUltrasoundRegions?: Record[]; +} + +export default IImageCalibration; diff --git a/packages/core/src/types/IImageData.ts b/packages/core/src/types/IImageData.ts index 47d967c38a..c704f4830f 100644 --- a/packages/core/src/types/IImageData.ts +++ b/packages/core/src/types/IImageData.ts @@ -1,5 +1,6 @@ import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData'; import { Point3, Scaling, Mat3 } from '../types'; +import IImageCalibration from './IImageCalibration'; /** * IImageData of an image, which stores actual scalarData and metaData about the image. @@ -24,6 +25,9 @@ interface IImageData { scaling?: Scaling; /** whether the image has pixel spacing and it is not undefined */ hasPixelSpacing?: boolean; + + calibration?: IImageCalibration; + /** preScale object */ preScale?: { /** boolean flag to indicate whether the image has been scaled */ diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index da69b85a42..636f40bf10 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -30,6 +30,7 @@ import type Plane from './Plane'; import type IStreamingImageVolume from './IStreamingImageVolume'; import type ViewportInputOptions from './ViewportInputOptions'; import type IImageData from './IImageData'; +import type IImageCalibration from './IImageCalibration'; import type CPUIImageData from './CPUIImageData'; import type { CPUImageData } from './CPUIImageData'; import type IImage from './IImage'; @@ -102,6 +103,7 @@ export type { IStreamingImageVolume, IImage, IImageData, + IImageCalibration, CPUIImageData, CPUImageData, EventTypes, diff --git a/packages/tools/src/enums/index.js b/packages/tools/src/enums/index.js index 14d6b6cb33..baba448c82 100644 --- a/packages/tools/src/enums/index.js +++ b/packages/tools/src/enums/index.js @@ -4,6 +4,8 @@ import AnnotationStyleStates from './AnnotationStyleStates'; import Events from './Events'; import SegmentationRepresentations from './SegmentationRepresentations'; import { Swipe } from './Touch'; +import { Enums } from '@cornerstonejs/core'; +const { CalibrationTypes } = Enums; export { MouseBindings, @@ -13,4 +15,5 @@ export { Events, SegmentationRepresentations, Swipe, + CalibrationTypes, }; diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 7ffde08af8..84de40ec8c 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -1,4 +1,4 @@ -import { Events } from '../../enums'; +import { Events, CalibrationTypes } from '../../enums'; import { getEnabledElement, triggerEvent, @@ -51,6 +51,23 @@ import { StyleSpecifier } from '../../types/AnnotationStyle'; const { transformWorldToIndex } = csUtils; +const lengthUnits = (handles, image) => { + const { calibration, hasPixelSpacing } = image; + const units = hasPixelSpacing ? 'mm' : 'px;'; + if (!calibration) return units; + if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; + if (!CalibrationTypes) { + console.warn('Calibration types undefined'); + return `${units} CT Undef`; + } + if (calibration.types === CalibrationTypes.USER) { + return `${units} User Calibration`; + } + if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; + if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; + return units; +}; + /** * LengthTool let you draw annotations that measures the length of two drawing * points on a slice. You can use the LengthTool in all imaging planes even in oblique @@ -812,7 +829,8 @@ class LengthTool extends AnnotationTool { continue; } - const { imageData, dimensions, hasPixelSpacing } = image; + const { imageData, dimensions } = image; + console.log('imageData=', image, imageData); const length = this._calculateLength(worldPos1, worldPos2); @@ -830,7 +848,7 @@ class LengthTool extends AnnotationTool { // todo: add insideVolume calculation, for removing tool if outside cachedStats[targetId] = { length, - unit: hasPixelSpacing ? 'mm' : 'px', + unit: lengthUnits(null, image), }; } From 92eb32f5f3036a197af6bca3d3129d4c67f8506c Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 9 Jun 2023 16:04:14 -0400 Subject: [PATCH 02/37] fix(measurement): Fix ERMF display --- .../core/src/RenderingEngine/StackViewport.ts | 13 ++++++-- .../src/tools/annotation/BidirectionalTool.ts | 5 +-- .../src/tools/annotation/CircleROITool.ts | 7 ++-- .../tools/src/tools/annotation/LengthTool.ts | 21 ++---------- packages/tools/src/utilities/lengthUnits.ts | 33 +++++++++++++++++++ 5 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 packages/tools/src/utilities/lengthUnits.ts diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 212ced1683..32d31d4898 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -74,6 +74,7 @@ import { ImagePixelModule, ImagePlaneModule, } from '../types'; +import { CalibrationTypes } from '../enums'; const EPSILON = 1; // Slice Thickness @@ -559,6 +560,7 @@ class StackViewport extends Viewport implements IStackViewport { const voiLUTFunctionEnum = this._getValidVOILUTFunction(voiLUTFunction); this.VOILUTFunction = voiLUTFunctionEnum; + this.calibration = null; let imagePlaneModule = this._getImagePlaneModule(imageId); if (!this.useCPURendering) { @@ -628,6 +630,12 @@ class StackViewport extends Viewport implements IStackViewport { return imagePlaneModule; } + this.calibration = { + type: CalibrationTypes.USER, + PixelSpacing: [calibratedColumnSpacing, calibratedRowSpacing], + }; + this.hasPixelSpacing = calibratedRowSpacing && calibratedColumnSpacing; + // If no actor (first load) and calibration doesn't match headers // -> needs calibration if ( @@ -648,6 +656,7 @@ class StackViewport extends Viewport implements IStackViewport { // This updates the render copy imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; + imagePlaneModule.calibration = this.calibration; return imagePlaneModule; } @@ -659,6 +668,7 @@ class StackViewport extends Viewport implements IStackViewport { calibratedPixelSpacing.appliedSpacing = calibratedPixelSpacing; imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; + imagePlaneModule.calibration = this.calibration; // If current actor spacing matches the calibrated spacing if ( @@ -2700,8 +2710,7 @@ class StackViewport extends Viewport implements IStackViewport { imageId ); - this.calibration = imagePlaneModule.calibration; - console.log('this.calibration', this.calibration, imagePlaneModule); + this.calibration ||= imagePlaneModule.calibration; const newImagePlaneModule: ImagePlaneModule = { ...imagePlaneModule, diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index 1128264f1b..2d649b78c4 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -1,4 +1,4 @@ -import { vec2, vec3, mat2, mat3, mat2d } from 'gl-matrix'; +import { vec2, vec3 } from 'gl-matrix'; import { getEnabledElement, triggerEvent, @@ -7,6 +7,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; +import lengthUnits from '../../utilities/lengthUnits'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -1310,7 +1311,7 @@ class BidirectionalTool extends AnnotationTool { cachedStats[targetId] = { length, width, - unit: hasPixelSpacing ? 'mm' : 'px', + unit: lengthUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 04243f7885..03bb36ddc0 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -9,6 +9,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; +import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -886,7 +887,7 @@ class CircleROITool extends AnnotationTool { if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${area.toFixed(2)} ${areaUnit}\xb2`; + : `Area: ${area.toFixed(2)} ${areaUnit}`; textLines.push(areaLine); } @@ -1044,9 +1045,9 @@ class CircleROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: hasPixelSpacing ? 'mm' : 'px', + areaUnit: areaUnits(null, image), radius: worldWidth / 2, - radiusUnit: hasPixelSpacing ? 'mm' : 'px', + radiusUnit: lengthUnits(null, image), perimeter: 2 * Math.PI * (worldWidth / 2), }; } else { diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 84de40ec8c..3537025fdd 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -1,4 +1,4 @@ -import { Events, CalibrationTypes } from '../../enums'; +import { Events } from '../../enums'; import { getEnabledElement, triggerEvent, @@ -7,6 +7,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; +import lengthUnits from '../../utilities/lengthUnits'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -51,23 +52,6 @@ import { StyleSpecifier } from '../../types/AnnotationStyle'; const { transformWorldToIndex } = csUtils; -const lengthUnits = (handles, image) => { - const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm' : 'px;'; - if (!calibration) return units; - if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; - if (!CalibrationTypes) { - console.warn('Calibration types undefined'); - return `${units} CT Undef`; - } - if (calibration.types === CalibrationTypes.USER) { - return `${units} User Calibration`; - } - if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; - if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; - return units; -}; - /** * LengthTool let you draw annotations that measures the length of two drawing * points on a slice. You can use the LengthTool in all imaging planes even in oblique @@ -830,7 +814,6 @@ class LengthTool extends AnnotationTool { } const { imageData, dimensions } = image; - console.log('imageData=', image, imageData); const length = this._calculateLength(worldPos1, worldPos2); diff --git a/packages/tools/src/utilities/lengthUnits.ts b/packages/tools/src/utilities/lengthUnits.ts new file mode 100644 index 0000000000..acf42de3f0 --- /dev/null +++ b/packages/tools/src/utilities/lengthUnits.ts @@ -0,0 +1,33 @@ +import { CalibrationTypes } from '../enums'; + +const lengthUnits = (handles, image): string => { + const { calibration, hasPixelSpacing } = image; + const units = hasPixelSpacing ? 'mm' : 'px;'; + if (!calibration) return units; + if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; + if (calibration.types === CalibrationTypes.USER) { + return `${units} User Calibration`; + } + if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; + if (calibration.type === CalibrationTypes.USER) return `${units} USER`; + if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; + return units; +}; + +const areaUnits = (handles, image): string => { + const { calibration, hasPixelSpacing } = image; + const units = hasPixelSpacing ? 'mm\xb2' : 'px\xb2'; + if (!calibration) return units; + if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; + if (calibration.types === CalibrationTypes.USER) { + return `${units} User Calibration`; + } + if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; + if (calibration.type === CalibrationTypes.USER) return `${units} USER`; + if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; + return units; +}; + +export default lengthUnits; + +export { areaUnits, lengthUnits }; From 1fc950b201677523d1b8c108a6af1fe4ef3ae7ce Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 12 Jun 2023 10:23:33 -0400 Subject: [PATCH 03/37] fix(measurement): Use consistent rounding/sizing on measurements --- packages/core/src/enums/CalibrationTypes.ts | 12 ++++++---- .../src/tools/annotation/BidirectionalTool.ts | 5 ++-- .../src/tools/annotation/CircleROITool.ts | 11 +++++---- .../src/tools/annotation/EllipticalROITool.ts | 12 ++++++---- .../tools/src/tools/annotation/LengthTool.ts | 3 ++- .../tools/annotation/PlanarFreehandROITool.ts | 6 +++-- .../src/tools/annotation/RectangleROITool.ts | 14 ++++++----- packages/tools/src/utilities/index.ts | 2 ++ packages/tools/src/utilities/lengthUnits.ts | 24 ++++--------------- .../tools/src/utilities/roundMeasurement.ts | 13 ++++++++++ 10 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 packages/tools/src/utilities/roundMeasurement.ts diff --git a/packages/core/src/enums/CalibrationTypes.ts b/packages/core/src/enums/CalibrationTypes.ts index 4117357559..c5ea92b2c8 100644 --- a/packages/core/src/enums/CalibrationTypes.ts +++ b/packages/core/src/enums/CalibrationTypes.ts @@ -1,9 +1,11 @@ export enum CalibrationTypes { - NOT_APPLICABLE, - UNKNOWN, - ERMF, - USER, - PROJECTION, + NOT_APPLICABLE = '', + UNKNOWN = 'Unknown', + ERMF = 'ERMF', + USER = 'User', + PROJECTION = 'Proj', + REGION = 'Region', + ERROR = 'Error', } export default CalibrationTypes; diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index 2d649b78c4..bc4db906e1 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -8,6 +8,7 @@ import { import type { Types } from '@cornerstonejs/core'; import lengthUnits from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -1253,8 +1254,8 @@ class BidirectionalTool extends AnnotationTool { // spaceBetweenSlices & pixelSpacing & // magnitude in each direction? Otherwise, this is "px"? const textLines = [ - `L: ${length.toFixed(2)} ${unit}`, - `W: ${width.toFixed(2)} ${unit}`, + `L: ${roundMeasurement(length)} ${unit}`, + `W: ${roundMeasurement(width)} ${unit}`, ]; return textLines; diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 03bb36ddc0..933f6b6fed 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -10,6 +10,7 @@ import { import type { Types } from '@cornerstonejs/core'; import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -880,27 +881,27 @@ class CircleROITool extends AnnotationTool { if (radius) { const radiusLine = isEmptyArea ? `Radius: Oblique not supported` - : `Radius: ${radius.toFixed(2)} ${radiusUnit}`; + : `Radius: ${roundMeasurement(radius)} ${radiusUnit}`; textLines.push(radiusLine); } if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${area.toFixed(2)} ${areaUnit}`; + : `Area: ${roundMeasurement(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`); + textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${max.toFixed(2)} ${unit}`); + textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`); + textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); } return textLines; diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 2148a84002..87ef36c94a 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -9,6 +9,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; +import { areaUnits } from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -998,20 +1000,20 @@ class EllipticalROITool extends AnnotationTool { if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${area.toFixed(2)} ${areaUnit}\xb2`; + : `Area: ${roundMeasurement(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`); + textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${max.toFixed(2)} ${unit}`); + textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`); + textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); } return textLines; @@ -1156,7 +1158,7 @@ class EllipticalROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: hasPixelSpacing ? 'mm' : 'px', + areaUnit: areaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 3537025fdd..ee124f4005 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -8,6 +8,7 @@ import { import type { Types } from '@cornerstonejs/core'; import lengthUnits from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -777,7 +778,7 @@ class LengthTool extends AnnotationTool { return; } - const textLines = [`${length.toFixed(2)} ${unit}`]; + const textLines = [`${roundMeasurement(length)} ${unit}`]; return textLines; } diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index ff2b6bc553..77b86f2874 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -9,6 +9,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; + +import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; import { @@ -722,7 +724,7 @@ class PlanarFreehandROITool extends AnnotationTool { continue; } - const { imageData, metadata, hasPixelSpacing } = image; + const { imageData, metadata } = image; const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)); const area = polyline.calculateAreaOfPoints(canvasCoordinates); @@ -847,7 +849,7 @@ class PlanarFreehandROITool extends AnnotationTool { mean, max, stdDev, - areaUnit: hasPixelSpacing ? 'mm' : 'px', + areaUnit: areaUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 9b041d966b..b146c8e963 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -9,6 +9,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; +import { areaUnits } from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -865,10 +867,10 @@ class RectangleROITool extends AnnotationTool { const textLines: string[] = []; const unit = getModalityUnit(Modality, isPreScaled, isSuvScaled); - textLines.push(`Area: ${area.toFixed(2)} ${areaUnit}\xb2`); - textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`); - textLines.push(`Max: ${max.toFixed(2)} ${unit}`); - textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`); + textLines.push(`Area: ${roundMeasurement(area)} ${areaUnit}`); + textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); + textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); + textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); return textLines; }; @@ -912,7 +914,7 @@ class RectangleROITool extends AnnotationTool { continue; } - const { dimensions, imageData, metadata, hasPixelSpacing } = image; + const { dimensions, imageData, metadata } = image; const scalarData = 'getScalarData' in image ? image.getScalarData() : image.scalarData; @@ -1003,7 +1005,7 @@ class RectangleROITool extends AnnotationTool { mean, stdDev, max, - areaUnit: hasPixelSpacing ? 'mm' : 'px', + areaUnit: areaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/utilities/index.ts b/packages/tools/src/utilities/index.ts index 9c7c844747..e6e8adb9be 100644 --- a/packages/tools/src/utilities/index.ts +++ b/packages/tools/src/utilities/index.ts @@ -16,6 +16,7 @@ import jumpToSlice from './viewport/jumpToSlice'; import pointInShapeCallback from './pointInShapeCallback'; import pointInSurroundingSphereCallback from './pointInSurroundingSphereCallback'; import scroll from './scroll'; +import roundMeasurement from './roundMeasurement'; // name spaces import * as segmentation from './segmentation'; @@ -65,4 +66,5 @@ export { planarFreehandROITool, stackPrefetch, scroll, + roundMeasurement, }; diff --git a/packages/tools/src/utilities/lengthUnits.ts b/packages/tools/src/utilities/lengthUnits.ts index acf42de3f0..e7c460a489 100644 --- a/packages/tools/src/utilities/lengthUnits.ts +++ b/packages/tools/src/utilities/lengthUnits.ts @@ -1,31 +1,17 @@ -import { CalibrationTypes } from '../enums'; - const lengthUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm' : 'px;'; - if (!calibration) return units; + const units = hasPixelSpacing ? 'mm' : 'px'; + if (!calibration || !calibration.type) return units; if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; - if (calibration.types === CalibrationTypes.USER) { - return `${units} User Calibration`; - } - if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; - if (calibration.type === CalibrationTypes.USER) return `${units} USER`; - if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; - return units; + return `${units} ${calibration.type}`; }; const areaUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; const units = hasPixelSpacing ? 'mm\xb2' : 'px\xb2'; - if (!calibration) return units; + if (!calibration || !calibration.type) return units; if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; - if (calibration.types === CalibrationTypes.USER) { - return `${units} User Calibration`; - } - if (calibration.type === CalibrationTypes.ERMF) return `${units} ERMF`; - if (calibration.type === CalibrationTypes.USER) return `${units} USER`; - if (calibration.type === CalibrationTypes.PROJECTION) return `${units} PROJ`; - return units; + return `${units} ${calibration.type}`; }; export default lengthUnits; diff --git a/packages/tools/src/utilities/roundMeasurement.ts b/packages/tools/src/utilities/roundMeasurement.ts new file mode 100644 index 0000000000..8e5e7fbd7f --- /dev/null +++ b/packages/tools/src/utilities/roundMeasurement.ts @@ -0,0 +1,13 @@ +/** Rounds the measurement value appropriately */ +function roundMeasurement(value, scaling = 1) { + if (value === undefined || value === null || value === '') return 'NaN'; + const scaleValue = value * scaling; + if (scaleValue >= 100) return Math.round(scaleValue); + if (scaleValue >= 10) return scaleValue.toFixed(1); + if (scaleValue >= 0.1) return scaleValue.toFixed(2); + if (scaleValue >= 0.001) return scaleValue.toFixed(4); + if (scaleValue >= 0.00001) return scaleValue.toFixed(6); + return scaleValue; +} + +export default roundMeasurement; From 34b18c5cbb9b1069e150ed6e734d9aa9eedb71f7 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 12 Jun 2023 10:26:47 -0400 Subject: [PATCH 04/37] Fix freehand units --- .../tools/src/tools/annotation/PlanarFreehandROITool.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index 77b86f2874..15953a4c3a 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -11,6 +11,7 @@ import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; +import roundMeasurement from '../../utilities/roundMeasurement'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; import { @@ -934,20 +935,20 @@ class PlanarFreehandROITool extends AnnotationTool { if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${area.toFixed(2)} ${areaUnit}\xb2`; + : `Area: ${roundMeasurement(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`); + textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${max.toFixed(2)} ${unit}`); + textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`); + textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); } return textLines; From 487d8aeab6f0bed39d4389381585bb78a5f4b521 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 12 Jun 2023 10:46:12 -0400 Subject: [PATCH 05/37] api-check --- common/reviews/api/core.api.md | 39 ++++ .../api/streaming-image-volume-loader.api.md | 39 +++- common/reviews/api/tools.api.md | 180 +++++++++++++++++- 3 files changed, 251 insertions(+), 7 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 1620c5e0fa..140655965f 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -125,6 +125,24 @@ export const cache: Cache_2; // @public (undocumented) function calculateViewportsSpatialRegistration(viewport1: IStackViewport, viewport2: IStackViewport): void; +// @public (undocumented) +enum CalibrationTypes { + // (undocumented) + ERMF = "ERMF", + // (undocumented) + ERROR = "Error", + // (undocumented) + NOT_APPLICABLE = "", + // (undocumented) + PROJECTION = "Proj", + // (undocumented) + REGION = "Region", + // (undocumented) + UNKNOWN = "Unknown", + // (undocumented) + USER = "User" +} + // @public (undocumented) type CameraModifiedEvent = CustomEvent_2; @@ -437,6 +455,7 @@ type CPUIImageData = { scalarData: PixelDataTypedArray; scaling: Scaling; hasPixelSpacing?: boolean; + calibration?: IImageCalibration; preScale?: { scaled?: boolean; scalingParameters?: { @@ -562,6 +581,7 @@ declare namespace Enums { export { EVENTS as Events, BlendModes, + CalibrationTypes, InterpolationType, RequestType, ViewportType, @@ -1125,8 +1145,24 @@ interface IImage { windowWidth: number[] | number; } +// @public (undocumented) +interface IImageCalibration { + // (undocumented) + hasPixelSpacing?: boolean; + // (undocumented) + isProjection?: boolean; + // (undocumented) + PixelSpacing: [number, number]; + // (undocumented) + SequenceOfUltrasoundRegions?: Record[]; + // (undocumented) + type: CalibrationTypes; +} + // @public (undocumented) interface IImageData { + // (undocumented) + calibration?: IImageCalibration; // (undocumented) dimensions: Point3; // (undocumented) @@ -2355,6 +2391,7 @@ declare namespace Types { IStreamingImageVolume, IImage, IImageData, + IImageCalibration, CPUIImageData, CPUImageData, EventTypes, @@ -2495,6 +2532,8 @@ export class Viewport implements IViewport { // (undocumented) addActors(actors: Array, resetCameraPanAndZoom?: boolean): void; // (undocumented) + protected calibration: IImageCalibration; + // (undocumented) readonly canvas: HTMLCanvasElement; // (undocumented) canvasToWorld: (canvasPos: Point2) => Point3; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index d0d702c770..46e0efd62b 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,7 +4,6 @@ ```ts -import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { mat4 } from 'gl-matrix'; import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData'; @@ -40,6 +39,24 @@ enum BlendModes { MINIMUM_INTENSITY_BLEND = BlendMode.MINIMUM_INTENSITY_BLEND, } +// @public (undocumented) +enum CalibrationTypes { + // (undocumented) + ERMF = 'ERMF', + // (undocumented) + ERROR = 'Error', + // (undocumented) + NOT_APPLICABLE = '', + // (undocumented) + PROJECTION = 'Proj', + // (undocumented) + REGION = 'Region', + // (undocumented) + UNKNOWN = 'Unknown', + // (undocumented) + USER = 'User', +} + // @public type CameraModifiedEvent = CustomEvent_2; @@ -349,6 +366,8 @@ type CPUIImageData = { scalarData: PixelDataTypedArray; scaling: Scaling; hasPixelSpacing?: boolean; + calibration?: IImageCalibration; + preScale?: { scaled?: boolean; scalingParameters?: { @@ -783,8 +802,24 @@ interface IImage { windowWidth: number[] | number; } +// @public +interface IImageCalibration { + // (undocumented) + hasPixelSpacing?: boolean; + // (undocumented) + isProjection?: boolean; + // (undocumented) + PixelSpacing: [number, number]; + // (undocumented) + SequenceOfUltrasoundRegions?: Record[]; + // (undocumented) + type: CalibrationTypes; +} + // @public interface IImageData { + // (undocumented) + calibration?: IImageCalibration; dimensions: Point3; direction: Mat3; hasPixelSpacing?: boolean; @@ -1505,7 +1540,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: default_2; + requestType: RequestType_2; additionalDetails: { volumeId: string; }; diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 991214e823..281b7ef2a0 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -549,6 +549,14 @@ export class BidirectionalTool extends AnnotationTool { touchDragCallback: any; } +// @public +enum BlendModes { + AVERAGE_INTENSITY_BLEND = BlendMode.AVERAGE_INTENSITY_BLEND, + COMPOSITE = BlendMode.COMPOSITE_BLEND, + MAXIMUM_INTENSITY_BLEND = BlendMode.MAXIMUM_INTENSITY_BLEND, + MINIMUM_INTENSITY_BLEND = BlendMode.MINIMUM_INTENSITY_BLEND, +} + declare namespace boundingBox { export { extend2DBoundingBoxInViewAxis, @@ -586,6 +594,27 @@ function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, rowPixelSpacing: number, columnPixelSpacing: number): void; +// @public (undocumented) +enum CalibrationTypes { + // (undocumented) + ERMF = 'ERMF', + // (undocumented) + ERROR = 'Error', + // (undocumented) + NOT_APPLICABLE = '', + // (undocumented) + PROJECTION = 'Proj', + // (undocumented) + REGION = 'Region', + // (undocumented) + UNKNOWN = 'Unknown', + // (undocumented) + USER = 'User', +} + +// @public (undocumented) +const CalibrationTypes_2: typeof Enums_2.CalibrationTypes; + // @public type CameraModifiedEvent = CustomEvent_2; @@ -612,7 +641,7 @@ declare namespace cine { export { playClip, stopClip, - Events_2 as Events, + Events_3 as Events, getToolState, addToolState } @@ -907,6 +936,14 @@ type ContourSetData = { segmentIndex?: number; }; +// @public (undocumented) +enum ContourType { + // (undocumented) + CLOSED_PLANAR = 'CLOSED_PLANAR', + // (undocumented) + OPEN_PLANAR = 'OPEN_PLANAR', +} + // @public (undocumented) function copyPoints(points: ITouchPoints): ITouchPoints; @@ -1164,6 +1201,8 @@ type CPUIImageData = { scalarData: PixelDataTypedArray; scaling: Scaling; hasPixelSpacing?: boolean; + calibration?: IImageCalibration; + preScale?: { scaled?: boolean; scalingParameters?: { @@ -1563,6 +1602,13 @@ function drawRect(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, rec // @public (undocumented) function drawTextBox(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textUID: string, textLines: Array, position: Types_2.Point2, options?: {}): SVGRect; +// @public +enum DynamicOperatorType { + AVERAGE = 'AVERAGE', + SUBTRACT = 'SUBTRACT', + SUM = 'SUM', +} + declare namespace dynamicVolume { export { getDataInTime, @@ -1703,19 +1749,63 @@ function enable(element: any): void; declare namespace Enums { export { + CalibrationTypes_2 as CalibrationTypes, MouseBindings, KeyboardBindings, ToolModes, AnnotationStyleStates, - Events, + Events_2 as Events, SegmentationRepresentations, Swipe } } export { Enums } -// @public (undocumented) +// @public enum Events { + CACHE_SIZE_EXCEEDED = 'CACHE_SIZE_EXCEEDED', + CAMERA_MODIFIED = 'CORNERSTONE_CAMERA_MODIFIED', + + CAMERA_RESET = 'CORNERSTONE_CAMERA_RESET', + DISPLAY_AREA_MODIFIED = 'CORNERSTONE_DISPLAY_AREA_MODIFIED', + ELEMENT_DISABLED = 'CORNERSTONE_ELEMENT_DISABLED', + ELEMENT_ENABLED = 'CORNERSTONE_ELEMENT_ENABLED', + GEOMETRY_CACHE_GEOMETRY_ADDED = 'CORNERSTONE_GEOMETRY_CACHE_GEOMETRY_ADDED', + IMAGE_CACHE_IMAGE_ADDED = 'CORNERSTONE_IMAGE_CACHE_IMAGE_ADDED', + IMAGE_CACHE_IMAGE_REMOVED = 'CORNERSTONE_IMAGE_CACHE_IMAGE_REMOVED', + IMAGE_LOAD_ERROR = 'IMAGE_LOAD_ERROR', + IMAGE_LOAD_FAILED = 'CORNERSTONE_IMAGE_LOAD_FAILED', + IMAGE_LOAD_PROGRESS = 'CORNERSTONE_IMAGE_LOAD_PROGRESS', + IMAGE_LOADED = 'CORNERSTONE_IMAGE_LOADED', + + IMAGE_RENDERED = 'CORNERSTONE_IMAGE_RENDERED', + IMAGE_SPACING_CALIBRATED = 'CORNERSTONE_IMAGE_SPACING_CALIBRATED', + IMAGE_VOLUME_MODIFIED = 'CORNERSTONE_IMAGE_VOLUME_MODIFIED', + PRE_STACK_NEW_IMAGE = 'CORNERSTONE_PRE_STACK_NEW_IMAGE', + STACK_NEW_IMAGE = 'CORNERSTONE_STACK_NEW_IMAGE', + STACK_VIEWPORT_NEW_STACK = 'CORNERSTONE_STACK_VIEWPORT_NEW_STACK', + STACK_VIEWPORT_SCROLL = 'CORNERSTONE_STACK_VIEWPORT_SCROLL', + + VOI_MODIFIED = 'CORNERSTONE_VOI_MODIFIED', + + VOLUME_CACHE_VOLUME_ADDED = 'CORNERSTONE_VOLUME_CACHE_VOLUME_ADDED', + VOLUME_CACHE_VOLUME_REMOVED = 'CORNERSTONE_VOLUME_CACHE_VOLUME_REMOVED', + VOLUME_LOADED = 'CORNERSTONE_VOLUME_LOADED', + + VOLUME_LOADED_FAILED = 'CORNERSTONE_VOLUME_LOADED_FAILED', + + VOLUME_NEW_IMAGE = 'CORNERSTONE_VOLUME_NEW_IMAGE', + + VOLUME_SCROLL_OUT_OF_BOUNDS = 'CORNERSTONE_VOLUME_SCROLL_OUT_OF_BOUNDS', + + VOLUME_VIEWPORT_NEW_VOLUME = 'CORNERSTONE_VOLUME_VIEWPORT_NEW_VOLUME', + // IMAGE_CACHE_FULL = 'CORNERSTONE_IMAGE_CACHE_FULL', + // PRE_RENDER = 'CORNERSTONE_PRE_RENDER', + // ELEMENT_RESIZED = 'CORNERSTONE_ELEMENT_RESIZED', +} + +// @public (undocumented) +enum Events_2 { // (undocumented) ANNOTATION_ADDED = "CORNERSTONE_TOOLS_ANNOTATION_ADDED", // (undocumented) @@ -1781,7 +1871,7 @@ enum Events { } // @public (undocumented) -enum Events_2 { +enum Events_3 { // (undocumented) CLIP_STARTED = "CORNERSTONE_CINE_TOOL_STARTED", // (undocumented) @@ -1995,6 +2085,12 @@ class FrameOfReferenceSpecificAnnotationManager implements IAnnotationManager { // @public (undocumented) function generateImageFromTimeData(dynamicVolume: Types_2.IDynamicImageVolume, operation: string, frameNumbers?: number[]): Float32Array; +// @public (undocumented) +enum GeometryType { + // (undocumented) + CONTOUR = 'contour', +} + // @public (undocumented) function getActiveSegmentationRepresentation(toolGroupId: string): ToolGroupSpecificRepresentation; @@ -2484,8 +2580,24 @@ interface IImage { windowWidth: number[] | number; } +// @public +interface IImageCalibration { + // (undocumented) + hasPixelSpacing?: boolean; + // (undocumented) + isProjection?: boolean; + // (undocumented) + PixelSpacing: [number, number]; + // (undocumented) + SequenceOfUltrasoundRegions?: Record[]; + // (undocumented) + type: CalibrationTypes; +} + // @public interface IImageData { + // (undocumented) + calibration?: IImageCalibration; dimensions: Point3; direction: Mat3; hasPixelSpacing?: boolean; @@ -2732,6 +2844,14 @@ type InteractionTypes = 'Mouse' | 'Touch'; // @public (undocumented) function interpolateAnnotation(enabledElement: Types_2.IEnabledElement, annotation: PlanarFreehandROIAnnotation, knotsRatioPercentage: number): boolean; +// @public +enum InterpolationType { + // (undocumented) + FAST_LINEAR, + LINEAR, + NEAREST, +} + // @public (undocumented) function intersectLine(line1Start: Types_2.Point2, line1End: Types_2.Point2, line2Start: Types_2.Point2, line2End: Types_2.Point2): number[]; @@ -3559,6 +3679,18 @@ declare namespace orientation_2 { } } +// @public (undocumented) +enum OrientationAxis { + // (undocumented) + ACQUISITION = 'acquisition', + // (undocumented) + AXIAL = 'axial', + // (undocumented) + CORONAL = 'coronal', + // (undocumented) + SAGITTAL = 'sagittal', +} + // @public type OrientationVectors = { viewPlaneNormal: Point3; @@ -4331,6 +4463,13 @@ type RepresentationPublicInput = { type: Enums.SegmentationRepresentations; }; +// @public +enum RequestType { + Interaction = 'interaction', + Prefetch = 'prefetch', + Thumbnail = 'thumbnail', +} + // @public (undocumented) function resetAnnotationManager(): void; @@ -4340,6 +4479,9 @@ function resetElementCursor(element: HTMLDivElement): void; // @public type RGB = [number, number, number]; +// @public (undocumented) +function roundMeasurement(value: any, scaling?: number): string | number; + // @public (undocumented) interface ScaleOverlayAnnotation extends Annotation { // (undocumented) @@ -4690,6 +4832,15 @@ function setToolGroupSpecificConfig(toolGroupId: string, config: SegmentationRep // @public (undocumented) function setToolGroupSpecificConfig_2(toolGroupId: string, segmentationRepresentationConfig: SegmentationRepresentationConfig): void; +// @public +enum SharedArrayBufferModes { + AUTO = 'auto', + // (undocumented) + FALSE = 'false', + // (undocumented) + TRUE = 'true', +} + // @public (undocumented) function showAllAnnotations(): void; @@ -5354,7 +5505,8 @@ declare namespace utilities { rectangleROITool, planarFreehandROITool, stackPrefetch, - scroll_2 as scroll + scroll_2 as scroll, + roundMeasurement } } export { utilities } @@ -5423,6 +5575,15 @@ type ViewportProperties = { invert?: boolean; }; +// @public +enum ViewportType { + ORTHOGRAPHIC = 'orthographic', + PERSPECTIVE = 'perspective', + STACK = 'stack', + // (undocumented) + VOLUME_3D = 'volume3d', +} + declare namespace visibility { export { setAnnotationVisibility, @@ -5447,6 +5608,15 @@ type VOI = { windowCenter: number; }; +// @public +enum VOILUTFunctionType { + // (undocumented) + LINEAR = 'LINEAR', + // (undocumented) + SAMPLED_SIGMOID = 'SIGMOID', // SIGMOID is sampled in 1024 even steps so we call it SAMPLED_SIGMOID + // EXACT_LINEAR = 'EXACT_LINEAR', TODO: Add EXACT_LINEAR option from DICOM NEMA +} + // @public type VoiModifiedEvent = CustomEvent_2; From 8ad6ca36472183921e40481c613ed267660065ad Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 12 Jun 2023 11:30:27 -0400 Subject: [PATCH 06/37] API check fix --- common/reviews/api/streaming-image-volume-loader.api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 46e0efd62b..06e6ec25f1 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,6 +4,7 @@ ```ts +import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { mat4 } from 'gl-matrix'; import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData'; @@ -1540,7 +1541,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: RequestType_2; + requestType: default_2; additionalDetails: { volumeId: string; }; From ce28aad7569650eed6b4936f522c02eebe88353b Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 13 Jun 2023 15:06:58 -0400 Subject: [PATCH 07/37] PR comments --- .../core/src/RenderingEngine/StackViewport.ts | 2 +- packages/core/src/enums/CalibrationTypes.ts | 44 ++++++++++++++++++- packages/core/src/types/IImageCalibration.ts | 15 ++++++- packages/tools/src/enums/index.js | 3 -- .../src/tools/annotation/BidirectionalTool.ts | 10 ++--- .../src/tools/annotation/CircleROITool.ts | 21 +++++---- .../src/tools/annotation/EllipticalROITool.ts | 14 +++--- .../tools/src/tools/annotation/LengthTool.ts | 8 ++-- .../tools/annotation/PlanarFreehandROITool.ts | 14 +++--- .../src/tools/annotation/RectangleROITool.ts | 14 +++--- .../src/utilities/calibratedLengthUnits.ts | 37 ++++++++++++++++ packages/tools/src/utilities/index.ts | 4 +- packages/tools/src/utilities/lengthUnits.ts | 19 -------- .../tools/src/utilities/roundMeasurement.ts | 13 ------ packages/tools/src/utilities/roundNumber.ts | 35 +++++++++++++++ 15 files changed, 173 insertions(+), 80 deletions(-) create mode 100644 packages/tools/src/utilities/calibratedLengthUnits.ts delete mode 100644 packages/tools/src/utilities/lengthUnits.ts delete mode 100644 packages/tools/src/utilities/roundMeasurement.ts create mode 100644 packages/tools/src/utilities/roundNumber.ts diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 32d31d4898..851bfea125 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -632,7 +632,7 @@ class StackViewport extends Viewport implements IStackViewport { this.calibration = { type: CalibrationTypes.USER, - PixelSpacing: [calibratedColumnSpacing, calibratedRowSpacing], + pixelSpacing: [calibratedColumnSpacing, calibratedRowSpacing], }; this.hasPixelSpacing = calibratedRowSpacing && calibratedColumnSpacing; diff --git a/packages/core/src/enums/CalibrationTypes.ts b/packages/core/src/enums/CalibrationTypes.ts index c5ea92b2c8..c4dc942142 100644 --- a/packages/core/src/enums/CalibrationTypes.ts +++ b/packages/core/src/enums/CalibrationTypes.ts @@ -1,10 +1,52 @@ +/** + * Defines the calibration types available. These define how the units + * for measurements are specified. + */ export enum CalibrationTypes { + /** + * Not applicable means the units are directly defind by the underlying + * hardware, such as CT and MR volumetric displays, so no special handling + * or notification is required. + */ NOT_APPLICABLE = '', - UNKNOWN = 'Unknown', + /** + * ERMF is estimated radiographic magnification factor. This defines how + * much the image is magnified at the detector as opposed to the location in + * the body of interest. This occurs because the radiation beam is expanding + * and effectively magnifies the image on the detector compared to where the + * point of interest in the body is. + * This suggests that measurements can be partially trusted, but the user + * still needs to be aware that different depths within the body have differing + * ERMF values, so precise measurements would still need to be manually calibrated. + */ ERMF = 'ERMF', + /** + * User calibration means that the user has provided a custom calibration + * specifying how large the image data is. This type can occur on + * volumetric images, eg for scout images that might have invalid spacing + * tags. + */ USER = 'User', + /** + * A projection calibration means the raw detector size, without any + * ERMF applied, meaning that the size in the body cannot be trusted and + * that a calibration should be applied. + * This is different from Error in that there is simply no magnification + * factor applied as opposed to having multiple, inconsistent magnification + * factors. + */ PROJECTION = 'Proj', + /** + * A region calibration is used for other types of images, typically + * ultrasouunds where the distance in the image may mean something other than + * physical distance, such as mV or Hz or some other measurement values. + */ REGION = 'Region', + /** + * Error is used to define mismatches between various units, such as when + * there are two different ERMF values specified. This is an indication to + * NOT trust the measurement values but to manually calibrate. + */ ERROR = 'Error', } diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 3bc6ef55af..9320584108 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -5,11 +5,22 @@ import CalibrationTypes from '../enums/CalibrationTypes'; * of image calibration. */ export interface IImageCalibration { - PixelSpacing: [number, number]; + /** The pixel spacing for the image, in mm between pixel centers */ + pixelSpacing: [number, number]; + /** The type of the pixel spacing, distinguishing between various + * types projection (CR/DX/MG) spacing and volumetric spacing ('') + */ type: CalibrationTypes; + // A tooltip which can be used to explain the calibration information + tooltip?: string; + /** Indication that the image has some spacing information (the pixelSpacing + * when hasPixelSpacing is null can just be 1,1) */ hasPixelSpacing?: boolean; + /** Indication of projection (eg X-Ray type) spacing and volumetric type */ isProjection?: boolean; - SequenceOfUltrasoundRegions?: Record[]; + // The DICOM defined ultrasound regions. Used for non-distance spacing + // units. + sequenceOfUltrasoundRegions?: Record[]; } export default IImageCalibration; diff --git a/packages/tools/src/enums/index.js b/packages/tools/src/enums/index.js index baba448c82..14d6b6cb33 100644 --- a/packages/tools/src/enums/index.js +++ b/packages/tools/src/enums/index.js @@ -4,8 +4,6 @@ import AnnotationStyleStates from './AnnotationStyleStates'; import Events from './Events'; import SegmentationRepresentations from './SegmentationRepresentations'; import { Swipe } from './Touch'; -import { Enums } from '@cornerstonejs/core'; -const { CalibrationTypes } = Enums; export { MouseBindings, @@ -15,5 +13,4 @@ export { Events, SegmentationRepresentations, Swipe, - CalibrationTypes, }; diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index bc4db906e1..80f273931f 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -7,8 +7,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import lengthUnits from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import calibratedLengthUnits from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -1254,8 +1254,8 @@ class BidirectionalTool extends AnnotationTool { // spaceBetweenSlices & pixelSpacing & // magnitude in each direction? Otherwise, this is "px"? const textLines = [ - `L: ${roundMeasurement(length)} ${unit}`, - `W: ${roundMeasurement(width)} ${unit}`, + `L: ${roundNumber(length)} ${unit}`, + `W: ${roundNumber(width)} ${unit}`, ]; return textLines; @@ -1312,7 +1312,7 @@ class BidirectionalTool extends AnnotationTool { cachedStats[targetId] = { length, width, - unit: lengthUnits(null, image), + unit: calibratedLengthUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 933f6b6fed..9c1b025020 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -9,8 +9,11 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import { + calibratedLengthUnits, + calibratedAreaUnits, +} from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -881,27 +884,27 @@ class CircleROITool extends AnnotationTool { if (radius) { const radiusLine = isEmptyArea ? `Radius: Oblique not supported` - : `Radius: ${roundMeasurement(radius)} ${radiusUnit}`; + : `Radius: ${roundNumber(radius)} ${radiusUnit}`; textLines.push(radiusLine); } if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${roundMeasurement(area)} ${areaUnit}`; + : `Area: ${roundNumber(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); + textLines.push(`Mean: ${roundNumber(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); + textLines.push(`Max: ${roundNumber(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); + textLines.push(`Std Dev: ${roundNumber(stdDev)} ${unit}`); } return textLines; @@ -1046,9 +1049,9 @@ class CircleROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: areaUnits(null, image), + areaUnit: calibratedAreaUnits(null, image), radius: worldWidth / 2, - radiusUnit: lengthUnits(null, image), + radiusUnit: calibratedLengthUnits(null, image), perimeter: 2 * Math.PI * (worldWidth / 2), }; } else { diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 87ef36c94a..27306d683f 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -9,8 +9,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { areaUnits } from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -1000,20 +1000,20 @@ class EllipticalROITool extends AnnotationTool { if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${roundMeasurement(area)} ${areaUnit}`; + : `Area: ${roundNumber(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); + textLines.push(`Mean: ${roundNumber(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); + textLines.push(`Max: ${roundNumber(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); + textLines.push(`Std Dev: ${roundNumber(stdDev)} ${unit}`); } return textLines; @@ -1158,7 +1158,7 @@ class EllipticalROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: areaUnits(null, image), + areaUnit: calibratedAreaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index ee124f4005..1c86f3ca5d 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -7,8 +7,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import lengthUnits from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import calibratedLengthUnits from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; import { @@ -778,7 +778,7 @@ class LengthTool extends AnnotationTool { return; } - const textLines = [`${roundMeasurement(length)} ${unit}`]; + const textLines = [`${roundNumber(length)} ${unit}`]; return textLines; } @@ -832,7 +832,7 @@ class LengthTool extends AnnotationTool { // todo: add insideVolume calculation, for removing tool if outside cachedStats[targetId] = { length, - unit: lengthUnits(null, image), + unit: calibratedLengthUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index 15953a4c3a..a1164ac067 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -10,8 +10,8 @@ import { import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; -import { lengthUnits, areaUnits } from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; import { @@ -850,7 +850,7 @@ class PlanarFreehandROITool extends AnnotationTool { mean, max, stdDev, - areaUnit: areaUnits(null, image), + areaUnit: calibratedAreaUnits(null, image), }; } @@ -935,20 +935,20 @@ class PlanarFreehandROITool extends AnnotationTool { if (area) { const areaLine = isEmptyArea ? `Area: Oblique not supported` - : `Area: ${roundMeasurement(area)} ${areaUnit}`; + : `Area: ${roundNumber(area)} ${areaUnit}`; textLines.push(areaLine); } if (mean) { - textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); + textLines.push(`Mean: ${roundNumber(mean)} ${unit}`); } if (max) { - textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); + textLines.push(`Max: ${roundNumber(max)} ${unit}`); } if (stdDev) { - textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); + textLines.push(`Std Dev: ${roundNumber(stdDev)} ${unit}`); } return textLines; diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index b146c8e963..5b911de629 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -9,8 +9,8 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { areaUnits } from '../../utilities/lengthUnits'; -import roundMeasurement from '../../utilities/roundMeasurement'; +import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { addAnnotation, @@ -867,10 +867,10 @@ class RectangleROITool extends AnnotationTool { const textLines: string[] = []; const unit = getModalityUnit(Modality, isPreScaled, isSuvScaled); - textLines.push(`Area: ${roundMeasurement(area)} ${areaUnit}`); - textLines.push(`Mean: ${roundMeasurement(mean)} ${unit}`); - textLines.push(`Max: ${roundMeasurement(max)} ${unit}`); - textLines.push(`Std Dev: ${roundMeasurement(stdDev)} ${unit}`); + textLines.push(`Area: ${roundNumber(area)} ${areaUnit}`); + textLines.push(`Mean: ${roundNumber(mean)} ${unit}`); + textLines.push(`Max: ${roundNumber(max)} ${unit}`); + textLines.push(`Std Dev: ${roundNumber(stdDev)} ${unit}`); return textLines; }; @@ -1005,7 +1005,7 @@ class RectangleROITool extends AnnotationTool { mean, stdDev, max, - areaUnit: areaUnits(null, image), + areaUnit: calibratedAreaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/utilities/calibratedLengthUnits.ts b/packages/tools/src/utilities/calibratedLengthUnits.ts new file mode 100644 index 0000000000..5edb641755 --- /dev/null +++ b/packages/tools/src/utilities/calibratedLengthUnits.ts @@ -0,0 +1,37 @@ +/** + * Extracts the length units and the type of calibration for those units + * into the response. The length units will typically be either mm or px + * while the calibration type can be any of a number of different calibraiton types. + * + * Volumetric images have no calibration type, so are just the raw mm. + * + * TODO: Handle region calibration + * + * @param handles - used to detect if the spacing information is different + * between various points (eg angled ERMF or US Region) + * @param image - to extract the calibration from + * @param image.calibration - calibration value to extract units form + * @returns String containing the units and type of calibration + */ +const calibratedLengthUnits = (handles, image): string => { + const { calibration, hasPixelSpacing } = image; + const units = hasPixelSpacing ? 'mm' : 'px'; + if (!calibration || !calibration.type) return units; + if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; + return `${units} ${calibration.type}`; +}; + +/** + * Extracts the area units, including the squared sign plus calibration type. + */ +const calibratedAreaUnits = (handles, image): string => { + const { calibration, hasPixelSpacing } = image; + const units = hasPixelSpacing ? 'mm\xb2' : 'px\xb2'; + if (!calibration || !calibration.type) return units; + if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; + return `${units} ${calibration.type}`; +}; + +export default calibratedLengthUnits; + +export { calibratedAreaUnits, calibratedLengthUnits }; diff --git a/packages/tools/src/utilities/index.ts b/packages/tools/src/utilities/index.ts index e6e8adb9be..d0a91d0376 100644 --- a/packages/tools/src/utilities/index.ts +++ b/packages/tools/src/utilities/index.ts @@ -16,7 +16,7 @@ import jumpToSlice from './viewport/jumpToSlice'; import pointInShapeCallback from './pointInShapeCallback'; import pointInSurroundingSphereCallback from './pointInSurroundingSphereCallback'; import scroll from './scroll'; -import roundMeasurement from './roundMeasurement'; +import roundNumber from './roundNumber'; // name spaces import * as segmentation from './segmentation'; @@ -66,5 +66,5 @@ export { planarFreehandROITool, stackPrefetch, scroll, - roundMeasurement, + roundNumber as roundMeasurement, }; diff --git a/packages/tools/src/utilities/lengthUnits.ts b/packages/tools/src/utilities/lengthUnits.ts deleted file mode 100644 index e7c460a489..0000000000 --- a/packages/tools/src/utilities/lengthUnits.ts +++ /dev/null @@ -1,19 +0,0 @@ -const lengthUnits = (handles, image): string => { - const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm' : 'px'; - if (!calibration || !calibration.type) return units; - if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; - return `${units} ${calibration.type}`; -}; - -const areaUnits = (handles, image): string => { - const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm\xb2' : 'px\xb2'; - if (!calibration || !calibration.type) return units; - if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; - return `${units} ${calibration.type}`; -}; - -export default lengthUnits; - -export { areaUnits, lengthUnits }; diff --git a/packages/tools/src/utilities/roundMeasurement.ts b/packages/tools/src/utilities/roundMeasurement.ts deleted file mode 100644 index 8e5e7fbd7f..0000000000 --- a/packages/tools/src/utilities/roundMeasurement.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** Rounds the measurement value appropriately */ -function roundMeasurement(value, scaling = 1) { - if (value === undefined || value === null || value === '') return 'NaN'; - const scaleValue = value * scaling; - if (scaleValue >= 100) return Math.round(scaleValue); - if (scaleValue >= 10) return scaleValue.toFixed(1); - if (scaleValue >= 0.1) return scaleValue.toFixed(2); - if (scaleValue >= 0.001) return scaleValue.toFixed(4); - if (scaleValue >= 0.00001) return scaleValue.toFixed(6); - return scaleValue; -} - -export default roundMeasurement; diff --git a/packages/tools/src/utilities/roundNumber.ts b/packages/tools/src/utilities/roundNumber.ts new file mode 100644 index 0000000000..a0c1a7f5e9 --- /dev/null +++ b/packages/tools/src/utilities/roundNumber.ts @@ -0,0 +1,35 @@ +/** + * Truncates to a relative number of places to show numbers cleanly such + * that in addition to the leading digit, there are precision additional + * digits. The default precision shows numbers to at least 1/10 % + * + * For example, with the default precision 2: + * 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 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 roundNumber; From 78b8d9f3be2e831de9f000a6bb1c661121049ca3 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 13 Jun 2023 15:21:24 -0400 Subject: [PATCH 08/37] PR fixes --- packages/core/src/RenderingEngine/StackViewport.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 851bfea125..889f1bb590 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -10,7 +10,6 @@ import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransf import * as metaData from '../metaData'; import Viewport from './Viewport'; import eventTarget from '../eventTarget'; -import Events from '../enums/Events'; import { triggerEvent, isEqual, @@ -48,8 +47,13 @@ import { getColormap } from './helpers/cpuFallback/colors/index'; import { loadAndCacheImage } from '../loaders/imageLoader'; import imageLoadPoolManager from '../requestPool/imageLoadPoolManager'; -import InterpolationType from '../enums/InterpolationType'; -import VOILUTFunctionType from '../enums/VOILUTFunctionType'; +import { + InterpolationType, + RequestType, + Events, + CalibrationTypes, + VOILUTFunctionType, +} from '../enums'; import canvasToPixel from './helpers/cpuFallback/rendering/canvasToPixel'; import pixelToCanvas from './helpers/cpuFallback/rendering/pixelToCanvas'; import getDefaultViewport from './helpers/cpuFallback/rendering/getDefaultViewport'; @@ -59,7 +63,6 @@ import resize from './helpers/cpuFallback/rendering/resize'; import resetCamera from './helpers/cpuFallback/rendering/resetCamera'; import { Transform } from './helpers/cpuFallback/rendering/transform'; import { getConfiguration, getShouldUseCPURendering } from '../init'; -import RequestType from '../enums/RequestType'; import { StackViewportNewStackEventDetail, StackViewportScrollEventDetail, @@ -74,7 +77,6 @@ import { ImagePixelModule, ImagePlaneModule, } from '../types'; -import { CalibrationTypes } from '../enums'; const EPSILON = 1; // Slice Thickness From d66ee792804c5caf073c8526f803a2dc8b9bb0af Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 19 Jun 2023 08:16:15 -0400 Subject: [PATCH 09/37] API check --- common/reviews/api/core.api.md | 8 +- common/reviews/api/tools.api.md | 168 ++------------------------------ package.json | 5 +- yarn.lock | 14 +-- 4 files changed, 24 insertions(+), 171 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 86e9c6e028..9d4b1f9523 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -138,8 +138,6 @@ enum CalibrationTypes { // (undocumented) REGION = "Region", // (undocumented) - UNKNOWN = "Unknown", - // (undocumented) USER = "User" } @@ -1152,9 +1150,11 @@ interface IImageCalibration { // (undocumented) isProjection?: boolean; // (undocumented) - PixelSpacing: [number, number]; + pixelSpacing: [number, number]; + // (undocumented) + sequenceOfUltrasoundRegions?: Record[]; // (undocumented) - SequenceOfUltrasoundRegions?: Record[]; + tooltip?: string; // (undocumented) type: CalibrationTypes; } diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 0a5124a684..dc4cbdc625 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -549,14 +549,6 @@ export class BidirectionalTool extends AnnotationTool { touchDragCallback: any; } -// @public -enum BlendModes { - AVERAGE_INTENSITY_BLEND = BlendMode.AVERAGE_INTENSITY_BLEND, - COMPOSITE = BlendMode.COMPOSITE_BLEND, - MAXIMUM_INTENSITY_BLEND = BlendMode.MAXIMUM_INTENSITY_BLEND, - MINIMUM_INTENSITY_BLEND = BlendMode.MINIMUM_INTENSITY_BLEND, -} - declare namespace boundingBox { export { extend2DBoundingBoxInViewAxis, @@ -594,27 +586,6 @@ function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, rowPixelSpacing: number, columnPixelSpacing: number): void; -// @public (undocumented) -enum CalibrationTypes { - // (undocumented) - ERMF = 'ERMF', - // (undocumented) - ERROR = 'Error', - // (undocumented) - NOT_APPLICABLE = '', - // (undocumented) - PROJECTION = 'Proj', - // (undocumented) - REGION = 'Region', - // (undocumented) - UNKNOWN = 'Unknown', - // (undocumented) - USER = 'User', -} - -// @public (undocumented) -const CalibrationTypes_2: typeof Enums_2.CalibrationTypes; - // @public type CameraModifiedEvent = CustomEvent_2; @@ -641,7 +612,7 @@ declare namespace cine { export { playClip, stopClip, - Events_3 as Events, + Events_2 as Events, getToolState, addToolState } @@ -945,14 +916,6 @@ type ContourSetData = { segmentIndex?: number; }; -// @public (undocumented) -enum ContourType { - // (undocumented) - CLOSED_PLANAR = 'CLOSED_PLANAR', - // (undocumented) - OPEN_PLANAR = 'OPEN_PLANAR', -} - // @public (undocumented) function copyPoints(points: ITouchPoints): ITouchPoints; @@ -1611,13 +1574,6 @@ function drawRect(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, rec // @public (undocumented) function drawTextBox(svgDrawingHelper: SVGDrawingHelper, annotationUID: string, textUID: string, textLines: Array, position: Types_2.Point2, options?: {}): SVGRect; -// @public -enum DynamicOperatorType { - AVERAGE = 'AVERAGE', - SUBTRACT = 'SUBTRACT', - SUM = 'SUM', -} - declare namespace dynamicVolume { export { getDataInTime, @@ -1758,63 +1714,19 @@ function enable(element: any): void; declare namespace Enums { export { - CalibrationTypes_2 as CalibrationTypes, MouseBindings, KeyboardBindings, ToolModes, AnnotationStyleStates, - Events_2 as Events, + Events, SegmentationRepresentations, Swipe } } export { Enums } -// @public -enum Events { - CACHE_SIZE_EXCEEDED = 'CACHE_SIZE_EXCEEDED', - CAMERA_MODIFIED = 'CORNERSTONE_CAMERA_MODIFIED', - - CAMERA_RESET = 'CORNERSTONE_CAMERA_RESET', - DISPLAY_AREA_MODIFIED = 'CORNERSTONE_DISPLAY_AREA_MODIFIED', - ELEMENT_DISABLED = 'CORNERSTONE_ELEMENT_DISABLED', - ELEMENT_ENABLED = 'CORNERSTONE_ELEMENT_ENABLED', - GEOMETRY_CACHE_GEOMETRY_ADDED = 'CORNERSTONE_GEOMETRY_CACHE_GEOMETRY_ADDED', - IMAGE_CACHE_IMAGE_ADDED = 'CORNERSTONE_IMAGE_CACHE_IMAGE_ADDED', - IMAGE_CACHE_IMAGE_REMOVED = 'CORNERSTONE_IMAGE_CACHE_IMAGE_REMOVED', - IMAGE_LOAD_ERROR = 'IMAGE_LOAD_ERROR', - IMAGE_LOAD_FAILED = 'CORNERSTONE_IMAGE_LOAD_FAILED', - IMAGE_LOAD_PROGRESS = 'CORNERSTONE_IMAGE_LOAD_PROGRESS', - IMAGE_LOADED = 'CORNERSTONE_IMAGE_LOADED', - - IMAGE_RENDERED = 'CORNERSTONE_IMAGE_RENDERED', - IMAGE_SPACING_CALIBRATED = 'CORNERSTONE_IMAGE_SPACING_CALIBRATED', - IMAGE_VOLUME_MODIFIED = 'CORNERSTONE_IMAGE_VOLUME_MODIFIED', - PRE_STACK_NEW_IMAGE = 'CORNERSTONE_PRE_STACK_NEW_IMAGE', - STACK_NEW_IMAGE = 'CORNERSTONE_STACK_NEW_IMAGE', - STACK_VIEWPORT_NEW_STACK = 'CORNERSTONE_STACK_VIEWPORT_NEW_STACK', - STACK_VIEWPORT_SCROLL = 'CORNERSTONE_STACK_VIEWPORT_SCROLL', - - VOI_MODIFIED = 'CORNERSTONE_VOI_MODIFIED', - - VOLUME_CACHE_VOLUME_ADDED = 'CORNERSTONE_VOLUME_CACHE_VOLUME_ADDED', - VOLUME_CACHE_VOLUME_REMOVED = 'CORNERSTONE_VOLUME_CACHE_VOLUME_REMOVED', - VOLUME_LOADED = 'CORNERSTONE_VOLUME_LOADED', - - VOLUME_LOADED_FAILED = 'CORNERSTONE_VOLUME_LOADED_FAILED', - - VOLUME_NEW_IMAGE = 'CORNERSTONE_VOLUME_NEW_IMAGE', - - VOLUME_SCROLL_OUT_OF_BOUNDS = 'CORNERSTONE_VOLUME_SCROLL_OUT_OF_BOUNDS', - - VOLUME_VIEWPORT_NEW_VOLUME = 'CORNERSTONE_VOLUME_VIEWPORT_NEW_VOLUME', - // IMAGE_CACHE_FULL = 'CORNERSTONE_IMAGE_CACHE_FULL', - // PRE_RENDER = 'CORNERSTONE_PRE_RENDER', - // ELEMENT_RESIZED = 'CORNERSTONE_ELEMENT_RESIZED', -} - // @public (undocumented) -enum Events_2 { +enum Events { // (undocumented) ANNOTATION_ADDED = "CORNERSTONE_TOOLS_ANNOTATION_ADDED", // (undocumented) @@ -1880,7 +1792,7 @@ enum Events_2 { } // @public (undocumented) -enum Events_3 { +enum Events_2 { // (undocumented) CLIP_STARTED = "CORNERSTONE_CINE_TOOL_STARTED", // (undocumented) @@ -2094,12 +2006,6 @@ class FrameOfReferenceSpecificAnnotationManager implements IAnnotationManager { // @public (undocumented) function generateImageFromTimeData(dynamicVolume: Types_2.IDynamicImageVolume, operation: string, frameNumbers?: number[]): Float32Array; -// @public (undocumented) -enum GeometryType { - // (undocumented) - CONTOUR = 'contour', -} - // @public (undocumented) function getActiveSegmentationRepresentation(toolGroupId: string): ToolGroupSpecificRepresentation; @@ -2591,15 +2497,13 @@ interface IImage { // @public interface IImageCalibration { - // (undocumented) hasPixelSpacing?: boolean; - // (undocumented) isProjection?: boolean; + pixelSpacing: [number, number]; // (undocumented) - PixelSpacing: [number, number]; - // (undocumented) - SequenceOfUltrasoundRegions?: Record[]; + sequenceOfUltrasoundRegions?: Record[]; // (undocumented) + tooltip?: string; type: CalibrationTypes; } @@ -2853,14 +2757,6 @@ type InteractionTypes = 'Mouse' | 'Touch'; // @public (undocumented) function interpolateAnnotation(enabledElement: Types_2.IEnabledElement, annotation: PlanarFreehandROIAnnotation, knotsRatioPercentage: number): boolean; -// @public -enum InterpolationType { - // (undocumented) - FAST_LINEAR, - LINEAR, - NEAREST, -} - // @public (undocumented) function intersectLine(line1Start: Types_2.Point2, line1End: Types_2.Point2, line2Start: Types_2.Point2, line2End: Types_2.Point2): number[]; @@ -3688,18 +3584,6 @@ declare namespace orientation_2 { } } -// @public (undocumented) -enum OrientationAxis { - // (undocumented) - ACQUISITION = 'acquisition', - // (undocumented) - AXIAL = 'axial', - // (undocumented) - CORONAL = 'coronal', - // (undocumented) - SAGITTAL = 'sagittal', -} - // @public type OrientationVectors = { viewPlaneNormal: Point3; @@ -4472,13 +4356,6 @@ type RepresentationPublicInput = { type: Enums.SegmentationRepresentations; }; -// @public -enum RequestType { - Interaction = 'interaction', - Prefetch = 'prefetch', - Thumbnail = 'thumbnail', -} - // @public (undocumented) function resetAnnotationManager(): void; @@ -4489,7 +4366,7 @@ function resetElementCursor(element: HTMLDivElement): void; type RGB = [number, number, number]; // @public (undocumented) -function roundMeasurement(value: any, scaling?: number): string | number; +function roundNumber(value: string | number, precision?: number): string; // @public (undocumented) interface ScaleOverlayAnnotation extends Annotation { @@ -4841,15 +4718,6 @@ function setToolGroupSpecificConfig(toolGroupId: string, config: SegmentationRep // @public (undocumented) function setToolGroupSpecificConfig_2(toolGroupId: string, segmentationRepresentationConfig: SegmentationRepresentationConfig): void; -// @public -enum SharedArrayBufferModes { - AUTO = 'auto', - // (undocumented) - FALSE = 'false', - // (undocumented) - TRUE = 'true', -} - // @public (undocumented) function showAllAnnotations(): void; @@ -5515,7 +5383,7 @@ declare namespace utilities { planarFreehandROITool, stackPrefetch, scroll_2 as scroll, - roundMeasurement + roundNumber as roundMeasurement } } export { utilities } @@ -5584,15 +5452,6 @@ type ViewportProperties = { invert?: boolean; }; -// @public -enum ViewportType { - ORTHOGRAPHIC = 'orthographic', - PERSPECTIVE = 'perspective', - STACK = 'stack', - // (undocumented) - VOLUME_3D = 'volume3d', -} - declare namespace visibility { export { setAnnotationVisibility, @@ -5617,15 +5476,6 @@ type VOI = { windowCenter: number; }; -// @public -enum VOILUTFunctionType { - // (undocumented) - LINEAR = 'LINEAR', - // (undocumented) - SAMPLED_SIGMOID = 'SIGMOID', // SIGMOID is sampled in 1024 even steps so we call it SAMPLED_SIGMOID - // EXACT_LINEAR = 'EXACT_LINEAR', TODO: Add EXACT_LINEAR option from DICOM NEMA -} - // @public type VoiModifiedEvent = CustomEvent_2; diff --git a/package.json b/package.json index e779ae8404..f6b7fc79cd 100644 --- a/package.json +++ b/package.json @@ -159,5 +159,8 @@ "not ie < 11", "not op_mini all" ], - "dependencies": {} + "dependencies": {}, + "resolutions": { + "canvas": "2.11.2" + } } diff --git a/yarn.lock b/yarn.lock index 896b6903a2..f1b88f418d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6696,13 +6696,13 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz#f58a717afe92f9e69d0e35ff64df596bfad93912" integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== -canvas@2.9.0: - version "2.9.0" - resolved "https://registry.npmjs.org/canvas/-/canvas-2.9.0.tgz#7df0400b141a7e42e597824f377935ba96880f2a" - integrity sha512-0l93g7uxp7rMyr7H+XRQ28A3ud0dKIUTIEkUe1Dxh4rjUYN7B93+SjC3r1PDKA18xcQN87OFGgUnyw7LSgNLSQ== +canvas@2.11.2, canvas@2.9.0: + version "2.11.2" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== dependencies: "@mapbox/node-pre-gyp" "^1.0.0" - nan "^2.15.0" + nan "^2.17.0" simple-get "^3.0.3" caseless@~0.12.0: @@ -15048,9 +15048,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.15.0, nan@^2.16.0: +nan@^2.16.0, nan@^2.17.0: version "2.17.0" - resolved "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== nanoid@3.3.1: From de90b68f212ebb7912e5f1f33be8c0a8e651a74a Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 27 Jun 2023 09:56:00 -0400 Subject: [PATCH 10/37] Api check --- .../api/streaming-image-volume-loader.api.md | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 24ad3c292d..579c782ac7 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -40,21 +40,13 @@ enum BlendModes { MINIMUM_INTENSITY_BLEND = BlendMode.MINIMUM_INTENSITY_BLEND, } -// @public (undocumented) +// @public enum CalibrationTypes { - // (undocumented) ERMF = 'ERMF', - // (undocumented) ERROR = 'Error', - // (undocumented) NOT_APPLICABLE = '', - // (undocumented) PROJECTION = 'Proj', - // (undocumented) REGION = 'Region', - // (undocumented) - UNKNOWN = 'Unknown', - // (undocumented) USER = 'User', } @@ -814,15 +806,20 @@ interface IImage { // @public interface IImageCalibration { - // (undocumented) hasPixelSpacing?: boolean; - // (undocumented) + isProjection?: boolean; + + pixelSpacing?: [number, number]; + + scale?: number; + // (undocumented) - PixelSpacing: [number, number]; - // (undocumented) - SequenceOfUltrasoundRegions?: Record[]; + sequenceOfUltrasoundRegions?: Record[]; + // (undocumented) + tooltip?: string; + type: CalibrationTypes; } @@ -1031,8 +1028,7 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale: number; - columnScale: number; + scale: number; imageData: vtkImageData; worldToIndex: mat4; }; From 349a8f74b09d75de397d1748a20342d8b5947e0b Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 27 Jun 2023 10:12:20 -0400 Subject: [PATCH 11/37] api-check --- .../reviews/api/streaming-image-volume-loader.api.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 579c782ac7..cea6b393f0 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -807,19 +807,12 @@ interface IImage { // @public interface IImageCalibration { hasPixelSpacing?: boolean; - isProjection?: boolean; - - pixelSpacing?: [number, number]; - - scale?: number; - + pixelSpacing: [number, number]; // (undocumented) sequenceOfUltrasoundRegions?: Record[]; - // (undocumented) tooltip?: string; - type: CalibrationTypes; } @@ -1028,7 +1021,8 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - scale: number; + rowScale: number; + columnScale: number; imageData: vtkImageData; worldToIndex: mat4; }; From fc85886f94bbaa7f1a9c2732ba53d031d44edf60 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 08:59:08 -0400 Subject: [PATCH 12/37] Example calibrations --- .../examples/stackAnnotationTools/index.ts | 76 +++++++++++++++++++ .../src/utilities/calibrateImageSpacing.ts | 12 ++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index d667b844e8..2fb12f29eb 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -12,12 +12,15 @@ import { addButtonToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools'; +import dicomImageLoader from '@cornerstonejs/dicom-image-loader'; // This is for debugging purposes console.warn( 'Click on index.ts to open source code for this example --------->' ); +const { wadors } = dicomImageLoader; + const { LengthTool, ProbeTool, @@ -30,6 +33,7 @@ const { ToolGroupManager, ArrowAnnotateTool, Enums: csToolsEnums, + utilities, } = cornerstoneTools; const { ViewportType, Events } = Enums; @@ -185,6 +189,65 @@ addButtonToToolbar({ }, }); +const calibrationFunctions: Record = {}; + +const calibrations = [ + { + value: 'Default', + selected: 'clearCalibration', + }, + { + value: 'User Calibration 0.5', + selected: 'userCalibration', + calibration: { + scale: 0.5, + type: Enums.CalibrationTypes.USER, + }, + }, + { + value: 'Uncalibrated', + selected: 'applyMetadata', + metadata: { + '00280030': null, + }, + }, + { + value: 'Uncalibrated', + selected: 'applyMetadata', + metadata: { + '00280030': null, + }, + }, + { + value: 'Aspect 1:2', + selected: 'applyMetadata', + metadata: { + '00280030': { Value: [0.5, 1] }, + }, + }, + { + value: 'Aspect 1:1', + selected: 'applyMetadata', + metadata: { + '00280030': { Value: [0.5, 0.5] }, + }, + }, +]; +const calibrationNames = calibrations.map((it) => it.value); + +addDropdownToToolbar({ + options: { values: calibrationNames }, + onSelectedValueChange: (newCalibrationValue) => { + const calibration = calibrations.find( + (it) => it.value === newCalibrationValue + ); + if (!calibration) return; + const f = calibrationFunctions[calibration.selected]; + if (!f) return; + f.apply(calibration); + }, +}); + /** * Runs the demo */ @@ -250,6 +313,19 @@ async function run() { // Instantiate a rendering engine const renderingEngine = new RenderingEngine(renderingEngineId); + calibrationFunctions.userCalibration = function calibrationSelected() { + utilities.calibrateImageSpacing(imageIds[0], renderingEngine, 2, 2); + }; + calibrationFunctions.clearCalibration = function clearCalibration() { + utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null, null); + }; + calibrationFunctions.applyMetadata = function applyMetadata() { + const instance = wadors.metaDataManager.get(imageIds[0]); + console.log('Applying', this.metadata); + Object.assign(instance, this.metadata); + utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null, null); + }; + // Create a stack viewport const viewportInput = { viewportId, diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index f4fe5e867f..685eb3c0b4 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -24,10 +24,14 @@ export default function calibrateImageSpacing( columnPixelSpacing = rowPixelSpacing; } - calibratedPixelSpacingMetadataProvider.add(imageId, { - rowPixelSpacing, - columnPixelSpacing, - }); + if (!rowPixelSpacing) { + calibratedPixelSpacingMetadataProvider.add(imageId, null); + } else { + calibratedPixelSpacingMetadataProvider.add(imageId, { + rowPixelSpacing, + columnPixelSpacing, + }); + } // 2. Update the actor for stackViewports const viewports = renderingEngine.getStackViewports(); From 266d68671f3549288304e7b68ddc28e9d2bf9b2a Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 15:24:19 -0400 Subject: [PATCH 13/37] fix: Update the image calibration to include a message --- common/reviews/api/core.api.md | 10 +++- .../api/streaming-image-volume-loader.api.md | 5 +- common/reviews/api/tools.api.md | 7 ++- .../core/src/RenderingEngine/StackViewport.ts | 58 ++++++++----------- packages/core/src/types/IImageCalibration.ts | 8 ++- .../calibratedPixelSpacingMetadataProvider.ts | 14 ++--- .../examples/stackAnnotationTools/index.ts | 41 +++++++++---- .../src/utilities/calibrateImageSpacing.ts | 28 ++++----- 8 files changed, 92 insertions(+), 79 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 9d4b1f9523..68b6e08349 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1145,12 +1145,16 @@ interface IImage { // @public (undocumented) interface IImageCalibration { + // (undocumented) + columnPixelSpacing?: number; // (undocumented) hasPixelSpacing?: boolean; // (undocumented) isProjection?: boolean; // (undocumented) - pixelSpacing: [number, number]; + rowPixelSpacing?: number; + // (undocumented) + scale?: number; // (undocumented) sequenceOfUltrasoundRegions?: Record[]; // (undocumented) @@ -1944,8 +1948,8 @@ export { metaData } // @public (undocumented) const metadataProvider: { - add: (imageId: string, payload: CalibratedPixelValue) => void; - get: (type: string, imageId: string) => CalibratedPixelValue; + add: (imageId: string, payload: IImageCalibration) => void; + get: (type: string, imageId: string) => IImageCalibration; }; // @public (undocumented) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index cea6b393f0..3acbd8889a 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -806,9 +806,12 @@ interface IImage { // @public interface IImageCalibration { + // (undocumented) + columnPixelSpacing?: number; hasPixelSpacing?: boolean; isProjection?: boolean; - pixelSpacing: [number, number]; + rowPixelSpacing?: number; + scale?: number; // (undocumented) sequenceOfUltrasoundRegions?: Record[]; // (undocumented) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index dc4cbdc625..e8339c17aa 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -584,7 +584,7 @@ export class BrushTool extends BaseTool { function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) -function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, rowPixelSpacing: number, columnPixelSpacing: number): void; +function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration | number, columnPixelSpacing?: number): void; // @public type CameraModifiedEvent = CustomEvent_2; @@ -2497,9 +2497,12 @@ interface IImage { // @public interface IImageCalibration { + // (undocumented) + columnPixelSpacing?: number; hasPixelSpacing?: boolean; isProjection?: boolean; - pixelSpacing: [number, number]; + rowPixelSpacing?: number; + scale?: number; // (undocumented) sequenceOfUltrasoundRegions?: Record[]; // (undocumented) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 828d9540f5..f49b0ab776 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -40,6 +40,7 @@ import { VolumeActor, Mat3, ColormapRegistration, + IImageCalibration, } from '../types'; import { ViewportInput } from '../types/IViewport'; import drawImageSync from './helpers/cpuFallback/drawImageSync'; @@ -95,6 +96,7 @@ interface ImageDataMetaData { type CalibrationEvent = { rowScale: number; columnScale: number; + calibration: IImageCalibration; }; type SetVOIOptions = { @@ -596,19 +598,25 @@ class StackViewport extends Viewport implements IStackViewport { * @returns modified imagePlaneModule with the calibrated spacings */ private calibrateIfNecessary(imageId, imagePlaneModule) { - const calibratedPixelSpacing = metaData.get( - 'calibratedPixelSpacing', - imageId - ); + const calibration = metaData.get('calibratedPixelSpacing', imageId); + this.calibration = calibration; + imagePlaneModule.calibration = calibration; - if (!calibratedPixelSpacing) { - return imagePlaneModule; - } + if (!calibration) return imagePlaneModule; - const { + let { rowPixelSpacing: calibratedRowSpacing, columnPixelSpacing: calibratedColumnSpacing, - } = calibratedPixelSpacing; + } = calibration; + const { scale } = calibration; + + if (scale && !calibratedRowSpacing) { + calibratedRowSpacing = (imagePlaneModule.rowPixelSpacing || 1) / scale; + calibratedColumnSpacing = + (imagePlaneModule.columnPixelSpacing || 1) / scale; + } + + if (!scale && !calibratedRowSpacing) return imagePlaneModule; // Todo: This is necessary in general, but breaks an edge case when an image // is calibrated to some other spacing, and it gets calibrated BACK to the @@ -620,45 +628,28 @@ class StackViewport extends Viewport implements IStackViewport { return imagePlaneModule; } + this.hasPixelSpacing = calibratedRowSpacing && calibratedColumnSpacing; + // Check if there is already an actor const imageDataMetadata = this.getImageData(); - // If no actor (first load) and calibration matches the dicom header - if ( - !imageDataMetadata && - imagePlaneModule.rowPixelSpacing === calibratedRowSpacing && - imagePlaneModule.columnPixelSpacing === calibratedColumnSpacing - ) { - return imagePlaneModule; - } - - this.calibration = { - type: CalibrationTypes.USER, - pixelSpacing: [calibratedColumnSpacing, calibratedRowSpacing], - }; - this.hasPixelSpacing = calibratedRowSpacing && calibratedColumnSpacing; - // If no actor (first load) and calibration doesn't match headers // -> needs calibration - if ( - !imageDataMetadata && - (imagePlaneModule.rowPixelSpacing !== calibratedRowSpacing || - imagePlaneModule.columnPixelSpacing !== calibratedColumnSpacing) - ) { + if (!imageDataMetadata) { this._publishCalibratedEvent = true; this._calibrationEvent = { rowScale: calibratedRowSpacing / imagePlaneModule.rowPixelSpacing, columnScale: calibratedColumnSpacing / imagePlaneModule.columnPixelSpacing, + calibration, }; // modify the calibration object to store the actual updated values applied - calibratedPixelSpacing.appliedSpacing = calibratedPixelSpacing; + calibration.appliedSpacing = calibration; // This updates the render copy imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; - imagePlaneModule.calibration = this.calibration; return imagePlaneModule; } @@ -667,15 +658,13 @@ class StackViewport extends Viewport implements IStackViewport { const [columnPixelSpacing, rowPixelSpacing] = imageData.getSpacing(); // modify the calibration object to store the actual updated values applied - calibratedPixelSpacing.appliedSpacing = calibratedPixelSpacing; imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; - imagePlaneModule.calibration = this.calibration; // If current actor spacing matches the calibrated spacing if ( rowPixelSpacing === calibratedRowSpacing && - columnPixelSpacing === calibratedPixelSpacing + columnPixelSpacing === calibratedColumnSpacing ) { // No calibration is required return imagePlaneModule; @@ -687,6 +676,7 @@ class StackViewport extends Viewport implements IStackViewport { this._calibrationEvent = { rowScale: calibratedRowSpacing / rowPixelSpacing, columnScale: calibratedColumnSpacing / columnPixelSpacing, + calibration, }; return imagePlaneModule; diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 9320584108..6170ec3490 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -6,11 +6,17 @@ import CalibrationTypes from '../enums/CalibrationTypes'; */ export interface IImageCalibration { /** The pixel spacing for the image, in mm between pixel centers */ - pixelSpacing: [number, number]; + rowPixelSpacing?: number; + columnPixelSpacing?: number; + + /** The scaling of this image - new spacing = original pixelSpacing/scale */ + scale?: number; + /** The type of the pixel spacing, distinguishing between various * types projection (CR/DX/MG) spacing and volumetric spacing ('') */ type: CalibrationTypes; + // A tooltip which can be used to explain the calibration information tooltip?: string; /** Indication that the image has some spacing information (the pixelSpacing diff --git a/packages/core/src/utilities/calibratedPixelSpacingMetadataProvider.ts b/packages/core/src/utilities/calibratedPixelSpacingMetadataProvider.ts index 623ad9f4fe..4944a72a4f 100644 --- a/packages/core/src/utilities/calibratedPixelSpacingMetadataProvider.ts +++ b/packages/core/src/utilities/calibratedPixelSpacingMetadataProvider.ts @@ -1,13 +1,7 @@ import imageIdToURI from './imageIdToURI'; +import { IImageCalibration } from '../types'; -export type CalibratedPixelValue = { - rowPixelSpacing: number; - columnPixelSpacing: number; - // These values get updated by the viewport after the change to record the applied value - appliedSpacing?: CalibratedPixelValue; -}; - -const state: Record = {}; // Calibrated pixel spacing per imageId +const state: Record = {}; // Calibrated pixel spacing per imageId /** * Simple metadataProvider object to store metadata for calibrated spacings. @@ -20,7 +14,7 @@ const metadataProvider = { * @param imageId - the imageId for the metadata to store * @param payload - the payload composed of new calibrated pixel spacings */ - add: (imageId: string, payload: CalibratedPixelValue): void => { + add: (imageId: string, payload: IImageCalibration): void => { const imageURI = imageIdToURI(imageId); state[imageURI] = payload; }, @@ -31,7 +25,7 @@ const metadataProvider = { * @param imageId - the imageId to enquire about * @returns the calibrated pixel spacings for the imageId if it exists, otherwise undefined */ - get: (type: string, imageId: string): CalibratedPixelValue => { + get: (type: string, imageId: string): IImageCalibration => { if (type === 'calibratedPixelSpacing') { const imageURI = imageIdToURI(imageId); return state[imageURI]; diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 2fb12f29eb..12bb552442 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -194,7 +194,7 @@ const calibrationFunctions: Record = {}; const calibrations = [ { value: 'Default', - selected: 'clearCalibration', + selected: 'userCalibration', }, { value: 'User Calibration 0.5', @@ -205,14 +205,31 @@ const calibrations = [ }, }, { - value: 'Uncalibrated', - selected: 'applyMetadata', - metadata: { - '00280030': null, + value: 'ERMF 2', + selected: 'userCalibration', + calibration: { + scale: 2, + type: Enums.CalibrationTypes.ERMF, + }, + }, + { + value: 'Projected', + selected: 'userCalibration', + calibration: { + scale: 1, + type: Enums.CalibrationTypes.PROJECTION, }, }, { - value: 'Uncalibrated', + value: 'Error', + selected: 'userCalibration', + calibration: { + scale: 1, + type: Enums.CalibrationTypes.ERROR, + }, + }, + { + value: 'px units', selected: 'applyMetadata', metadata: { '00280030': null, @@ -314,16 +331,16 @@ async function run() { const renderingEngine = new RenderingEngine(renderingEngineId); calibrationFunctions.userCalibration = function calibrationSelected() { - utilities.calibrateImageSpacing(imageIds[0], renderingEngine, 2, 2); - }; - calibrationFunctions.clearCalibration = function clearCalibration() { - utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null, null); + utilities.calibrateImageSpacing( + imageIds[0], + renderingEngine, + this.calibration + ); }; calibrationFunctions.applyMetadata = function applyMetadata() { const instance = wadors.metaDataManager.get(imageIds[0]); - console.log('Applying', this.metadata); Object.assign(instance, this.metadata); - utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null, null); + utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null); }; // Create a stack viewport diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index 685eb3c0b4..c2cada9b7c 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -1,4 +1,4 @@ -import { utilities } from '@cornerstonejs/core'; +import { utilities, Enums } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; const { calibratedPixelSpacingMetadataProvider } = utilities; @@ -15,23 +15,19 @@ const { calibratedPixelSpacingMetadataProvider } = utilities; export default function calibrateImageSpacing( imageId: string, renderingEngine: Types.IRenderingEngine, - rowPixelSpacing: number, - columnPixelSpacing: number + spacing: Types.IImageCalibration | number, + columnPixelSpacing?: number ): void { - // 1. Add the calibratedPixelSpacing metadata to the metadata provider - // If no column spacing provided, assume square pixels - if (!columnPixelSpacing) { - columnPixelSpacing = rowPixelSpacing; - } - - if (!rowPixelSpacing) { - calibratedPixelSpacingMetadataProvider.add(imageId, null); - } else { - calibratedPixelSpacingMetadataProvider.add(imageId, { - rowPixelSpacing, - columnPixelSpacing, - }); + // Handle prior change versions + if (typeof spacing === 'number') { + spacing = { + type: Enums.CalibrationTypes.USER, + rowPixelSpacing: spacing, + columnPixelSpacing: columnPixelSpacing || spacing, + }; } + // 1. Add the calibratedPixelSpacing metadata to the metadata + calibratedPixelSpacingMetadataProvider.add(imageId, spacing); // 2. Update the actor for stackViewports const viewports = renderingEngine.getStackViewports(); From 3465f64ba708fb491e1c7c4cd7904f28805ed729 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 15:38:53 -0400 Subject: [PATCH 14/37] PR requested changes --- packages/tools/examples/stackAnnotationTools/index.ts | 9 +++++---- packages/tools/src/tools/annotation/BidirectionalTool.ts | 2 +- packages/tools/src/tools/annotation/CircleROITool.ts | 2 +- packages/tools/src/tools/annotation/EllipticalROITool.ts | 2 +- packages/tools/src/tools/annotation/LengthTool.ts | 2 +- .../tools/src/tools/annotation/PlanarFreehandROITool.ts | 2 +- packages/tools/src/tools/annotation/RectangleROITool.ts | 2 +- .../{calibratedLengthUnits.ts => calibratedUnits.ts} | 0 8 files changed, 11 insertions(+), 10 deletions(-) rename packages/tools/src/utilities/{calibratedLengthUnits.ts => calibratedUnits.ts} (100%) diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 12bb552442..aed65adc91 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -213,18 +213,19 @@ const calibrations = [ }, }, { - value: 'Projected', + value: 'Projected 1.02', selected: 'userCalibration', calibration: { - scale: 1, + // Bug right now in StackViewport that fails to reset + scale: 1.02, type: Enums.CalibrationTypes.PROJECTION, }, }, { - value: 'Error', + value: 'Error 0.98', selected: 'userCalibration', calibration: { - scale: 1, + scale: 0.98, type: Enums.CalibrationTypes.ERROR, }, }, diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index 80f273931f..f21cb5c10b 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -7,7 +7,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import calibratedLengthUnits from '../../utilities/calibratedLengthUnits'; +import calibratedLengthUnits from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 9c1b025020..c19bde23c5 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -12,7 +12,7 @@ import type { Types } from '@cornerstonejs/core'; import { calibratedLengthUnits, calibratedAreaUnits, -} from '../../utilities/calibratedLengthUnits'; +} from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 27306d683f..8b89533078 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -9,7 +9,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 1c86f3ca5d..abd406b933 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -7,7 +7,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import calibratedLengthUnits from '../../utilities/calibratedLengthUnits'; +import calibratedLengthUnits from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index ad46fbde3d..be340733c0 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -10,7 +10,7 @@ import { import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; -import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 5b911de629..fcc2395bdd 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -9,7 +9,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits } from '../../utilities/calibratedLengthUnits'; +import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { diff --git a/packages/tools/src/utilities/calibratedLengthUnits.ts b/packages/tools/src/utilities/calibratedUnits.ts similarity index 100% rename from packages/tools/src/utilities/calibratedLengthUnits.ts rename to packages/tools/src/utilities/calibratedUnits.ts From 05e26c639e3881233d7b5a2f7d0fb77d3e905395 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 15:53:19 -0400 Subject: [PATCH 15/37] PR changes --- common/reviews/api/tools.api.md | 2 +- packages/tools/src/utilities/index.ts | 2 +- packages/tools/src/utilities/roundNumber.ts | 13 ++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index e8339c17aa..9a8d80b38d 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -5386,7 +5386,7 @@ declare namespace utilities { planarFreehandROITool, stackPrefetch, scroll_2 as scroll, - roundNumber as roundMeasurement + roundNumber } } export { utilities } diff --git a/packages/tools/src/utilities/index.ts b/packages/tools/src/utilities/index.ts index d0a91d0376..1a2ffac796 100644 --- a/packages/tools/src/utilities/index.ts +++ b/packages/tools/src/utilities/index.ts @@ -66,5 +66,5 @@ export { planarFreehandROITool, stackPrefetch, scroll, - roundNumber as roundMeasurement, + roundNumber, }; diff --git a/packages/tools/src/utilities/roundNumber.ts b/packages/tools/src/utilities/roundNumber.ts index a0c1a7f5e9..d338964974 100644 --- a/packages/tools/src/utilities/roundNumber.ts +++ b/packages/tools/src/utilities/roundNumber.ts @@ -1,12 +1,11 @@ /** - * Truncates to a relative number of places to show numbers cleanly such - * that in addition to the leading digit, there are precision additional - * digits. The default precision shows numbers to at least 1/10 % + * Truncates decimal points to that there is at least 1+precision significant + * digits. * - * For example, with the default precision 2: - * 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 + * 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 From 94bd2a02d9821fbf18962faa5d40acf3b3cb2069 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 17:00:20 -0400 Subject: [PATCH 16/37] Fix the calibration application so it works properly --- common/reviews/api/core.api.md | 5 +- .../api/streaming-image-volume-loader.api.md | 5 +- common/reviews/api/tools.api.md | 5 +- .../core/src/RenderingEngine/StackViewport.ts | 87 +++---------------- packages/core/src/types/EventTypes.ts | 5 +- .../core/src/utilities/imageToWorldCoords.ts | 11 ++- .../core/src/utilities/worldToImageCoords.ts | 13 ++- .../tools/src/tools/annotation/LengthTool.ts | 9 +- .../src/tools/base/AnnotationDisplayTool.ts | 62 ++----------- .../src/utilities/calibrateImageSpacing.ts | 3 +- .../tools/src/utilities/calibratedUnits.ts | 4 +- 11 files changed, 56 insertions(+), 153 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 68b6e08349..bf7f991e1f 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1433,8 +1433,9 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale: number; - columnScale: number; + rowScale?: number; + columnScale?: number; + scale?: number; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 3acbd8889a..50a6e6d075 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -1024,8 +1024,9 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale: number; - columnScale: number; + rowScale?: number; + columnScale?: number; + scale?: number; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 9a8d80b38d..94b9b35696 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -2724,8 +2724,9 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale: number; - columnScale: number; + rowScale?: number; + columnScale?: number; + scale?: number; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index f49b0ab776..4ebe3dd09c 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -94,8 +94,9 @@ interface ImageDataMetaData { } // TODO This needs to be exposed as its published to consumers. type CalibrationEvent = { - rowScale: number; - columnScale: number; + rowScale?: number; + columnScale?: number; + scale: number; calibration: IImageCalibration; }; @@ -599,83 +600,17 @@ class StackViewport extends Viewport implements IStackViewport { */ private calibrateIfNecessary(imageId, imagePlaneModule) { const calibration = metaData.get('calibratedPixelSpacing', imageId); - this.calibration = calibration; + const isUpdated = this.calibration !== calibration; + const { scale } = calibration || {}; + this.hasPixelSpacing = scale > 0 || imagePlaneModule.rowPixelSpacing > 0; imagePlaneModule.calibration = calibration; - if (!calibration) return imagePlaneModule; - - let { - rowPixelSpacing: calibratedRowSpacing, - columnPixelSpacing: calibratedColumnSpacing, - } = calibration; - const { scale } = calibration; - - if (scale && !calibratedRowSpacing) { - calibratedRowSpacing = (imagePlaneModule.rowPixelSpacing || 1) / scale; - calibratedColumnSpacing = - (imagePlaneModule.columnPixelSpacing || 1) / scale; - } + if (!isUpdated) return imagePlaneModule; - if (!scale && !calibratedRowSpacing) return imagePlaneModule; - - // Todo: This is necessary in general, but breaks an edge case when an image - // is calibrated to some other spacing, and it gets calibrated BACK to the - // original spacing. - if ( - imagePlaneModule.rowPixelSpacing === calibratedRowSpacing && - imagePlaneModule.columnPixelSpacing === calibratedColumnSpacing - ) { - return imagePlaneModule; - } - - this.hasPixelSpacing = calibratedRowSpacing && calibratedColumnSpacing; - - // Check if there is already an actor - const imageDataMetadata = this.getImageData(); - - // If no actor (first load) and calibration doesn't match headers - // -> needs calibration - if (!imageDataMetadata) { - this._publishCalibratedEvent = true; - - this._calibrationEvent = { - rowScale: calibratedRowSpacing / imagePlaneModule.rowPixelSpacing, - columnScale: - calibratedColumnSpacing / imagePlaneModule.columnPixelSpacing, - calibration, - }; - - // modify the calibration object to store the actual updated values applied - calibration.appliedSpacing = calibration; - // This updates the render copy - imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; - imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; - return imagePlaneModule; - } - - // If there is already an actor, check if calibration is needed for the current actor - const { imageData } = imageDataMetadata; - const [columnPixelSpacing, rowPixelSpacing] = imageData.getSpacing(); - - // modify the calibration object to store the actual updated values applied - imagePlaneModule.rowPixelSpacing = calibratedRowSpacing; - imagePlaneModule.columnPixelSpacing = calibratedColumnSpacing; - - // If current actor spacing matches the calibrated spacing - if ( - rowPixelSpacing === calibratedRowSpacing && - columnPixelSpacing === calibratedColumnSpacing - ) { - // No calibration is required - return imagePlaneModule; - } - - // Calibration is required + this.calibration = calibration; this._publishCalibratedEvent = true; - this._calibrationEvent = { - rowScale: calibratedRowSpacing / rowPixelSpacing, - columnScale: calibratedColumnSpacing / columnPixelSpacing, + scale, calibration, }; @@ -2731,12 +2666,12 @@ class StackViewport extends Viewport implements IStackViewport { if (!newImagePlaneModule.columnPixelSpacing) { newImagePlaneModule.columnPixelSpacing = 1; - this.hasPixelSpacing = false; + this.hasPixelSpacing = this.calibration?.scale > 0; } if (!newImagePlaneModule.rowPixelSpacing) { newImagePlaneModule.rowPixelSpacing = 1; - this.hasPixelSpacing = false; + this.hasPixelSpacing = this.calibration?.scale > 0; } if (!newImagePlaneModule.columnCosines) { diff --git a/packages/core/src/types/EventTypes.ts b/packages/core/src/types/EventTypes.ts index c0fb802764..cabcba9058 100644 --- a/packages/core/src/types/EventTypes.ts +++ b/packages/core/src/types/EventTypes.ts @@ -223,8 +223,9 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale: number; - columnScale: number; + rowScale?: number; + columnScale?: number; + scale?: number; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/packages/core/src/utilities/imageToWorldCoords.ts b/packages/core/src/utilities/imageToWorldCoords.ts index 1fe674d4e8..5c2921c03e 100644 --- a/packages/core/src/utilities/imageToWorldCoords.ts +++ b/packages/core/src/utilities/imageToWorldCoords.ts @@ -16,6 +16,10 @@ export default function imageToWorldCoords( imageCoords: Point2 ): Point3 | undefined { const imagePlaneModule = metaData.get('imagePlaneModule', imageId); + const calibratedPixelSpacing = metaData.get( + 'calibratedPixelSpacing', + imageId + ); if (!imagePlaneModule) { throw new Error(`No imagePlaneModule found for imageId: ${imageId}`); @@ -23,12 +27,15 @@ export default function imageToWorldCoords( const { columnCosines, - columnPixelSpacing, rowCosines, - rowPixelSpacing, imagePositionPatient: origin, } = imagePlaneModule; + let { columnPixelSpacing, rowPixelSpacing } = + calibratedPixelSpacing || imagePlaneModule; + columnPixelSpacing ||= 1; + rowPixelSpacing ||= 1; + // calculate the image coordinates in the world space const imageCoordsInWorld = vec3.create(); diff --git a/packages/core/src/utilities/worldToImageCoords.ts b/packages/core/src/utilities/worldToImageCoords.ts index ca74d7b755..65dff115d3 100644 --- a/packages/core/src/utilities/worldToImageCoords.ts +++ b/packages/core/src/utilities/worldToImageCoords.ts @@ -17,6 +17,10 @@ function worldToImageCoords( worldCoords: Point3 ): Point2 | undefined { const imagePlaneModule = metaData.get('imagePlaneModule', imageId); + const calibratedPixelSpacing = metaData.get( + 'calibratedPixelSpacing', + imageId + ); if (!imagePlaneModule) { throw new Error(`No imagePlaneModule found for imageId: ${imageId}`); @@ -27,14 +31,15 @@ function worldToImageCoords( const { columnCosines, - columnPixelSpacing, rowCosines, - rowPixelSpacing, imagePositionPatient: origin, - rows, - columns, } = imagePlaneModule; + let { columnPixelSpacing, rowPixelSpacing } = + calibratedPixelSpacing || imagePlaneModule; + columnPixelSpacing ||= 1; + rowPixelSpacing ||= 1; + // The origin is the image position patient, but since image coordinates start // from [0,0] for the top left hand of the first pixel, and the origin is at the // center of the first pixel, we need to account for this. diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index abd406b933..0729f8e788 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -7,7 +7,10 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import calibratedLengthUnits from '../../utilities/calibratedUnits'; +import { + calibratedLengthUnits, + getScale, +} from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; @@ -45,7 +48,6 @@ import { TextBoxHandle, PublicToolProps, ToolProps, - InteractionTypes, SVGDrawingHelper, } from '../../types'; import { LengthAnnotation } from '../../types/ToolSpecificAnnotationTypes'; @@ -815,8 +817,9 @@ class LengthTool extends AnnotationTool { } const { imageData, dimensions } = image; + const scale = getScale(image); - const length = this._calculateLength(worldPos1, worldPos2); + const length = this._calculateLength(worldPos1, worldPos2) / scale; const index1 = transformWorldToIndex(imageData, worldPos1); const index2 = transformWorldToIndex(imageData, worldPos2); diff --git a/packages/tools/src/tools/base/AnnotationDisplayTool.ts b/packages/tools/src/tools/base/AnnotationDisplayTool.ts index 277130ed41..73f4aa0c7c 100644 --- a/packages/tools/src/tools/base/AnnotationDisplayTool.ts +++ b/packages/tools/src/tools/base/AnnotationDisplayTool.ts @@ -7,8 +7,6 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { vec4 } from 'gl-matrix'; - import BaseTool from './BaseTool'; import { getAnnotationManager } from '../../stateManagement/annotation/annotationState'; import { Annotation, Annotations, SVGDrawingHelper } from '../../types'; @@ -30,6 +28,7 @@ import { StyleSpecifier } from '../../types/AnnotationStyle'; */ abstract class AnnotationDisplayTool extends BaseTool { static toolName; + // =================================================================== // Abstract Methods - Must be implemented. // =================================================================== @@ -83,31 +82,16 @@ abstract class AnnotationDisplayTool extends BaseTool { public onImageSpacingCalibrated = ( evt: Types.EventTypes.ImageSpacingCalibratedEvent ) => { - const { - element, - rowScale, - columnScale, - imageId, - imageData: calibratedImageData, - worldToIndex: nonCalibratedWorldToIndex, - } = evt.detail; - - const { viewport } = getEnabledElement(element); - - if (viewport instanceof VolumeViewport) { - throw new Error('Cannot calibrate a volume viewport'); - } - - const calibratedIndexToWorld = calibratedImageData.getIndexToWorld(); + const { element, imageId } = evt.detail; const imageURI = utilities.imageIdToURI(imageId); - const stateManager = getAnnotationManager(); - const framesOfReference = stateManager.getFramesOfReference(); + const annotationManager = getAnnotationManager(); + const framesOfReference = annotationManager.getFramesOfReference(); // For each frame Of Reference framesOfReference.forEach((frameOfReference) => { const frameOfReferenceSpecificAnnotations = - stateManager.getAnnotations(frameOfReference); + annotationManager.getAnnotations(frameOfReference); const toolSpecificAnnotations = frameOfReferenceSpecificAnnotations[this.getToolName()]; @@ -128,44 +112,8 @@ abstract class AnnotationDisplayTool extends BaseTool { // we can update the cachedStats and also rendering annotation.invalidated = true; annotation.data.cachedStats = {}; - - // Update annotation points to the new calibrated points. Basically, - // using the worldToIndex function we get the index on the non-calibrated - // image and then using the calibratedIndexToWorld function we get the - // corresponding point on the calibrated image world. - annotation.data.handles.points = annotation.data.handles.points.map( - (point) => { - const p = vec4.fromValues(...(point as Types.Point3), 1); - const pCalibrated = vec4.fromValues(0, 0, 0, 1); - const nonCalibratedIndexVec4 = vec4.create(); - vec4.transformMat4( - nonCalibratedIndexVec4, - p, - nonCalibratedWorldToIndex - ); - const calibratedIndex = [ - columnScale * nonCalibratedIndexVec4[0], - rowScale * nonCalibratedIndexVec4[1], - nonCalibratedIndexVec4[2], - ]; - - vec4.transformMat4( - pCalibrated, - vec4.fromValues( - calibratedIndex[0], - calibratedIndex[1], - calibratedIndex[2], - 1 - ), - calibratedIndexToWorld - ); - - return pCalibrated.slice(0, 3) as Types.Point3; - } - ); } }); - triggerAnnotationRender(element); }); }; diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index c2cada9b7c..a1f03f1c92 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -22,8 +22,7 @@ export default function calibrateImageSpacing( if (typeof spacing === 'number') { spacing = { type: Enums.CalibrationTypes.USER, - rowPixelSpacing: spacing, - columnPixelSpacing: columnPixelSpacing || spacing, + scale: 1 / spacing, }; } // 1. Add the calibratedPixelSpacing metadata to the metadata diff --git a/packages/tools/src/utilities/calibratedUnits.ts b/packages/tools/src/utilities/calibratedUnits.ts index 5edb641755..20be64e2ad 100644 --- a/packages/tools/src/utilities/calibratedUnits.ts +++ b/packages/tools/src/utilities/calibratedUnits.ts @@ -32,6 +32,8 @@ const calibratedAreaUnits = (handles, image): string => { return `${units} ${calibration.type}`; }; +const getScale = (image) => image.calibration?.scale || 1; + export default calibratedLengthUnits; -export { calibratedAreaUnits, calibratedLengthUnits }; +export { calibratedAreaUnits, calibratedLengthUnits, getScale }; From f4ce104a8a596777db2e2fa94aa0b1fa4b6fafcf Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 17:18:09 -0400 Subject: [PATCH 17/37] Fix remainder of tools --- .../tools/examples/stackAnnotationTools/index.ts | 12 ++++++++---- .../tools/src/tools/annotation/BidirectionalTool.ts | 13 ++++++++----- .../tools/src/tools/annotation/CircleROITool.ts | 11 ++++++++--- .../tools/src/tools/annotation/EllipticalROITool.ts | 8 ++++++-- .../src/tools/annotation/PlanarFreehandROITool.ts | 6 ++++-- .../tools/src/tools/annotation/RectangleROITool.ts | 5 +++-- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index aed65adc91..35737dac72 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -32,6 +32,7 @@ const { CobbAngleTool, ToolGroupManager, ArrowAnnotateTool, + PlanarFreehandROITool, Enums: csToolsEnums, utilities, } = cornerstoneTools; @@ -109,6 +110,7 @@ const toolsNames = [ AngleTool.toolName, CobbAngleTool.toolName, ArrowAnnotateTool.toolName, + PlanarFreehandROITool.toolName, ]; let selectedToolName = toolsNames[0]; @@ -213,19 +215,19 @@ const calibrations = [ }, }, { - value: 'Projected 1.02', + value: 'Projected 1', selected: 'userCalibration', calibration: { // Bug right now in StackViewport that fails to reset - scale: 1.02, + scale: 1, type: Enums.CalibrationTypes.PROJECTION, }, }, { - value: 'Error 0.98', + value: 'Error 1', selected: 'userCalibration', calibration: { - scale: 0.98, + scale: 1, type: Enums.CalibrationTypes.ERROR, }, }, @@ -283,6 +285,7 @@ async function run() { cornerstoneTools.addTool(AngleTool); cornerstoneTools.addTool(CobbAngleTool); cornerstoneTools.addTool(ArrowAnnotateTool); + cornerstoneTools.addTool(PlanarFreehandROITool); // Define a tool group, which defines how mouse events map to tool commands for // Any viewport using the group @@ -298,6 +301,7 @@ async function run() { toolGroup.addTool(AngleTool.toolName); toolGroup.addTool(CobbAngleTool.toolName); toolGroup.addTool(ArrowAnnotateTool.toolName); + toolGroup.addTool(PlanarFreehandROITool.toolName); // Set the initial state of the tools, here we set one tool active on left click. // This means left click will draw that tool. diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index f21cb5c10b..fa07c976d1 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -7,7 +7,10 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import calibratedLengthUnits from '../../utilities/calibratedUnits'; +import { + calibratedLengthUnits, + getScale, +} from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; @@ -1293,10 +1296,10 @@ class BidirectionalTool extends AnnotationTool { continue; } - const { imageData, dimensions, hasPixelSpacing } = image; - - const dist1 = this._calculateLength(worldPos1, worldPos2); - const dist2 = this._calculateLength(worldPos3, worldPos4); + const { imageData, dimensions } = image; + const scale = getScale(image); + const dist1 = this._calculateLength(worldPos1, worldPos2) / scale; + const dist2 = this._calculateLength(worldPos3, worldPos4) / scale; const length = dist1 > dist2 ? dist1 : dist2; const width = dist1 > dist2 ? dist2 : dist1; diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index c19bde23c5..92ac138960 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -12,6 +12,7 @@ import type { Types } from '@cornerstonejs/core'; import { calibratedLengthUnits, calibratedAreaUnits, + getScale, } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; @@ -1001,7 +1002,11 @@ class CircleROITool extends AnnotationTool { worldPos2 ); const isEmptyArea = worldWidth === 0 && worldHeight === 0; - const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)); + const scale = getScale(image); + const area = + Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)) / + scale / + scale; let count = 0; let mean = 0; @@ -1050,9 +1055,9 @@ class CircleROITool extends AnnotationTool { stdDev, isEmptyArea, areaUnit: calibratedAreaUnits(null, image), - radius: worldWidth / 2, + radius: worldWidth / 2 / scale, radiusUnit: calibratedLengthUnits(null, image), - perimeter: 2 * Math.PI * (worldWidth / 2), + perimeter: (2 * Math.PI * (worldWidth / 2)) / scale, }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 8b89533078..6ce8f446cb 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -9,7 +9,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; +import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { @@ -1110,7 +1110,11 @@ class EllipticalROITool extends AnnotationTool { worldPos2 ); const isEmptyArea = worldWidth === 0 && worldHeight === 0; - const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)); + const scale = getScale(image); + const area = + Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)) / + scale / + scale; let count = 0; let mean = 0; diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index be340733c0..decabb2b9e 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -10,7 +10,7 @@ import { import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; -import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; +import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; @@ -727,7 +727,9 @@ class PlanarFreehandROITool extends AnnotationTool { const { imageData, metadata } = image; const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)); - const area = polyline.calculateAreaOfPoints(canvasCoordinates); + const scale = getScale(image); + const area = + polyline.calculateAreaOfPoints(canvasCoordinates) / scale / scale; const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[0]); worldPosIndex[0] = Math.floor(worldPosIndex[0]); diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index fcc2395bdd..924441044a 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -9,7 +9,7 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits } from '../../utilities/calibratedUnits'; +import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { @@ -953,8 +953,9 @@ class RectangleROITool extends AnnotationTool { worldPos1, worldPos2 ); + const scale = getScale(image); - const area = Math.abs(worldWidth * worldHeight); + const area = Math.abs(worldWidth * worldHeight) / (scale * scale); let count = 0; let mean = 0; From cfaa2da5b83b0374193ac14893cc5cf865fb5505 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 17:37:53 -0400 Subject: [PATCH 18/37] Fix tests --- .../src/utilities/calibrateImageSpacing.ts | 7 +- packages/tools/test/LengthTool_test.js | 168 ++++++++++-------- 2 files changed, 98 insertions(+), 77 deletions(-) diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index a1f03f1c92..42854e7076 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -15,14 +15,13 @@ const { calibratedPixelSpacingMetadataProvider } = utilities; export default function calibrateImageSpacing( imageId: string, renderingEngine: Types.IRenderingEngine, - spacing: Types.IImageCalibration | number, - columnPixelSpacing?: number + spacing: Types.IImageCalibration | number ): void { - // Handle prior change versions + // Handle simple parameter version if (typeof spacing === 'number') { spacing = { type: Enums.CalibrationTypes.USER, - scale: 1 / spacing, + scale: spacing, }; } // 1. Add the calibratedPixelSpacing metadata to the metadata diff --git a/packages/tools/test/LengthTool_test.js b/packages/tools/test/LengthTool_test.js index 0f6e55d442..a996f0a656 100644 --- a/packages/tools/test/LengthTool_test.js +++ b/packages/tools/test/LengthTool_test.js @@ -24,8 +24,11 @@ const { Enums: csToolsEnums, cancelActiveManipulations, annotation, + utilities: toolsUtilities, } = csTools3d; +const { calibrateImageSpacing } = toolsUtilities; + const { Events: csToolsEvents } = csToolsEnums; const { @@ -1062,47 +1065,56 @@ describe('LengthTool:', () => { }); }); - /** Todo: this is a flaky test + /** Test that the calibration works as expected when provided a calibrated + * scale value. + */ describe('Calibration ', () => { + const FOR = 'for'; + beforeEach(function () { - csTools3d.init() - csTools3d.addTool(LengthTool) - cache.purgeCache() - this.stackToolGroup = ToolGroupManager.createToolGroup('stack') + csTools3d.init(); + csTools3d.addTool(LengthTool); + cache.purgeCache(); + this.stackToolGroup = ToolGroupManager.createToolGroup('stack'); this.stackToolGroup.addTool(LengthTool.toolName, { configuration: {}, - }) + }); this.stackToolGroup.setToolActive(LengthTool.toolName, { bindings: [{ mouseButton: 1 }], - }) + }); - this.renderingEngine = new RenderingEngine(renderingEngineId) - imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader) - volumeLoader.registerVolumeLoader('fakeVolumeLoader', fakeVolumeLoader) - metaData.addProvider(fakeMetaDataProvider, 10000) + this.renderingEngine = new RenderingEngine(renderingEngineId); + imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader); + volumeLoader.registerVolumeLoader('fakeVolumeLoader', fakeVolumeLoader); + metaData.addProvider(fakeMetaDataProvider, 10000); metaData.addProvider( - calibratedPixelSpacingMetadataProvider.get.bind( - calibratedPixelSpacingMetadataProvider + utilities.calibratedPixelSpacingMetadataProvider.get.bind( + utilities.calibratedPixelSpacingMetadataProvider ), 11000 - ) - }) + ); + }); afterEach(function () { - csTools3d.destroy() - eventTarget.reset() - cache.purgeCache() - this.renderingEngine.destroy() - metaData.removeProvider(fakeMetaDataProvider) - imageLoader.unregisterAllImageLoaders() - ToolGroupManager.destroyToolGroup('stack') - - DOMElements.forEach((el) => { - if (el.parentNode) { - el.parentNode.removeChild(el) - } - }) - }) + try { + csTools3d.destroy(); + eventTarget.reset(); + cache.purgeCache(); + this.renderingEngine.destroy(); + metaData.removeProvider(fakeMetaDataProvider); + imageLoader.unregisterAllImageLoaders(); + ToolGroupManager.destroyToolGroup('stack'); + + if (!this.DOMElements) return; + this.DOMElements.forEach((el) => { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + }); + } catch (e) { + console.warn(e); + } + }); it('Should be able to calibrate an image and update the tool', function (done) { const element = createViewport( @@ -1110,54 +1122,62 @@ describe('LengthTool:', () => { ViewportType.STACK, 256, 256 - ) + ); - const imageId1 = 'fakeImageLoader:imageURI_64_64_4_40_1_1_0_1' + const imageId1 = 'fakeImageLoader:imageURI_64_64_4_40_1_1_0_1'; - const vp = this.renderingEngine.getViewport(viewportId) + const vp = this.renderingEngine.getViewport(viewportId); + const scale = 1.5; + const index1 = [32, 32, 0]; + const index2 = [10, 1, 0]; const secondCallback = () => { - const lengthAnnotations = annotation.state.getAnnotations(LengthTool.toolName, FOR) + const lengthAnnotations = annotation.state.getAnnotations( + LengthTool.toolName, + element + ); // Can successfully add Length tool to annotationManager - expect(lengthAnnotations).toBeDefined() - expect(lengthAnnotations.length).toBe(1) - - const lengthAnnotation = lengthAnnotations[0] - expect(lengthAnnotation.metadata.toolName).toBe(LengthTool.toolName) - expect(lengthAnnotation.invalidated).toBe(false) - expect(lengthAnnotation.highlighted).toBe(true) - - const data = lengthAnnotation.data.cachedStats - const targets = Array.from(Object.keys(data)) - expect(targets.length).toBe(1) - - expect(data[targets[0]].length).toBe(calculateLength(p1, p2)) + expect(lengthAnnotations).toBeDefined(); + expect(lengthAnnotations.length).toBe(1); + + const lengthAnnotation = lengthAnnotations[0]; + expect(lengthAnnotation.metadata.toolName).toBe(LengthTool.toolName); + expect(lengthAnnotation.invalidated).toBe(false); + expect(lengthAnnotation.highlighted).toBe(true); + + const data = lengthAnnotation.data.cachedStats; + const targets = Array.from(Object.keys(data)); + expect(targets.length).toBe(1); + + console.log('data', data, targets[0]); + expect(data[targets[0]].length).toBeCloseTo( + calculateLength(index1, index2) / scale, + 0.05 + ); - annotation.state.removeAnnotation(lengthAnnotation.annotationUID) - done() - } + annotation.state.removeAnnotation(lengthAnnotation.annotationUID); + done(); + }; const firstCallback = () => { - element.removeEventListener(Events.IMAGE_RENDERED, firstCallback) - element.addEventListener(Events.IMAGE_RENDERED, secondCallback) - const index1 = [32, 32, 0] - const index2 = [10, 1, 0] + element.removeEventListener(Events.IMAGE_RENDERED, firstCallback); + element.addEventListener(Events.IMAGE_RENDERED, secondCallback); - const { imageData } = vp.getImageData() + const { imageData } = vp.getImageData(); const { pageX: pageX1, pageY: pageY1, clientX: clientX1, clientY: clientY1, - } = createNormalizedMouseEvent(imageData, index1, element, vp) + } = createNormalizedMouseEvent(imageData, index1, element, vp); const { pageX: pageX2, pageY: pageY2, clientX: clientX2, clientY: clientY2, - } = createNormalizedMouseEvent(imageData, index2, element, vp) + } = createNormalizedMouseEvent(imageData, index2, element, vp); let evt = new MouseEvent('mousedown', { target: element, @@ -1166,8 +1186,8 @@ describe('LengthTool:', () => { clientY: clientY1, pageX: pageX1, pageY: pageY1, - }) - element.dispatchEvent(evt) + }); + element.dispatchEvent(evt); evt = new MouseEvent('mousemove', { target: element, @@ -1176,34 +1196,36 @@ describe('LengthTool:', () => { clientY: clientY2, pageX: pageX2, pageY: pageY2, - }) - document.dispatchEvent(evt) + }); + document.dispatchEvent(evt); // Mouse Up instantly after - evt = new MouseEvent('mouseup') + evt = new MouseEvent('mouseup'); // Since there is tool rendering happening for any mouse event // we just attach a listener before the last one -> mouse up - document.dispatchEvent(evt) + document.dispatchEvent(evt); const imageId = this.renderingEngine .getViewport(viewportId) - .getCurrentImageId() + .getCurrentImageId(); - calibrateImageSpacing(imageId, this.renderingEngine, 1, 5) - } + console.log('Starting image calibration'); + calibrateImageSpacing(imageId, this.renderingEngine, scale); + console.log('Done image calibration'); + }; - element.addEventListener(Events.IMAGE_RENDERED, firstCallback) + element.addEventListener(Events.IMAGE_RENDERED, firstCallback); - this.stackToolGroup.addViewport(vp.id, this.renderingEngine.id) + this.stackToolGroup.addViewport(vp.id, this.renderingEngine.id); try { - vp.setStack([imageId1], 0) - this.renderingEngine.render() + vp.setStack([imageId1], 0); + this.renderingEngine.render(); } catch (e) { - done.fail(e) + console.warn('Calibrate failed:', e); + done.fail(e); } - }) - }) - */ + }); + }); }); From a7c3cfa4542a39d66a7318c1ae4c06b6d7c1b876 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 17:51:55 -0400 Subject: [PATCH 19/37] Fix tests --- .../test/stackViewport_gpu_render_test.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/core/test/stackViewport_gpu_render_test.js b/packages/core/test/stackViewport_gpu_render_test.js index 35abfa5c2f..c8bc284ca7 100644 --- a/packages/core/test/stackViewport_gpu_render_test.js +++ b/packages/core/test/stackViewport_gpu_render_test.js @@ -580,8 +580,7 @@ describe('renderingCore -- Stack', () => { const imageRenderedCallback = () => { calibratedPixelSpacingMetadataProvider.add(imageId1, { - rowPixelSpacing: 2, - columnPixelSpacing: 2, + scale: 0.5, }); vp.calibrateSpacing(imageId1); @@ -602,9 +601,8 @@ describe('renderingCore -- Stack', () => { element.addEventListener(Events.IMAGE_RENDERED, imageRenderedCallback); element.addEventListener(Events.IMAGE_SPACING_CALIBRATED, (evt) => { - const { rowScale, columnScale } = evt.detail; - expect(rowScale).toBe(2); - expect(columnScale).toBe(2); + const { scale } = evt.detail; + expect(scale).toBe(0.5); }); try { @@ -751,6 +749,8 @@ describe('renderingCore -- Stack', () => { }); describe('Calibration ', () => { + const scale = 1.5; + beforeEach(function () { cache.purgeCache(); this.DOMElements = []; @@ -778,7 +778,10 @@ describe('renderingCore -- Stack', () => { }); }); - it('Should be able to calibrate an image', function (done) { + const skipIt = () => null; + // TODO - renable this when affine transforms are supported as part of + // the calibration event instead of simple calibration ratios + skipIt('Should be able to calibrate an image', function (done) { const element = createViewport(this.renderingEngine, AXIAL, 256, 256); this.DOMElements.push(element); @@ -793,8 +796,9 @@ describe('renderingCore -- Stack', () => { .getViewport(viewportId) .getCurrentImageId(); - calibrateImageSpacing(imageId, this.renderingEngine, 1, 5); + calibrateImageSpacing(imageId, this.renderingEngine, scale); }; + const secondCallback = () => { const canvas = vp.getCanvas(); const image = canvas.toDataURL('image/png'); @@ -835,7 +839,7 @@ describe('renderingCore -- Stack', () => { .getViewport(viewportId) .getCurrentImageId(); - calibrateImageSpacing(imageId, this.renderingEngine, 1, 5); + calibrateImageSpacing(imageId, this.renderingEngine, scale); element.addEventListener( Events.IMAGE_RENDERED, @@ -851,8 +855,7 @@ describe('renderingCore -- Stack', () => { element.addEventListener(Events.IMAGE_SPACING_CALIBRATED, (evt) => { expect(evt.detail).toBeDefined(); - expect(evt.detail.rowScale).toBe(1); - expect(evt.detail.columnScale).toBe(5); + expect(evt.detail.scale).toBe(scale); expect(evt.detail.viewportId).toBe(viewportId); }); From 227dbaeacfdb3a597ee12690d479f6cf0285724c Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 17:56:59 -0400 Subject: [PATCH 20/37] PR changes --- packages/tools/src/utilities/calibratedUnits.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tools/src/utilities/calibratedUnits.ts b/packages/tools/src/utilities/calibratedUnits.ts index 20be64e2ad..14a10546b1 100644 --- a/packages/tools/src/utilities/calibratedUnits.ts +++ b/packages/tools/src/utilities/calibratedUnits.ts @@ -17,16 +17,18 @@ const calibratedLengthUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; const units = hasPixelSpacing ? 'mm' : 'px'; if (!calibration || !calibration.type) return units; + // TODO - handle US regions properly if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; return `${units} ${calibration.type}`; }; +const SQUARE = '\xb2'; /** * Extracts the area units, including the squared sign plus calibration type. */ const calibratedAreaUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm\xb2' : 'px\xb2'; + const units = (hasPixelSpacing ? 'mm' : 'px') + SQUARE; if (!calibration || !calibration.type) return units; if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; return `${units} ${calibration.type}`; From beecc544d4c9181771e86666ced7be1a50a7e18b Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 28 Jun 2023 18:05:34 -0400 Subject: [PATCH 21/37] Api check --- common/reviews/api/tools.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 94b9b35696..aec10abfa5 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -584,7 +584,7 @@ export class BrushTool extends BaseTool { function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) -function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration | number, columnPixelSpacing?: number): void; +function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration | number): void; // @public type CameraModifiedEvent = CustomEvent_2; From f50cc99146a3c4a7303c6cd822420ac9ca6dc214 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Thu, 29 Jun 2023 15:16:49 -0400 Subject: [PATCH 22/37] PR fixes --- common/reviews/api/core.api.md | 2 -- .../reviews/api/streaming-image-volume-loader.api.md | 5 +---- common/reviews/api/tools.api.md | 2 -- packages/core/src/RenderingEngine/StackViewport.ts | 10 ---------- packages/core/src/types/EventTypes.ts | 2 -- packages/core/src/types/IImageCalibration.ts | 12 +++++++++--- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index bf7f991e1f..61dbb1c08a 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1433,8 +1433,6 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale?: number; - columnScale?: number; scale?: number; imageData: vtkImageData; worldToIndex: mat4; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 50a6e6d075..9d121be300 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,7 +4,6 @@ ```ts -import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { mat4 } from 'gl-matrix'; import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData'; @@ -1024,8 +1023,6 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale?: number; - columnScale?: number; scale?: number; imageData: vtkImageData; worldToIndex: mat4; @@ -1544,7 +1541,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: default_2; + requestType: RequestType_2; additionalDetails: { volumeId: string; }; diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index aec10abfa5..2564fea1bb 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -2724,8 +2724,6 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale?: number; - columnScale?: number; scale?: number; imageData: vtkImageData; worldToIndex: mat4; diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 4ebe3dd09c..630aa2c7e4 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -2654,16 +2654,6 @@ class StackViewport extends Viewport implements IStackViewport { ...imagePlaneModule, }; - if (calibratedPixelSpacing?.appliedSpacing) { - // Over-ride the image plane module spacing, as the measurement data - // has already been created with the calibrated spacing provided from - // down below inside calibrateIfNecessary - const { rowPixelSpacing, columnPixelSpacing } = - calibratedPixelSpacing.appliedSpacing; - newImagePlaneModule.rowPixelSpacing = rowPixelSpacing; - newImagePlaneModule.columnPixelSpacing = columnPixelSpacing; - } - if (!newImagePlaneModule.columnPixelSpacing) { newImagePlaneModule.columnPixelSpacing = 1; this.hasPixelSpacing = this.calibration?.scale > 0; diff --git a/packages/core/src/types/EventTypes.ts b/packages/core/src/types/EventTypes.ts index cabcba9058..df2441ab6e 100644 --- a/packages/core/src/types/EventTypes.ts +++ b/packages/core/src/types/EventTypes.ts @@ -223,8 +223,6 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - rowScale?: number; - columnScale?: number; scale?: number; imageData: vtkImageData; worldToIndex: mat4; diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 6170ec3490..23e48f9dd6 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -13,14 +13,20 @@ export interface IImageCalibration { scale?: number; /** The type of the pixel spacing, distinguishing between various - * types projection (CR/DX/MG) spacing and volumetric spacing ('') + * types projection (CR/DX/MG) spacing and volumetric spacing (the type is + * an empty string as it doesn't get a suffix, but this distinguishes it + * from other types) */ type: CalibrationTypes; // A tooltip which can be used to explain the calibration information tooltip?: string; - /** Indication that the image has some spacing information (the pixelSpacing - * when hasPixelSpacing is null can just be 1,1) */ + /** + * Boolean to indicate that the image has spacing information (the pixelSpacing + * can just be 1,1 to use image pixels, but this + * isn't distinguishable from a real spacing of 1mm square pixels so a real + * flag here is needed.) + */ hasPixelSpacing?: boolean; /** Indication of projection (eg X-Ray type) spacing and volumetric type */ isProjection?: boolean; From 1f421b4aa614f01bdb532c230a1ae97beb2feed3 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Thu, 29 Jun 2023 15:33:02 -0400 Subject: [PATCH 23/37] Reverting unneeded change --- packages/core/src/utilities/imageToWorldCoords.ts | 9 +-------- packages/core/src/utilities/worldToImageCoords.ts | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/core/src/utilities/imageToWorldCoords.ts b/packages/core/src/utilities/imageToWorldCoords.ts index 5c2921c03e..29dd61dc9e 100644 --- a/packages/core/src/utilities/imageToWorldCoords.ts +++ b/packages/core/src/utilities/imageToWorldCoords.ts @@ -16,10 +16,6 @@ export default function imageToWorldCoords( imageCoords: Point2 ): Point3 | undefined { const imagePlaneModule = metaData.get('imagePlaneModule', imageId); - const calibratedPixelSpacing = metaData.get( - 'calibratedPixelSpacing', - imageId - ); if (!imagePlaneModule) { throw new Error(`No imagePlaneModule found for imageId: ${imageId}`); @@ -31,10 +27,7 @@ export default function imageToWorldCoords( imagePositionPatient: origin, } = imagePlaneModule; - let { columnPixelSpacing, rowPixelSpacing } = - calibratedPixelSpacing || imagePlaneModule; - columnPixelSpacing ||= 1; - rowPixelSpacing ||= 1; + const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = imagePlaneModule; // calculate the image coordinates in the world space const imageCoordsInWorld = vec3.create(); diff --git a/packages/core/src/utilities/worldToImageCoords.ts b/packages/core/src/utilities/worldToImageCoords.ts index 65dff115d3..8d373ea88c 100644 --- a/packages/core/src/utilities/worldToImageCoords.ts +++ b/packages/core/src/utilities/worldToImageCoords.ts @@ -17,10 +17,6 @@ function worldToImageCoords( worldCoords: Point3 ): Point2 | undefined { const imagePlaneModule = metaData.get('imagePlaneModule', imageId); - const calibratedPixelSpacing = metaData.get( - 'calibratedPixelSpacing', - imageId - ); if (!imagePlaneModule) { throw new Error(`No imagePlaneModule found for imageId: ${imageId}`); @@ -35,10 +31,7 @@ function worldToImageCoords( imagePositionPatient: origin, } = imagePlaneModule; - let { columnPixelSpacing, rowPixelSpacing } = - calibratedPixelSpacing || imagePlaneModule; - columnPixelSpacing ||= 1; - rowPixelSpacing ||= 1; + const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = imagePlaneModule; // The origin is the image position patient, but since image coordinates start // from [0,0] for the top left hand of the first pixel, and the origin is at the From 9c698a52b44ce51c00902ca619aa87ce70aff8e8 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Thu, 29 Jun 2023 17:11:18 -0400 Subject: [PATCH 24/37] Build fixes --- common/reviews/api/streaming-image-volume-loader.api.md | 3 ++- .../adapters/src/adapters/Cornerstone/{index.js => index.ts} | 0 packages/adapters/src/adapters/VTKjs/{index.js => index.ts} | 2 +- packages/adapters/src/adapters/{index.js => index.ts} | 2 +- tsconfig.base.json | 1 - 5 files changed, 4 insertions(+), 4 deletions(-) rename packages/adapters/src/adapters/Cornerstone/{index.js => index.ts} (100%) rename packages/adapters/src/adapters/VTKjs/{index.js => index.ts} (56%) rename packages/adapters/src/adapters/{index.js => index.ts} (94%) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 9d121be300..96a0c69488 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,6 +4,7 @@ ```ts +import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { mat4 } from 'gl-matrix'; import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData'; @@ -1541,7 +1542,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: RequestType_2; + requestType: default_2; additionalDetails: { volumeId: string; }; diff --git a/packages/adapters/src/adapters/Cornerstone/index.js b/packages/adapters/src/adapters/Cornerstone/index.ts similarity index 100% rename from packages/adapters/src/adapters/Cornerstone/index.js rename to packages/adapters/src/adapters/Cornerstone/index.ts diff --git a/packages/adapters/src/adapters/VTKjs/index.js b/packages/adapters/src/adapters/VTKjs/index.ts similarity index 56% rename from packages/adapters/src/adapters/VTKjs/index.js rename to packages/adapters/src/adapters/VTKjs/index.ts index c90f6463b3..15edce964a 100644 --- a/packages/adapters/src/adapters/VTKjs/index.js +++ b/packages/adapters/src/adapters/VTKjs/index.ts @@ -1,4 +1,4 @@ -import Segmentation from "./Segmentation.js"; +import Segmentation from "./Segmentation"; const VTKjs = { Segmentation diff --git a/packages/adapters/src/adapters/index.js b/packages/adapters/src/adapters/index.ts similarity index 94% rename from packages/adapters/src/adapters/index.js rename to packages/adapters/src/adapters/index.ts index 430510cebe..87f3c21877 100644 --- a/packages/adapters/src/adapters/index.js +++ b/packages/adapters/src/adapters/index.ts @@ -5,7 +5,7 @@ import VTKjs from "./VTKjs"; const adapters = { Cornerstone, Cornerstone3D, - VTKjs, + VTKjs }; export default adapters; diff --git a/tsconfig.base.json b/tsconfig.base.json index 6643ed3d14..5562fa7fa5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -37,7 +37,6 @@ "packages/**/dist", "packages/**/lib", "packages/**/lib-esm", - "packages/adapters", "packages/docs", "snippets", "examples" From 5228e4cb36391aaed328f5674f861f3d2163cdcd Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Thu, 29 Jun 2023 17:42:27 -0400 Subject: [PATCH 25/37] Convert null to 1 spacing to prevent deleting data --- packages/core/src/utilities/imageToWorldCoords.ts | 4 +++- packages/core/src/utilities/worldToImageCoords.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/utilities/imageToWorldCoords.ts b/packages/core/src/utilities/imageToWorldCoords.ts index 29dd61dc9e..bd2282b83e 100644 --- a/packages/core/src/utilities/imageToWorldCoords.ts +++ b/packages/core/src/utilities/imageToWorldCoords.ts @@ -27,7 +27,9 @@ export default function imageToWorldCoords( imagePositionPatient: origin, } = imagePlaneModule; - const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = imagePlaneModule; + let { columnPixelSpacing, rowPixelSpacing } = imagePlaneModule; + columnPixelSpacing ||= 1; + rowPixelSpacing ||= 1; // calculate the image coordinates in the world space const imageCoordsInWorld = vec3.create(); diff --git a/packages/core/src/utilities/worldToImageCoords.ts b/packages/core/src/utilities/worldToImageCoords.ts index 8d373ea88c..3a66fabd55 100644 --- a/packages/core/src/utilities/worldToImageCoords.ts +++ b/packages/core/src/utilities/worldToImageCoords.ts @@ -31,7 +31,9 @@ function worldToImageCoords( imagePositionPatient: origin, } = imagePlaneModule; - const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = imagePlaneModule; + let { columnPixelSpacing, rowPixelSpacing } = imagePlaneModule; + columnPixelSpacing ||= 1; + rowPixelSpacing ||= 1; // The origin is the image position patient, but since image coordinates start // from [0,0] for the top left hand of the first pixel, and the origin is at the From ff8e77adaf0b5211f9d065086822fe9385de8153 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 30 Jun 2023 10:42:14 -0400 Subject: [PATCH 26/37] Comments --- packages/core/src/utilities/imageToWorldCoords.ts | 1 + packages/core/src/utilities/worldToImageCoords.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/core/src/utilities/imageToWorldCoords.ts b/packages/core/src/utilities/imageToWorldCoords.ts index bd2282b83e..a686cc2bea 100644 --- a/packages/core/src/utilities/imageToWorldCoords.ts +++ b/packages/core/src/utilities/imageToWorldCoords.ts @@ -28,6 +28,7 @@ export default function imageToWorldCoords( } = imagePlaneModule; let { columnPixelSpacing, rowPixelSpacing } = imagePlaneModule; + // Use ||= to convert null and 0 as well as undefined to 1 columnPixelSpacing ||= 1; rowPixelSpacing ||= 1; diff --git a/packages/core/src/utilities/worldToImageCoords.ts b/packages/core/src/utilities/worldToImageCoords.ts index 3a66fabd55..6ed3f0b503 100644 --- a/packages/core/src/utilities/worldToImageCoords.ts +++ b/packages/core/src/utilities/worldToImageCoords.ts @@ -32,6 +32,7 @@ function worldToImageCoords( } = imagePlaneModule; let { columnPixelSpacing, rowPixelSpacing } = imagePlaneModule; + // Use ||= to convert null and 0 as well as undefined to 1 columnPixelSpacing ||= 1; rowPixelSpacing ||= 1; From 7887eb76c0b9851ddb557ca6b20bece21bfc4d5d Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 09:04:20 -0400 Subject: [PATCH 27/37] PR requested changes --- common/reviews/api/core.api.md | 6 +- .../api/streaming-image-volume-loader.api.md | 5 +- common/reviews/api/tools.api.md | 6 +- packages/core/src/enums/CalibrationTypes.ts | 2 + packages/core/src/types/IImageCalibration.ts | 17 +- .../tools/examples/calibrationTools/index.ts | 326 ++++++++++++++++++ .../examples/stackAnnotationTools/index.ts | 98 ------ .../src/tools/annotation/BidirectionalTool.ts | 10 +- .../src/tools/annotation/CircleROITool.ts | 21 +- .../src/tools/annotation/EllipticalROITool.ts | 9 +- .../tools/src/tools/annotation/LengthTool.ts | 10 +- .../tools/annotation/PlanarFreehandROITool.ts | 9 +- .../src/tools/annotation/RectangleROITool.ts | 9 +- .../src/utilities/calibrateImageSpacing.ts | 9 +- ...libratedUnits.ts => getCalibratedUnits.ts} | 30 +- utils/ExampleRunner/example-info.json | 4 + 16 files changed, 398 insertions(+), 173 deletions(-) create mode 100644 packages/tools/examples/calibrationTools/index.ts rename packages/tools/src/utilities/{calibratedUnits.ts => getCalibratedUnits.ts} (53%) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index d4e94d7856..1db381a602 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -140,6 +140,8 @@ enum CalibrationTypes { // (undocumented) REGION = "Region", // (undocumented) + UNCALIBRATED = "Uncalibrated", + // (undocumented) USER = "User" } @@ -1151,10 +1153,6 @@ interface IImageCalibration { // (undocumented) columnPixelSpacing?: number; // (undocumented) - hasPixelSpacing?: boolean; - // (undocumented) - isProjection?: boolean; - // (undocumented) rowPixelSpacing?: number; // (undocumented) scale?: number; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 62769c67c1..3578e642cc 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -49,6 +49,7 @@ enum CalibrationTypes { NOT_APPLICABLE = '', PROJECTION = 'Proj', REGION = 'Region', + UNCALIBRATED = 'Uncalibrated', USER = 'User', } @@ -811,13 +812,9 @@ interface IImage { interface IImageCalibration { // (undocumented) columnPixelSpacing?: number; - hasPixelSpacing?: boolean; - isProjection?: boolean; rowPixelSpacing?: number; scale?: number; - // (undocumented) sequenceOfUltrasoundRegions?: Record[]; - // (undocumented) tooltip?: string; type: CalibrationTypes; } diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 4bcfc01eae..88ba776e79 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -586,7 +586,7 @@ export class BrushTool extends BaseTool { function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) -function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration | number): void; +function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration): void; // @public type CameraModifiedEvent = CustomEvent_2; @@ -2502,13 +2502,9 @@ interface IImage { interface IImageCalibration { // (undocumented) columnPixelSpacing?: number; - hasPixelSpacing?: boolean; - isProjection?: boolean; rowPixelSpacing?: number; scale?: number; - // (undocumented) sequenceOfUltrasoundRegions?: Record[]; - // (undocumented) tooltip?: string; type: CalibrationTypes; } diff --git a/packages/core/src/enums/CalibrationTypes.ts b/packages/core/src/enums/CalibrationTypes.ts index c4dc942142..c1ce49fa50 100644 --- a/packages/core/src/enums/CalibrationTypes.ts +++ b/packages/core/src/enums/CalibrationTypes.ts @@ -48,6 +48,8 @@ export enum CalibrationTypes { * NOT trust the measurement values but to manually calibrate. */ ERROR = 'Error', + /** Uncalibrated image */ + UNCALIBRATED = 'Uncalibrated', } export default CalibrationTypes; diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 23e48f9dd6..5af9e7b0af 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -8,30 +8,17 @@ export interface IImageCalibration { /** The pixel spacing for the image, in mm between pixel centers */ rowPixelSpacing?: number; columnPixelSpacing?: number; - /** The scaling of this image - new spacing = original pixelSpacing/scale */ scale?: number; - /** The type of the pixel spacing, distinguishing between various * types projection (CR/DX/MG) spacing and volumetric spacing (the type is * an empty string as it doesn't get a suffix, but this distinguishes it * from other types) */ type: CalibrationTypes; - - // A tooltip which can be used to explain the calibration information + /** A tooltip which can be used to explain the calibration information */ tooltip?: string; - /** - * Boolean to indicate that the image has spacing information (the pixelSpacing - * can just be 1,1 to use image pixels, but this - * isn't distinguishable from a real spacing of 1mm square pixels so a real - * flag here is needed.) - */ - hasPixelSpacing?: boolean; - /** Indication of projection (eg X-Ray type) spacing and volumetric type */ - isProjection?: boolean; - // The DICOM defined ultrasound regions. Used for non-distance spacing - // units. + /** The DICOM defined ultrasound regions. Used for non-distance spacing units. */ sequenceOfUltrasoundRegions?: Record[]; } diff --git a/packages/tools/examples/calibrationTools/index.ts b/packages/tools/examples/calibrationTools/index.ts new file mode 100644 index 0000000000..30f88081e7 --- /dev/null +++ b/packages/tools/examples/calibrationTools/index.ts @@ -0,0 +1,326 @@ +import { + RenderingEngine, + Types, + Enums, + getRenderingEngine, +} from '@cornerstonejs/core'; +import { + initDemo, + createImageIdsAndCacheMetaData, + setTitleAndDescription, + addDropdownToToolbar, + addButtonToToolbar, +} from '../../../../utils/demo/helpers'; +import * as cornerstoneTools from '@cornerstonejs/tools'; +import dicomImageLoader from '@cornerstonejs/dicom-image-loader'; + +// This is for debugging purposes +console.warn( + 'Click on index.ts to open source code for this example --------->' +); + +const { wadors } = dicomImageLoader; + +const { + LengthTool, + ProbeTool, + RectangleROITool, + EllipticalROITool, + CircleROITool, + BidirectionalTool, + AngleTool, + CobbAngleTool, + ToolGroupManager, + ArrowAnnotateTool, + PlanarFreehandROITool, + Enums: csToolsEnums, + utilities, +} = cornerstoneTools; + +const { ViewportType, Events } = Enums; +const { MouseBindings } = csToolsEnums; +const renderingEngineId = 'myRenderingEngine'; +const viewportId = 'CT_STACK'; + +// ======== Set up page ======== // +setTitleAndDescription( + 'Calibration Tools Stack', + 'Calibration tools for a stack viewport (aspect ratio changes only supported initially)' +); + +const content = document.getElementById('content'); +const element = document.createElement('div'); + +// Disable right click context menu so we can have right click tools +element.oncontextmenu = (e) => e.preventDefault(); + +element.id = 'cornerstone-element'; +element.style.width = '500px'; +element.style.height = '500px'; + +content.appendChild(element); + +const info = document.createElement('div'); +content.appendChild(info); + +const instructions = document.createElement('p'); +instructions.innerText = 'Left Click to use selected tool'; +info.appendChild(instructions); + +const rotationInfo = document.createElement('div'); +info.appendChild(rotationInfo); + +const flipHorizontalInfo = document.createElement('div'); +info.appendChild(flipHorizontalInfo); + +const flipVerticalInfo = document.createElement('div'); +info.appendChild(flipVerticalInfo); + +element.addEventListener(Events.CAMERA_MODIFIED, (_) => { + // Get the rendering engine + const renderingEngine = getRenderingEngine(renderingEngineId); + + // Get the stack viewport + const viewport = ( + renderingEngine.getViewport(viewportId) + ); + + if (!viewport) { + return; + } + + const { flipHorizontal, flipVertical } = viewport.getCamera(); + const { rotation } = viewport.getProperties(); + + rotationInfo.innerText = `Rotation: ${Math.round(rotation)}`; + flipHorizontalInfo.innerText = `Flip horizontal: ${flipHorizontal}`; + flipVerticalInfo.innerText = `Flip vertical: ${flipVertical}`; +}); +// ============================= // + +const toolGroupId = 'STACK_TOOL_GROUP_ID'; + +const toolsNames = [ + LengthTool.toolName, + ProbeTool.toolName, + RectangleROITool.toolName, + EllipticalROITool.toolName, + CircleROITool.toolName, + BidirectionalTool.toolName, + AngleTool.toolName, + CobbAngleTool.toolName, + ArrowAnnotateTool.toolName, + PlanarFreehandROITool.toolName, +]; +let selectedToolName = toolsNames[0]; + +addDropdownToToolbar({ + options: { values: toolsNames, defaultValue: selectedToolName }, + onSelectedValueChange: (newSelectedToolNameAsStringOrNumber) => { + const newSelectedToolName = String(newSelectedToolNameAsStringOrNumber); + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + + // Set the new tool active + toolGroup.setToolActive(newSelectedToolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + ], + }); + + // Set the old tool passive + toolGroup.setToolPassive(selectedToolName); + + selectedToolName = newSelectedToolName; + }, +}); + +const calibrationFunctions: Record = {}; + +const calibrations = [ + { + value: 'Default', + selected: 'userCalibration', + }, + { + value: 'User Calibration 0.5', + selected: 'userCalibration', + calibration: { + scale: 0.5, + type: Enums.CalibrationTypes.USER, + }, + }, + { + value: 'ERMF 2', + selected: 'userCalibration', + calibration: { + scale: 2, + type: Enums.CalibrationTypes.ERMF, + }, + }, + { + value: 'Projected 1', + selected: 'userCalibration', + calibration: { + // Bug right now in StackViewport that fails to reset + scale: 1, + type: Enums.CalibrationTypes.PROJECTION, + }, + }, + { + value: 'Error 1', + selected: 'userCalibration', + calibration: { + scale: 1, + type: Enums.CalibrationTypes.ERROR, + }, + }, + { + value: 'px units', + selected: 'applyMetadata', + metadata: { + '00280030': null, + }, + }, + { + value: 'Aspect 1:2 (breaks existing annotations)', + selected: 'applyMetadata', + metadata: { + '00280030': { Value: [0.5, 1] }, + }, + }, + { + value: 'Aspect 1:1 (breaks existing annotations)', + selected: 'applyMetadata', + metadata: { + '00280030': { Value: [0.5, 0.5] }, + }, + }, +]; +const calibrationNames = calibrations.map((it) => it.value); + +addDropdownToToolbar({ + options: { values: calibrationNames }, + onSelectedValueChange: (newCalibrationValue) => { + const calibration = calibrations.find( + (it) => it.value === newCalibrationValue + ); + if (!calibration) return; + const f = calibrationFunctions[calibration.selected]; + if (!f) return; + f.apply(calibration); + }, +}); + +/** + * Runs the demo + */ +async function run() { + // Init Cornerstone and related libraries + await initDemo(); + + // Add tools to Cornerstone3D + cornerstoneTools.addTool(LengthTool); + cornerstoneTools.addTool(ProbeTool); + cornerstoneTools.addTool(RectangleROITool); + cornerstoneTools.addTool(EllipticalROITool); + cornerstoneTools.addTool(CircleROITool); + cornerstoneTools.addTool(BidirectionalTool); + cornerstoneTools.addTool(AngleTool); + cornerstoneTools.addTool(CobbAngleTool); + cornerstoneTools.addTool(ArrowAnnotateTool); + cornerstoneTools.addTool(PlanarFreehandROITool); + + // Define a tool group, which defines how mouse events map to tool commands for + // Any viewport using the group + const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); + + // Add the tools to the tool group + toolGroup.addTool(LengthTool.toolName); + toolGroup.addTool(ProbeTool.toolName); + toolGroup.addTool(RectangleROITool.toolName); + toolGroup.addTool(EllipticalROITool.toolName); + toolGroup.addTool(CircleROITool.toolName); + toolGroup.addTool(BidirectionalTool.toolName); + toolGroup.addTool(AngleTool.toolName); + toolGroup.addTool(CobbAngleTool.toolName); + toolGroup.addTool(ArrowAnnotateTool.toolName); + toolGroup.addTool(PlanarFreehandROITool.toolName); + + // Set the initial state of the tools, here we set one tool active on left click. + // This means left click will draw that tool. + toolGroup.setToolActive(LengthTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + ], + }); + // We set all the other tools passive here, this means that any state is rendered, and editable + // But aren't actively being drawn (see the toolModes example for information) + toolGroup.setToolPassive(ProbeTool.toolName); + toolGroup.setToolPassive(RectangleROITool.toolName); + toolGroup.setToolPassive(EllipticalROITool.toolName); + toolGroup.setToolPassive(CircleROITool.toolName); + toolGroup.setToolPassive(BidirectionalTool.toolName); + toolGroup.setToolPassive(AngleTool.toolName); + toolGroup.setToolPassive(CobbAngleTool.toolName); + toolGroup.setToolPassive(ArrowAnnotateTool.toolName); + + // Get Cornerstone imageIds and fetch metadata into RAM + const imageIds = await createImageIdsAndCacheMetaData({ + StudyInstanceUID: + '1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463', + SeriesInstanceUID: + '1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561', + wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb', + }); + + // Instantiate a rendering engine + const renderingEngine = new RenderingEngine(renderingEngineId); + + calibrationFunctions.userCalibration = function calibrationSelected() { + utilities.calibrateImageSpacing( + imageIds[0], + renderingEngine, + this.calibration + ); + }; + calibrationFunctions.applyMetadata = function applyMetadata() { + const instance = wadors.metaDataManager.get(imageIds[0]); + Object.assign(instance, this.metadata); + utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null); + }; + + // Create a stack viewport + const viewportInput = { + viewportId, + type: ViewportType.STACK, + element, + defaultOptions: { + background: [0.2, 0, 0.2], + }, + }; + + renderingEngine.enableElement(viewportInput); + + // Set the tool group on the viewport + toolGroup.addViewport(viewportId, renderingEngineId); + + // Get the stack viewport that was created + const viewport = ( + renderingEngine.getViewport(viewportId) + ); + + // Define a stack containing a single image + const stack = [imageIds[0]]; + + // Set the stack on the viewport + viewport.setStack(stack); + + // Render the image + viewport.render(); +} + +run(); diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 35737dac72..d667b844e8 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -12,15 +12,12 @@ import { addButtonToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools'; -import dicomImageLoader from '@cornerstonejs/dicom-image-loader'; // This is for debugging purposes console.warn( 'Click on index.ts to open source code for this example --------->' ); -const { wadors } = dicomImageLoader; - const { LengthTool, ProbeTool, @@ -32,9 +29,7 @@ const { CobbAngleTool, ToolGroupManager, ArrowAnnotateTool, - PlanarFreehandROITool, Enums: csToolsEnums, - utilities, } = cornerstoneTools; const { ViewportType, Events } = Enums; @@ -110,7 +105,6 @@ const toolsNames = [ AngleTool.toolName, CobbAngleTool.toolName, ArrowAnnotateTool.toolName, - PlanarFreehandROITool.toolName, ]; let selectedToolName = toolsNames[0]; @@ -191,83 +185,6 @@ addButtonToToolbar({ }, }); -const calibrationFunctions: Record = {}; - -const calibrations = [ - { - value: 'Default', - selected: 'userCalibration', - }, - { - value: 'User Calibration 0.5', - selected: 'userCalibration', - calibration: { - scale: 0.5, - type: Enums.CalibrationTypes.USER, - }, - }, - { - value: 'ERMF 2', - selected: 'userCalibration', - calibration: { - scale: 2, - type: Enums.CalibrationTypes.ERMF, - }, - }, - { - value: 'Projected 1', - selected: 'userCalibration', - calibration: { - // Bug right now in StackViewport that fails to reset - scale: 1, - type: Enums.CalibrationTypes.PROJECTION, - }, - }, - { - value: 'Error 1', - selected: 'userCalibration', - calibration: { - scale: 1, - type: Enums.CalibrationTypes.ERROR, - }, - }, - { - value: 'px units', - selected: 'applyMetadata', - metadata: { - '00280030': null, - }, - }, - { - value: 'Aspect 1:2', - selected: 'applyMetadata', - metadata: { - '00280030': { Value: [0.5, 1] }, - }, - }, - { - value: 'Aspect 1:1', - selected: 'applyMetadata', - metadata: { - '00280030': { Value: [0.5, 0.5] }, - }, - }, -]; -const calibrationNames = calibrations.map((it) => it.value); - -addDropdownToToolbar({ - options: { values: calibrationNames }, - onSelectedValueChange: (newCalibrationValue) => { - const calibration = calibrations.find( - (it) => it.value === newCalibrationValue - ); - if (!calibration) return; - const f = calibrationFunctions[calibration.selected]; - if (!f) return; - f.apply(calibration); - }, -}); - /** * Runs the demo */ @@ -285,7 +202,6 @@ async function run() { cornerstoneTools.addTool(AngleTool); cornerstoneTools.addTool(CobbAngleTool); cornerstoneTools.addTool(ArrowAnnotateTool); - cornerstoneTools.addTool(PlanarFreehandROITool); // Define a tool group, which defines how mouse events map to tool commands for // Any viewport using the group @@ -301,7 +217,6 @@ async function run() { toolGroup.addTool(AngleTool.toolName); toolGroup.addTool(CobbAngleTool.toolName); toolGroup.addTool(ArrowAnnotateTool.toolName); - toolGroup.addTool(PlanarFreehandROITool.toolName); // Set the initial state of the tools, here we set one tool active on left click. // This means left click will draw that tool. @@ -335,19 +250,6 @@ async function run() { // Instantiate a rendering engine const renderingEngine = new RenderingEngine(renderingEngineId); - calibrationFunctions.userCalibration = function calibrationSelected() { - utilities.calibrateImageSpacing( - imageIds[0], - renderingEngine, - this.calibration - ); - }; - calibrationFunctions.applyMetadata = function applyMetadata() { - const instance = wadors.metaDataManager.get(imageIds[0]); - Object.assign(instance, this.metadata); - utilities.calibrateImageSpacing(imageIds[0], renderingEngine, null); - }; - // Create a stack viewport const viewportInput = { viewportId, diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index fa07c976d1..5fce9e5cce 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -8,9 +8,9 @@ import { import type { Types } from '@cornerstonejs/core'; import { - calibratedLengthUnits, - getScale, -} from '../../utilities/calibratedUnits'; + getCalibratedLengthUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; @@ -1297,7 +1297,7 @@ class BidirectionalTool extends AnnotationTool { } const { imageData, dimensions } = image; - const scale = getScale(image); + const scale = getCalibratedScale(image); const dist1 = this._calculateLength(worldPos1, worldPos2) / scale; const dist2 = this._calculateLength(worldPos3, worldPos4) / scale; const length = dist1 > dist2 ? dist1 : dist2; @@ -1315,7 +1315,7 @@ class BidirectionalTool extends AnnotationTool { cachedStats[targetId] = { length, width, - unit: calibratedLengthUnits(null, image), + unit: getCalibratedLengthUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 92ac138960..9522c252ad 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -10,10 +10,10 @@ import { import type { Types } from '@cornerstonejs/core'; import { - calibratedLengthUnits, - calibratedAreaUnits, - getScale, -} from '../../utilities/calibratedUnits'; + getCalibratedLengthUnits, + getCalibratedAreaUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { @@ -1002,11 +1002,10 @@ class CircleROITool extends AnnotationTool { worldPos2 ); const isEmptyArea = worldWidth === 0 && worldHeight === 0; - const scale = getScale(image); - const area = - Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)) / - scale / - scale; + const scale = getCalibratedScale(image); + const area = Math.abs( + Math.PI * (worldWidth / scale / 2) * (worldHeight / scale / 2) + ); let count = 0; let mean = 0; @@ -1054,9 +1053,9 @@ class CircleROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: calibratedAreaUnits(null, image), + areaUnit: getCalibratedAreaUnits(null, image), radius: worldWidth / 2 / scale, - radiusUnit: calibratedLengthUnits(null, image), + radiusUnit: getCalibratedLengthUnits(null, image), perimeter: (2 * Math.PI * (worldWidth / 2)) / scale, }; } else { diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 6ce8f446cb..b59ea486ec 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -9,7 +9,10 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; +import { + getCalibratedAreaUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { @@ -1110,7 +1113,7 @@ class EllipticalROITool extends AnnotationTool { worldPos2 ); const isEmptyArea = worldWidth === 0 && worldHeight === 0; - const scale = getScale(image); + const scale = getCalibratedScale(image); const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)) / scale / @@ -1162,7 +1165,7 @@ class EllipticalROITool extends AnnotationTool { max, stdDev, isEmptyArea, - areaUnit: calibratedAreaUnits(null, image), + areaUnit: getCalibratedAreaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index 0729f8e788..052f7b43d8 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -8,9 +8,9 @@ import { import type { Types } from '@cornerstonejs/core'; import { - calibratedLengthUnits, - getScale, -} from '../../utilities/calibratedUnits'; + getCalibratedLengthUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { AnnotationTool } from '../base'; import throttle from '../../utilities/throttle'; @@ -817,7 +817,7 @@ class LengthTool extends AnnotationTool { } const { imageData, dimensions } = image; - const scale = getScale(image); + const scale = getCalibratedScale(image); const length = this._calculateLength(worldPos1, worldPos2) / scale; @@ -835,7 +835,7 @@ class LengthTool extends AnnotationTool { // todo: add insideVolume calculation, for removing tool if outside cachedStats[targetId] = { length, - unit: calibratedLengthUnits(null, image), + unit: getCalibratedLengthUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index decabb2b9e..f4b72fa838 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -10,7 +10,10 @@ import { import type { Types } from '@cornerstonejs/core'; import { vec3 } from 'gl-matrix'; -import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; +import { + getCalibratedAreaUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import { Events } from '../../enums'; import { AnnotationTool } from '../base'; @@ -727,7 +730,7 @@ class PlanarFreehandROITool extends AnnotationTool { const { imageData, metadata } = image; const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)); - const scale = getScale(image); + const scale = getCalibratedScale(image); const area = polyline.calculateAreaOfPoints(canvasCoordinates) / scale / scale; @@ -852,7 +855,7 @@ class PlanarFreehandROITool extends AnnotationTool { mean, max, stdDev, - areaUnit: calibratedAreaUnits(null, image), + areaUnit: getCalibratedAreaUnits(null, image), }; } diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 924441044a..1dcee2d26d 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -9,7 +9,10 @@ import { } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; -import { calibratedAreaUnits, getScale } from '../../utilities/calibratedUnits'; +import { + getCalibratedAreaUnits, + getCalibratedScale, +} from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; import { @@ -953,7 +956,7 @@ class RectangleROITool extends AnnotationTool { worldPos1, worldPos2 ); - const scale = getScale(image); + const scale = getCalibratedScale(image); const area = Math.abs(worldWidth * worldHeight) / (scale * scale); @@ -1006,7 +1009,7 @@ class RectangleROITool extends AnnotationTool { mean, stdDev, max, - areaUnit: calibratedAreaUnits(null, image), + areaUnit: getCalibratedAreaUnits(null, image), }; } else { this.isHandleOutsideImage = true; diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index 42854e7076..c359d9b8bd 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -15,15 +15,8 @@ const { calibratedPixelSpacingMetadataProvider } = utilities; export default function calibrateImageSpacing( imageId: string, renderingEngine: Types.IRenderingEngine, - spacing: Types.IImageCalibration | number + spacing: Types.IImageCalibration ): void { - // Handle simple parameter version - if (typeof spacing === 'number') { - spacing = { - type: Enums.CalibrationTypes.USER, - scale: spacing, - }; - } // 1. Add the calibratedPixelSpacing metadata to the metadata calibratedPixelSpacingMetadataProvider.add(imageId, spacing); diff --git a/packages/tools/src/utilities/calibratedUnits.ts b/packages/tools/src/utilities/getCalibratedUnits.ts similarity index 53% rename from packages/tools/src/utilities/calibratedUnits.ts rename to packages/tools/src/utilities/getCalibratedUnits.ts index 14a10546b1..c44c696b9b 100644 --- a/packages/tools/src/utilities/calibratedUnits.ts +++ b/packages/tools/src/utilities/getCalibratedUnits.ts @@ -1,3 +1,8 @@ +import { Enums } from '@cornerstonejs/core'; + +const { CalibrationTypes } = Enums; +const PIXEL_UNITS = 'px'; + /** * Extracts the length units and the type of calibration for those units * into the response. The length units will typically be either mm or px @@ -8,15 +13,18 @@ * TODO: Handle region calibration * * @param handles - used to detect if the spacing information is different - * between various points (eg angled ERMF or US Region) + * between various points (eg angled ERMF or US Region). + * Currently unused, but needed for correct US Region handling * @param image - to extract the calibration from - * @param image.calibration - calibration value to extract units form + * image.calibration - calibration value to extract units form * @returns String containing the units and type of calibration */ -const calibratedLengthUnits = (handles, image): string => { +const getCalibratedLengthUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; - const units = hasPixelSpacing ? 'mm' : 'px'; + // Anachronistic - moving to using calibration consistently, but not completed yet + const units = hasPixelSpacing ? 'mm' : PIXEL_UNITS; if (!calibration || !calibration.type) return units; + if (calibration.type === CalibrationTypes.UNCALIBRATED) return PIXEL_UNITS; // TODO - handle US regions properly if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; return `${units} ${calibration.type}`; @@ -26,16 +34,20 @@ const SQUARE = '\xb2'; /** * Extracts the area units, including the squared sign plus calibration type. */ -const calibratedAreaUnits = (handles, image): string => { +const getCalibratedAreaUnits = (handles, image): string => { const { calibration, hasPixelSpacing } = image; - const units = (hasPixelSpacing ? 'mm' : 'px') + SQUARE; + const units = (hasPixelSpacing ? 'mm' : PIXEL_UNITS) + SQUARE; if (!calibration || !calibration.type) return units; if (calibration.SequenceOfUltrasoundRegions) return 'US Region'; return `${units} ${calibration.type}`; }; -const getScale = (image) => image.calibration?.scale || 1; +/** + * Gets the scale divisor for converting from internal spacing to + * image spacing for calibrated images. + */ +const getCalibratedScale = (image) => image.calibration?.scale || 1; -export default calibratedLengthUnits; +export default getCalibratedLengthUnits; -export { calibratedAreaUnits, calibratedLengthUnits, getScale }; +export { getCalibratedAreaUnits, getCalibratedLengthUnits, getCalibratedScale }; diff --git a/utils/ExampleRunner/example-info.json b/utils/ExampleRunner/example-info.json index 3fb032cc48..80c53b896e 100644 --- a/utils/ExampleRunner/example-info.json +++ b/utils/ExampleRunner/example-info.json @@ -137,6 +137,10 @@ "name": "Stack Annotation Tools", "description": "Demonstrates usage of various annotation tools (Probe, Rectangle ROI, Elliptical ROI, Bidirectional measurements) on a Stack Viewport." }, + "calibrationTools": { + "name": "Calibration Tools", + "description": "Demonstrates usage of calibration tools on a Stack Viewport." + }, "volumeAnnotationTools": { "name": "Volume Annotation Tools ", "description": "Demonstrates annotation using the Length tool in a Volume Viewport (on axial, sagittal, and oblique views)" From 946606b09d6bd130a5c70465a5230dec706aa338 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 11:23:30 -0400 Subject: [PATCH 28/37] Fix tests --- packages/core/src/RenderingEngine/StackViewport.ts | 6 ++++++ packages/core/src/types/EventTypes.ts | 2 ++ .../core/test/stackViewport_gpu_render_test.js | 4 ++-- .../tools/src/utilities/calibrateImageSpacing.ts | 14 ++++++++++---- packages/tools/test/LengthTool_test.js | 7 +++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 23895ede39..6bae9ac6ad 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -609,6 +609,7 @@ class StackViewport extends Viewport implements IStackViewport { this.calibration = calibration; this._publishCalibratedEvent = true; + console.log('Calibration event is', scale, calibration); this._calibrationEvent = { scale, calibration, @@ -2336,6 +2337,11 @@ class StackViewport extends Viewport implements IStackViewport { }; if (!this.suppressEvents) { + if (!eventDetail.scale) { + throw new Error( + `Can't send scaling event ${JSON.stringify(this._calibrationEvent)}` + ); + } // Let the tools know the image spacing has been calibrated triggerEvent(this.element, Events.IMAGE_SPACING_CALIBRATED, eventDetail); } diff --git a/packages/core/src/types/EventTypes.ts b/packages/core/src/types/EventTypes.ts index 918144e774..04fe6d8317 100644 --- a/packages/core/src/types/EventTypes.ts +++ b/packages/core/src/types/EventTypes.ts @@ -9,6 +9,7 @@ import type IImageVolume from './IImageVolume'; import type { VOIRange } from './voi'; import type VOILUTFunctionType from '../enums/VOILUTFunctionType'; import type DisplayArea from './displayArea'; +import IImageCalibration from './IImageCalibration'; /** * CAMERA_MODIFIED Event's data @@ -226,6 +227,7 @@ type ImageSpacingCalibratedEventDetail = { renderingEngineId: string; imageId: string; scale?: number; + calibration?: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/packages/core/test/stackViewport_gpu_render_test.js b/packages/core/test/stackViewport_gpu_render_test.js index 6dab9765eb..7347bddc69 100644 --- a/packages/core/test/stackViewport_gpu_render_test.js +++ b/packages/core/test/stackViewport_gpu_render_test.js @@ -601,8 +601,8 @@ describe('renderingCore -- Stack', () => { element.addEventListener(Events.IMAGE_RENDERED, imageRenderedCallback); element.addEventListener(Events.IMAGE_SPACING_CALIBRATED, (evt) => { - const { scale } = evt.detail; - expect(scale).toBe(0.5); + const { calibration } = evt.detail; + expect(calibration?.scale).toBe(0.5); }); try { diff --git a/packages/tools/src/utilities/calibrateImageSpacing.ts b/packages/tools/src/utilities/calibrateImageSpacing.ts index c359d9b8bd..c898095899 100644 --- a/packages/tools/src/utilities/calibrateImageSpacing.ts +++ b/packages/tools/src/utilities/calibrateImageSpacing.ts @@ -9,16 +9,22 @@ const { calibratedPixelSpacingMetadataProvider } = utilities; * their reference imageIds. Finally, it triggers a re-render for invalidated annotations. * @param imageId - ImageId for the calibrated image * @param rowPixelSpacing - Spacing in row direction - * @param columnPixelSpacing - Spacing in column direction - * @param renderingEngine - Cornerstone RenderingEngine instance + * @param calibrationOrScale - either the calibration object or a scale value */ export default function calibrateImageSpacing( imageId: string, renderingEngine: Types.IRenderingEngine, - spacing: Types.IImageCalibration + calibrationOrScale: Types.IImageCalibration | number ): void { + // Handle simple parameter version + if (typeof calibrationOrScale === 'number') { + calibrationOrScale = { + type: Enums.CalibrationTypes.USER, + scale: calibrationOrScale, + }; + } // 1. Add the calibratedPixelSpacing metadata to the metadata - calibratedPixelSpacingMetadataProvider.add(imageId, spacing); + calibratedPixelSpacingMetadataProvider.add(imageId, calibrationOrScale); // 2. Update the actor for stackViewports const viewports = renderingEngine.getStackViewports(); diff --git a/packages/tools/test/LengthTool_test.js b/packages/tools/test/LengthTool_test.js index a996f0a656..95e7d531bb 100644 --- a/packages/tools/test/LengthTool_test.js +++ b/packages/tools/test/LengthTool_test.js @@ -16,7 +16,7 @@ const { getEnabledElement, } = cornerstone3D; -const { Events, ViewportType } = Enums; +const { Events, ViewportType, CalibrationTypes } = Enums; const { LengthTool, @@ -1211,7 +1211,10 @@ describe('LengthTool:', () => { .getCurrentImageId(); console.log('Starting image calibration'); - calibrateImageSpacing(imageId, this.renderingEngine, scale); + calibrateImageSpacing(imageId, this.renderingEngine, { + type: CalibrationTypes.USER, + scale, + }); console.log('Done image calibration'); }; From ab7e5162c3e0f7f86d76ea8a114fe38554c7dd7f Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 11:37:02 -0400 Subject: [PATCH 29/37] api-check --- common/reviews/api/core.api.md | 1 + common/reviews/api/streaming-image-volume-loader.api.md | 1 + common/reviews/api/tools.api.md | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 1db381a602..5d5a213d3e 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1435,6 +1435,7 @@ type ImageSpacingCalibratedEventDetail = { renderingEngineId: string; imageId: string; scale?: number; + calibration?: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 3578e642cc..eed95efea0 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -1025,6 +1025,7 @@ type ImageSpacingCalibratedEventDetail = { renderingEngineId: string; imageId: string; scale?: number; + calibration?: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 88ba776e79..d3134069ac 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -586,7 +586,7 @@ export class BrushTool extends BaseTool { function calculateAreaOfPoints(points: Types_2.Point2[]): number; // @public (undocumented) -function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, spacing: Types_2.IImageCalibration): void; +function calibrateImageSpacing(imageId: string, renderingEngine: Types_2.IRenderingEngine, calibrationOrScale: Types_2.IImageCalibration | number): void; // @public type CameraModifiedEvent = CustomEvent_2; @@ -2724,6 +2724,7 @@ type ImageSpacingCalibratedEventDetail = { renderingEngineId: string; imageId: string; scale?: number; + calibration?: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; From ba0962c84bb15732fd5bb78aeeef91d38e627c51 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 15:13:23 -0400 Subject: [PATCH 30/37] Remove unneeded calibration event --- packages/core/src/RenderingEngine/StackViewport.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 6bae9ac6ad..0251691d94 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -2337,11 +2337,6 @@ class StackViewport extends Viewport implements IStackViewport { }; if (!this.suppressEvents) { - if (!eventDetail.scale) { - throw new Error( - `Can't send scaling event ${JSON.stringify(this._calibrationEvent)}` - ); - } // Let the tools know the image spacing has been calibrated triggerEvent(this.element, Events.IMAGE_SPACING_CALIBRATED, eventDetail); } From 3044ca3679b5d3645e72d998581258e0f435a63e Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 15:41:42 -0400 Subject: [PATCH 31/37] Remove resolutions in favour of updating resemblejs --- package.json | 3 +-- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f6b7fc79cd..50c68e06cf 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "postcss": "^8.4.23", "prettier": "^2.8.8", "puppeteer": "^13.5.0", - "resemblejs": "^4.1.0", + "resemblejs": "^5.0.0", "rollup": "^3.21.3", "shader-loader": "^1.3.1", "shelljs": "^0.8.5", @@ -161,6 +161,5 @@ ], "dependencies": {}, "resolutions": { - "canvas": "2.11.2" } } diff --git a/yarn.lock b/yarn.lock index f1b88f418d..439a153b13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6696,7 +6696,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz#f58a717afe92f9e69d0e35ff64df596bfad93912" integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== -canvas@2.11.2, canvas@2.9.0: +canvas@2.11.2: version "2.11.2" resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== @@ -18686,12 +18686,12 @@ requizzle@^0.2.3: dependencies: lodash "^4.17.21" -resemblejs@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/resemblejs/-/resemblejs-4.1.0.tgz#66c29028febdb31997e9a164e5b2320c26816621" - integrity sha512-s9/+nQ7bnT+C7XBdnMCcC/QppvJcTmJ7fXZMtuTZMFJycN2kj/tacleyx9O1mURPDYNZsgKMfcamImM9+X+keQ== +resemblejs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resemblejs/-/resemblejs-5.0.0.tgz#f5a0c6aaa59dcfb9f5192e7ab8740616cbbbf220" + integrity sha512-+B0eP9k9VDP/YhBbH+ZdYmHiotdtuc6blVI+h8wwkY2cOow+uiIpSmgkBBBtrEAL0D31/gR/AJPwDeX5TcwmIA== optionalDependencies: - canvas "2.9.0" + canvas "2.11.2" resize-observer-polyfill@^1.5.1: version "1.5.1" From 5dcd6ba3d82a0b154079e0c0e1627bdba08491d2 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 10 Jul 2023 20:25:13 -0400 Subject: [PATCH 32/37] Fix the 1:1 and 0.5:1 aspect ratios --- packages/core/src/RenderingEngine/StackViewport.ts | 1 - packages/tools/examples/calibrationTools/index.ts | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 0251691d94..23895ede39 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -609,7 +609,6 @@ class StackViewport extends Viewport implements IStackViewport { this.calibration = calibration; this._publishCalibratedEvent = true; - console.log('Calibration event is', scale, calibration); this._calibrationEvent = { scale, calibration, diff --git a/packages/tools/examples/calibrationTools/index.ts b/packages/tools/examples/calibrationTools/index.ts index 30f88081e7..cf219350cd 100644 --- a/packages/tools/examples/calibrationTools/index.ts +++ b/packages/tools/examples/calibrationTools/index.ts @@ -137,11 +137,16 @@ addDropdownToToolbar({ }); const calibrationFunctions: Record = {}; +const originalSpacing = 0.976562; const calibrations = [ { value: 'Default', selected: 'userCalibration', + calibration: { + scale: 1, + type: Enums.CalibrationTypes.NOT_APPLICABLE, + }, }, { value: 'User Calibration 0.5', @@ -187,14 +192,14 @@ const calibrations = [ value: 'Aspect 1:2 (breaks existing annotations)', selected: 'applyMetadata', metadata: { - '00280030': { Value: [0.5, 1] }, + '00280030': { Value: [0.5 * originalSpacing, originalSpacing] }, }, }, { value: 'Aspect 1:1 (breaks existing annotations)', selected: 'applyMetadata', metadata: { - '00280030': { Value: [0.5, 0.5] }, + '00280030': { Value: [originalSpacing, originalSpacing] }, }, }, ]; From fd0c805523647bf697f0699762c98231acd7160f Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 11 Jul 2023 14:54:32 -0400 Subject: [PATCH 33/37] Fix exception being thrown on point near tool --- .../tools/src/tools/annotation/AngleTool.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/tools/src/tools/annotation/AngleTool.ts b/packages/tools/src/tools/annotation/AngleTool.ts index 07538028e3..e7723a6d99 100644 --- a/packages/tools/src/tools/annotation/AngleTool.ts +++ b/packages/tools/src/tools/annotation/AngleTool.ts @@ -16,6 +16,7 @@ import { import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking'; import * as lineSegment from '../../utilities/math/line'; import angleBetweenLines from '../../utilities/math/angle/angleBetweenLines'; +import roundNumber from '../../utilities/roundNumber'; import { drawHandles as drawHandlesSvg, @@ -200,7 +201,6 @@ class AngleTool extends AnnotationTool { const [point1, point2, point3] = data.handles.points; const canvasPoint1 = viewport.worldToCanvas(point1); const canvasPoint2 = viewport.worldToCanvas(point2); - const canvasPoint3 = viewport.worldToCanvas(point3); const line1 = { start: { @@ -213,6 +213,17 @@ class AngleTool extends AnnotationTool { }, }; + const distanceToPoint = lineSegment.distanceToPoint( + [line1.start.x, line1.start.y], + [line1.end.x, line1.end.y], + [canvasCoords[0], canvasCoords[1]] + ); + + if (distanceToPoint <= proximity) return true; + if (!point3) return false; + + const canvasPoint3 = viewport.worldToCanvas(point3); + const line2 = { start: { x: canvasPoint2[0], @@ -224,19 +235,13 @@ class AngleTool extends AnnotationTool { }, }; - const distanceToPoint = lineSegment.distanceToPoint( - [line1.start.x, line1.start.y], - [line1.end.x, line1.end.y], - [canvasCoords[0], canvasCoords[1]] - ); - const distanceToPoint2 = lineSegment.distanceToPoint( [line2.start.x, line2.start.y], [line2.end.x, line2.end.y], [canvasCoords[0], canvasCoords[1]] ); - if (distanceToPoint <= proximity || distanceToPoint2 <= proximity) { + if (distanceToPoint2 <= proximity) { return true; } @@ -783,7 +788,7 @@ class AngleTool extends AnnotationTool { return; } - const textLines = [`${angle.toFixed(2)} ${String.fromCharCode(176)}`]; + const textLines = [`${roundNumber(angle)} ${String.fromCharCode(176)}`]; return textLines; } From 6f94ab58ce7e8e6a01046f12b164fb3fe4296eca Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Sun, 16 Jul 2023 16:31:27 -0400 Subject: [PATCH 34/37] PR requested changes --- common/reviews/api/core.api.md | 5 +++-- .../api/streaming-image-volume-loader.api.md | 4 ++-- common/reviews/api/tools.api.md | 4 ++-- packages/core/src/types/EventTypes.ts | 4 ++-- packages/core/src/types/IImageCalibration.ts | 14 +++++++++++++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 4ed65ddcb3..fd0814c089 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1148,6 +1148,8 @@ interface IImage { // @public (undocumented) interface IImageCalibration { + // (undocumented) + aspect?: number; // (undocumented) columnPixelSpacing?: number; // (undocumented) @@ -1432,8 +1434,7 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - scale?: number; - calibration?: IImageCalibration; + calibration: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index eed95efea0..798fbcbf92 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -810,6 +810,7 @@ interface IImage { // @public interface IImageCalibration { + aspect?: number; // (undocumented) columnPixelSpacing?: number; rowPixelSpacing?: number; @@ -1024,8 +1025,7 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - scale?: number; - calibration?: IImageCalibration; + calibration: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 79d7f6d3a6..760c976e3c 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -2500,6 +2500,7 @@ interface IImage { // @public interface IImageCalibration { + aspect?: number; // (undocumented) columnPixelSpacing?: number; rowPixelSpacing?: number; @@ -2723,8 +2724,7 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - scale?: number; - calibration?: IImageCalibration; + calibration: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/packages/core/src/types/EventTypes.ts b/packages/core/src/types/EventTypes.ts index 04fe6d8317..3c1da36b77 100644 --- a/packages/core/src/types/EventTypes.ts +++ b/packages/core/src/types/EventTypes.ts @@ -226,8 +226,8 @@ type ImageSpacingCalibratedEventDetail = { viewportId: string; renderingEngineId: string; imageId: string; - scale?: number; - calibration?: IImageCalibration; + /** calibration contains the scaling information as well as other calibration info */ + calibration: IImageCalibration; imageData: vtkImageData; worldToIndex: mat4; }; diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 5af9e7b0af..7b8b2af30d 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -5,11 +5,23 @@ import CalibrationTypes from '../enums/CalibrationTypes'; * of image calibration. */ export interface IImageCalibration { - /** The pixel spacing for the image, in mm between pixel centers */ + /** + * The pixel spacing for the image, in mm between pixel centers + * These are not required, and are deprecated in favour of getting the original + * image spacing and then applying the transforms. The values here should + * be identical to original spacing. + */ rowPixelSpacing?: number; columnPixelSpacing?: number; /** The scaling of this image - new spacing = original pixelSpacing/scale */ scale?: number; + /** + * The aspect ratio of the screen. + * Defaults to 1 if not specified. + * Not well handled currently as changing the aspect ratio does not result in + * updating measurements in any meaningful way. + */ + aspect?: number; /** The type of the pixel spacing, distinguishing between various * types projection (CR/DX/MG) spacing and volumetric spacing (the type is * an empty string as it doesn't get a suffix, but this distinguishes it From d8b1d7ae4c07e0115ebe14bfe7fdd8a5b0fe3758 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Mon, 17 Jul 2023 10:40:20 -0400 Subject: [PATCH 35/37] Updated aspect docs as requested --- packages/core/src/types/IImageCalibration.ts | 14 +++++++++----- .../tools/src/tools/annotation/CircleROITool.ts | 6 +++++- .../tools/src/utilities/getCalibratedUnits.ts | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/core/src/types/IImageCalibration.ts b/packages/core/src/types/IImageCalibration.ts index 7b8b2af30d..987d648e11 100644 --- a/packages/core/src/types/IImageCalibration.ts +++ b/packages/core/src/types/IImageCalibration.ts @@ -13,13 +13,17 @@ export interface IImageCalibration { */ rowPixelSpacing?: number; columnPixelSpacing?: number; - /** The scaling of this image - new spacing = original pixelSpacing/scale */ + /** The scaling of measurement values relative to the base pixel spacing (1 if not specified) */ scale?: number; /** - * The aspect ratio of the screen. - * Defaults to 1 if not specified. - * Not well handled currently as changing the aspect ratio does not result in - * updating measurements in any meaningful way. + * The calibration aspect ratio for non-square calibrations. + * This is the aspect ratio similar to the scale above that applies when + * the viewport is displaying non-square image pixels as square screen pixels. + * + * Defaults to 1 if not specified, and is also 1 if the Viewport has squared + * up the image pixels so that they are displayed as a square. + * Not well handled currently as this needs to be incorporated into + * tools when doing calculations. */ aspect?: number; /** The type of the pixel spacing, distinguishing between various diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 288d0280f2..2150ff0ba1 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -13,6 +13,7 @@ import { getCalibratedLengthUnits, getCalibratedAreaUnits, getCalibratedScale, + getCalibratedAspect, } from '../../utilities/getCalibratedUnits'; import roundNumber from '../../utilities/roundNumber'; import throttle from '../../utilities/throttle'; @@ -1001,8 +1002,11 @@ class CircleROITool extends AnnotationTool { ); const isEmptyArea = worldWidth === 0 && worldHeight === 0; const scale = getCalibratedScale(image); + const aspect = getCalibratedAspect(image); const area = Math.abs( - Math.PI * (worldWidth / scale / 2) * (worldHeight / scale / 2) + Math.PI * + (worldWidth / scale / 2) * + (worldHeight / aspect / scale / 2) ); let count = 0; diff --git a/packages/tools/src/utilities/getCalibratedUnits.ts b/packages/tools/src/utilities/getCalibratedUnits.ts index c44c696b9b..628568ec79 100644 --- a/packages/tools/src/utilities/getCalibratedUnits.ts +++ b/packages/tools/src/utilities/getCalibratedUnits.ts @@ -48,6 +48,19 @@ const getCalibratedAreaUnits = (handles, image): string => { */ const getCalibratedScale = (image) => image.calibration?.scale || 1; +/** Gets the aspect ratio of the screen display relative to the image + * display in order to square up measurement values. + * That is, suppose the spacing on the image is 1, 0.5 (x,y spacing) + * This is displayed at 1, 1 spacing on screen, then the + * aspect value will be 1/0.5 = 2 + */ +const getCalibratedAspect = (image) => image.calibration?.aspect || 1; + export default getCalibratedLengthUnits; -export { getCalibratedAreaUnits, getCalibratedLengthUnits, getCalibratedScale }; +export { + getCalibratedAreaUnits, + getCalibratedLengthUnits, + getCalibratedScale, + getCalibratedAspect, +}; From 8d2d5ca6bb76939b1a4130122b9ef2f8437e93ad Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Wed, 19 Jul 2023 15:29:46 -0400 Subject: [PATCH 36/37] Update API --- common/reviews/api/streaming-image-volume-loader.api.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 361dcf7283..6cb6e2cf57 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,7 +4,6 @@ ```ts -import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { GetGPUTier } from 'detect-gpu'; import type { mat4 } from 'gl-matrix'; import type { TierResult } from 'detect-gpu'; @@ -1546,7 +1545,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: default_2; + requestType: RequestType_2; additionalDetails: { volumeId: string; }; From e412d6ce375329961a7cbeca67ae7f1f3cbdc8e7 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 21 Jul 2023 15:05:09 -0400 Subject: [PATCH 37/37] api-check --- common/reviews/api/streaming-image-volume-loader.api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 6cb6e2cf57..361dcf7283 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -4,6 +4,7 @@ ```ts +import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType'; import type { GetGPUTier } from 'detect-gpu'; import type { mat4 } from 'gl-matrix'; import type { TierResult } from 'detect-gpu'; @@ -1545,7 +1546,7 @@ export class StreamingImageVolume extends BaseStreamingImageVolume { }; }; priority: number; - requestType: RequestType_2; + requestType: default_2; additionalDetails: { volumeId: string; };