@cornerstonejs/tools 2.0.0-beta.18 → 2.0.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/tools/ScaleOverlayTool.d.ts +1 -1
- package/dist/esm/tools/annotation/BidirectionalTool.js +6 -7
- package/dist/esm/tools/annotation/CircleROITool.js +14 -14
- package/dist/esm/tools/annotation/DragProbeTool.js +3 -3
- package/dist/esm/tools/annotation/EllipticalROITool.js +12 -12
- package/dist/esm/tools/annotation/LengthTool.js +2 -2
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js +13 -13
- package/dist/esm/tools/annotation/ProbeTool.js +10 -10
- package/dist/esm/tools/annotation/RectangleROITool.js +12 -12
- package/dist/esm/tools/annotation/SplineROITool.js +5 -5
- package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +2 -2
- package/dist/esm/utilities/getCalibratedUnits.d.ts +6 -0
- package/dist/esm/utilities/getCalibratedUnits.js +16 -8
- package/dist/esm/utilities/getPixelValueUnits.d.ts +6 -0
- package/dist/esm/utilities/{getModalityUnit.js → getPixelValueUnits.js} +2 -2
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/esm/utilities/getModalityUnit.d.ts +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AnnotationDisplayTool from './base/AnnotationDisplayTool';
|
|
2
2
|
import { ScaleOverlayAnnotation } from '../types/ToolSpecificAnnotationTypes';
|
|
3
3
|
import type { Types } from '@cornerstonejs/core';
|
|
4
|
-
import { PublicToolProps, ToolProps, SVGDrawingHelper } from '../types';
|
|
4
|
+
import { EventTypes, PublicToolProps, ToolProps, SVGDrawingHelper } from '../types';
|
|
5
5
|
declare class ScaleOverlayTool extends AnnotationDisplayTool {
|
|
6
6
|
static toolName: any;
|
|
7
7
|
touchDragCallback: any;
|
|
@@ -609,13 +609,13 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
609
609
|
const index4 = transformWorldToIndex(imageData, worldPos4);
|
|
610
610
|
const handles1 = [index1, index2];
|
|
611
611
|
const handles2 = [index3, index4];
|
|
612
|
-
const { scale: scale1,
|
|
613
|
-
const { scale: scale2,
|
|
612
|
+
const { scale: scale1, lengthUnits: units1 } = getCalibratedLengthUnitsAndScale(image, handles1);
|
|
613
|
+
const { scale: scale2, lengthUnits: units2 } = getCalibratedLengthUnitsAndScale(image, handles2);
|
|
614
614
|
const dist1 = this._calculateLength(worldPos1, worldPos2) / scale1;
|
|
615
615
|
const dist2 = this._calculateLength(worldPos3, worldPos4) / scale2;
|
|
616
616
|
const length = dist1 > dist2 ? dist1 : dist2;
|
|
617
617
|
const width = dist1 > dist2 ? dist2 : dist1;
|
|
618
|
-
const
|
|
618
|
+
const lengthUnits = dist1 > dist2 ? units1 : units2;
|
|
619
619
|
const widthUnit = dist1 > dist2 ? units2 : units1;
|
|
620
620
|
this._isInsideVolume(index1, index2, index3, index4, dimensions)
|
|
621
621
|
? (this.isHandleOutsideImage = false)
|
|
@@ -623,8 +623,7 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
623
623
|
cachedStats[targetId] = {
|
|
624
624
|
length,
|
|
625
625
|
width,
|
|
626
|
-
|
|
627
|
-
lengthUnit,
|
|
626
|
+
lengthUnits,
|
|
628
627
|
widthUnit,
|
|
629
628
|
};
|
|
630
629
|
}
|
|
@@ -714,7 +713,7 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
714
713
|
}
|
|
715
714
|
function defaultGetTextLines(data, targetId) {
|
|
716
715
|
const { cachedStats, label } = data;
|
|
717
|
-
const { length, width, unit,
|
|
716
|
+
const { length, width, unit, lengthUnits, widthUnit } = cachedStats[targetId];
|
|
718
717
|
const textLines = [];
|
|
719
718
|
if (label) {
|
|
720
719
|
textLines.push(label);
|
|
@@ -722,7 +721,7 @@ function defaultGetTextLines(data, targetId) {
|
|
|
722
721
|
if (length === undefined) {
|
|
723
722
|
return textLines;
|
|
724
723
|
}
|
|
725
|
-
textLines.push(`L: ${roundNumber(length)} ${
|
|
724
|
+
textLines.push(`L: ${roundNumber(length)} ${lengthUnits || unit}`, `W: ${roundNumber(width)} ${widthUnit || unit}`);
|
|
726
725
|
return textLines;
|
|
727
726
|
}
|
|
728
727
|
BidirectionalTool.toolName = 'Bidirectional';
|
|
@@ -16,7 +16,7 @@ import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorld
|
|
|
16
16
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
17
17
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
18
18
|
import { pointInShapeCallback } from '../../utilities';
|
|
19
|
-
import {
|
|
19
|
+
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
20
20
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
21
21
|
import { getCanvasCircleCorners, getCanvasCircleRadius, } from '../../utilities/math/circle';
|
|
22
22
|
import { pointInEllipse } from '../../utilities/math/ellipse';
|
|
@@ -347,14 +347,14 @@ class CircleROITool extends AnnotationTool {
|
|
|
347
347
|
const canvasCorners = getCanvasCircleCorners(canvasCoordinates);
|
|
348
348
|
const { centerPointRadius } = this.configuration;
|
|
349
349
|
if (!data.cachedStats[targetId] ||
|
|
350
|
-
data.cachedStats[targetId].
|
|
350
|
+
data.cachedStats[targetId].areaUnits == null) {
|
|
351
351
|
data.cachedStats[targetId] = {
|
|
352
352
|
Modality: null,
|
|
353
353
|
area: null,
|
|
354
354
|
max: null,
|
|
355
355
|
mean: null,
|
|
356
356
|
stdDev: null,
|
|
357
|
-
|
|
357
|
+
areaUnits: null,
|
|
358
358
|
radius: null,
|
|
359
359
|
radiusUnit: null,
|
|
360
360
|
perimeter: null,
|
|
@@ -508,16 +508,16 @@ class CircleROITool extends AnnotationTool {
|
|
|
508
508
|
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(viewPlaneNormal, viewUp, worldPos1, worldPos2);
|
|
509
509
|
const isEmptyArea = worldWidth === 0 && worldHeight === 0;
|
|
510
510
|
const handles = [pos1Index, pos2Index];
|
|
511
|
-
const { scale,
|
|
511
|
+
const { scale, lengthUnits, areaUnits } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
512
512
|
const aspect = getCalibratedAspect(image);
|
|
513
513
|
const area = Math.abs(Math.PI *
|
|
514
514
|
(worldWidth / scale / 2) *
|
|
515
515
|
(worldHeight / aspect / scale / 2));
|
|
516
|
-
const
|
|
516
|
+
const pixelUnitsOptions = {
|
|
517
517
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
518
518
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
519
519
|
};
|
|
520
|
-
const
|
|
520
|
+
const pixelValueUnits = getPixelValueUnits(metadata.Modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
|
|
521
521
|
const pointsInShape = pointInShapeCallback(imageData, (pointLPS) => pointInEllipse(ellipseObj, pointLPS, {
|
|
522
522
|
fast: true,
|
|
523
523
|
}), this.configuration.statsCalculator.statsCallback, boundsIJK);
|
|
@@ -531,11 +531,11 @@ class CircleROITool extends AnnotationTool {
|
|
|
531
531
|
statsArray: stats.array,
|
|
532
532
|
pointsInShape: pointsInShape,
|
|
533
533
|
isEmptyArea,
|
|
534
|
-
|
|
534
|
+
areaUnits,
|
|
535
535
|
radius: worldWidth / 2 / scale,
|
|
536
|
-
radiusUnit:
|
|
536
|
+
radiusUnit: lengthUnits,
|
|
537
537
|
perimeter: (2 * Math.PI * (worldWidth / 2)) / scale,
|
|
538
|
-
|
|
538
|
+
pixelValueUnits,
|
|
539
539
|
};
|
|
540
540
|
}
|
|
541
541
|
else {
|
|
@@ -558,7 +558,7 @@ class CircleROITool extends AnnotationTool {
|
|
|
558
558
|
}
|
|
559
559
|
function defaultGetTextLines(data, targetId) {
|
|
560
560
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
561
|
-
const { radius, radiusUnit, area, mean, stdDev, max, isEmptyArea,
|
|
561
|
+
const { radius, radiusUnit, area, mean, stdDev, max, isEmptyArea, areaUnits, pixelValueUnits, } = cachedVolumeStats;
|
|
562
562
|
const textLines = [];
|
|
563
563
|
if (radius) {
|
|
564
564
|
const radiusLine = isEmptyArea
|
|
@@ -569,17 +569,17 @@ function defaultGetTextLines(data, targetId) {
|
|
|
569
569
|
if (area) {
|
|
570
570
|
const areaLine = isEmptyArea
|
|
571
571
|
? `Area: Oblique not supported`
|
|
572
|
-
: `Area: ${roundNumber(area)} ${
|
|
572
|
+
: `Area: ${roundNumber(area)} ${areaUnits}`;
|
|
573
573
|
textLines.push(areaLine);
|
|
574
574
|
}
|
|
575
575
|
if (mean) {
|
|
576
|
-
textLines.push(`Mean: ${roundNumber(mean)} ${
|
|
576
|
+
textLines.push(`Mean: ${roundNumber(mean)} ${pixelValueUnits}`);
|
|
577
577
|
}
|
|
578
578
|
if (max) {
|
|
579
|
-
textLines.push(`Max: ${roundNumber(max)} ${
|
|
579
|
+
textLines.push(`Max: ${roundNumber(max)} ${pixelValueUnits}`);
|
|
580
580
|
}
|
|
581
581
|
if (stdDev) {
|
|
582
|
-
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${
|
|
582
|
+
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${pixelValueUnits}`);
|
|
583
583
|
}
|
|
584
584
|
return textLines;
|
|
585
585
|
}
|
|
@@ -84,7 +84,7 @@ class DragProbeTool extends ProbeTool {
|
|
|
84
84
|
annotation,
|
|
85
85
|
styleSpecifier,
|
|
86
86
|
});
|
|
87
|
-
const
|
|
87
|
+
const pixelUnitsOptions = {
|
|
88
88
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
89
89
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
90
90
|
};
|
|
@@ -122,13 +122,13 @@ class DragProbeTool extends ProbeTool {
|
|
|
122
122
|
}
|
|
123
123
|
function defaultGetTextLines(data, targetId) {
|
|
124
124
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
125
|
-
const { index, value,
|
|
125
|
+
const { index, value, pixelValueUnits } = cachedVolumeStats;
|
|
126
126
|
if (value === undefined) {
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
const textLines = [];
|
|
130
130
|
textLines.push(`(${index[0]}, ${index[1]}, ${index[2]})`);
|
|
131
|
-
textLines.push(`${value.toFixed(2)} ${
|
|
131
|
+
textLines.push(`${value.toFixed(2)} ${pixelValueUnits}`);
|
|
132
132
|
return textLines;
|
|
133
133
|
}
|
|
134
134
|
DragProbeTool.toolName = 'DragProbe';
|
|
@@ -17,7 +17,7 @@ import { pointInEllipse, getCanvasEllipseCorners, } from '../../utilities/math/e
|
|
|
17
17
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
18
18
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
19
19
|
import { pointInShapeCallback } from '../../utilities/';
|
|
20
|
-
import {
|
|
20
|
+
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
21
21
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
22
22
|
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
23
23
|
const { transformWorldToIndex } = csUtils;
|
|
@@ -428,14 +428,14 @@ class EllipticalROITool extends AnnotationTool {
|
|
|
428
428
|
const canvasCorners = (getCanvasEllipseCorners(canvasCoordinates));
|
|
429
429
|
const { centerPointRadius } = this.configuration;
|
|
430
430
|
if (!data.cachedStats[targetId] ||
|
|
431
|
-
data.cachedStats[targetId].
|
|
431
|
+
data.cachedStats[targetId].areaUnits == null) {
|
|
432
432
|
data.cachedStats[targetId] = {
|
|
433
433
|
Modality: null,
|
|
434
434
|
area: null,
|
|
435
435
|
max: null,
|
|
436
436
|
mean: null,
|
|
437
437
|
stdDev: null,
|
|
438
|
-
|
|
438
|
+
areaUnits: null,
|
|
439
439
|
};
|
|
440
440
|
this._calculateCachedStats(annotation, viewport, renderingEngine);
|
|
441
441
|
}
|
|
@@ -592,11 +592,11 @@ class EllipticalROITool extends AnnotationTool {
|
|
|
592
592
|
const area = Math.abs(Math.PI * (worldWidth / 2) * (worldHeight / 2)) /
|
|
593
593
|
scale /
|
|
594
594
|
scale;
|
|
595
|
-
const
|
|
595
|
+
const pixelUnitsOptions = {
|
|
596
596
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
597
597
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
598
598
|
};
|
|
599
|
-
const
|
|
599
|
+
const pixelValueUnits = getPixelValueUnits(metadata.Modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
|
|
600
600
|
const pointsInShape = pointInShapeCallback(imageData, (pointLPS) => pointInEllipse(ellipseObj, pointLPS, { fast: true }), this.configuration.statsCalculator.statsCallback, boundsIJK);
|
|
601
601
|
const stats = this.configuration.statsCalculator.getStatistics();
|
|
602
602
|
cachedStats[targetId] = {
|
|
@@ -608,8 +608,8 @@ class EllipticalROITool extends AnnotationTool {
|
|
|
608
608
|
statsArray: stats.array,
|
|
609
609
|
pointsInShape,
|
|
610
610
|
isEmptyArea,
|
|
611
|
-
|
|
612
|
-
|
|
611
|
+
areaUnits,
|
|
612
|
+
pixelValueUnits,
|
|
613
613
|
};
|
|
614
614
|
}
|
|
615
615
|
annotation.invalidated = false;
|
|
@@ -647,22 +647,22 @@ class EllipticalROITool extends AnnotationTool {
|
|
|
647
647
|
}
|
|
648
648
|
function defaultGetTextLines(data, targetId) {
|
|
649
649
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
650
|
-
const { area, mean, stdDev, max, isEmptyArea,
|
|
650
|
+
const { area, mean, stdDev, max, isEmptyArea, areaUnits, pixelValueUnits } = cachedVolumeStats;
|
|
651
651
|
const textLines = [];
|
|
652
652
|
if (area) {
|
|
653
653
|
const areaLine = isEmptyArea
|
|
654
654
|
? `Area: Oblique not supported`
|
|
655
|
-
: `Area: ${roundNumber(area)} ${
|
|
655
|
+
: `Area: ${roundNumber(area)} ${areaUnits}`;
|
|
656
656
|
textLines.push(areaLine);
|
|
657
657
|
}
|
|
658
658
|
if (mean) {
|
|
659
|
-
textLines.push(`Mean: ${roundNumber(mean)} ${
|
|
659
|
+
textLines.push(`Mean: ${roundNumber(mean)} ${pixelValueUnits}`);
|
|
660
660
|
}
|
|
661
661
|
if (max) {
|
|
662
|
-
textLines.push(`Max: ${roundNumber(max)} ${
|
|
662
|
+
textLines.push(`Max: ${roundNumber(max)} ${pixelValueUnits}`);
|
|
663
663
|
}
|
|
664
664
|
if (stdDev) {
|
|
665
|
-
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${
|
|
665
|
+
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${pixelValueUnits}`);
|
|
666
666
|
}
|
|
667
667
|
return textLines;
|
|
668
668
|
}
|
|
@@ -393,14 +393,14 @@ class LengthTool extends AnnotationTool {
|
|
|
393
393
|
const index1 = transformWorldToIndex(imageData, worldPos1);
|
|
394
394
|
const index2 = transformWorldToIndex(imageData, worldPos2);
|
|
395
395
|
const handles = [index1, index2];
|
|
396
|
-
const { scale,
|
|
396
|
+
const { scale, lengthUnits } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
397
397
|
const length = this._calculateLength(worldPos1, worldPos2) / scale;
|
|
398
398
|
this._isInsideVolume(index1, index2, dimensions)
|
|
399
399
|
? (this.isHandleOutsideImage = false)
|
|
400
400
|
: (this.isHandleOutsideImage = true);
|
|
401
401
|
cachedStats[targetId] = {
|
|
402
402
|
length,
|
|
403
|
-
|
|
403
|
+
lengthUnits,
|
|
404
404
|
};
|
|
405
405
|
}
|
|
406
406
|
annotation.invalidated = false;
|
|
@@ -19,7 +19,7 @@ import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
|
19
19
|
import { getLineSegmentIntersectionsCoordinates } from '../../utilities/math/polyline';
|
|
20
20
|
import pointInShapeCallback from '../../utilities/pointInShapeCallback';
|
|
21
21
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
22
|
-
import {
|
|
22
|
+
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
23
23
|
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
24
24
|
import calculatePerimeter from '../../utilities/contours/calculatePerimeter';
|
|
25
25
|
import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
|
|
@@ -249,11 +249,11 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
249
249
|
}
|
|
250
250
|
return result;
|
|
251
251
|
}, this.configuration.statsCalculator.statsCallback, boundsIJK);
|
|
252
|
-
const
|
|
252
|
+
const pixelUnitsOptions = {
|
|
253
253
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
254
254
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
255
255
|
};
|
|
256
|
-
const
|
|
256
|
+
const pixelValueUnits = getPixelValueUnits(metadata.Modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
|
|
257
257
|
const stats = this.configuration.statsCalculator.getStatistics();
|
|
258
258
|
cachedStats[targetId] = {
|
|
259
259
|
Modality: metadata.Modality,
|
|
@@ -264,8 +264,8 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
264
264
|
stdDev: stats.stdDev?.value,
|
|
265
265
|
statsArray: stats.array,
|
|
266
266
|
pointsInShape: pointsInShape,
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
areaUnits,
|
|
268
|
+
pixelValueUnits,
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
271
|
triggerAnnotationModified(annotation, enabledElement.viewport.element, ChangeTypes.StatsUpdated);
|
|
@@ -443,14 +443,14 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
443
443
|
if (!this.commonData?.movingTextBox) {
|
|
444
444
|
const { data } = annotation;
|
|
445
445
|
if (!data.cachedStats[targetId] ||
|
|
446
|
-
data.cachedStats[targetId].
|
|
446
|
+
data.cachedStats[targetId].areaUnits == null) {
|
|
447
447
|
data.cachedStats[targetId] = {
|
|
448
448
|
Modality: null,
|
|
449
449
|
area: null,
|
|
450
450
|
max: null,
|
|
451
451
|
mean: null,
|
|
452
452
|
stdDev: null,
|
|
453
|
-
|
|
453
|
+
areaUnits: null,
|
|
454
454
|
};
|
|
455
455
|
this._calculateCachedStats(annotation, viewport, renderingEngine, enabledElement);
|
|
456
456
|
}
|
|
@@ -462,25 +462,25 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
462
462
|
}
|
|
463
463
|
function defaultGetTextLines(data, targetId) {
|
|
464
464
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
465
|
-
const { area, mean, stdDev, perimeter, max, isEmptyArea,
|
|
465
|
+
const { area, mean, stdDev, perimeter, max, isEmptyArea, areaUnits, pixelValueUnits, } = cachedVolumeStats || {};
|
|
466
466
|
const textLines = [];
|
|
467
467
|
if (area) {
|
|
468
468
|
const areaLine = isEmptyArea
|
|
469
469
|
? `Area: Oblique not supported`
|
|
470
|
-
: `Area: ${roundNumber(area)} ${
|
|
470
|
+
: `Area: ${roundNumber(area)} ${areaUnits}`;
|
|
471
471
|
textLines.push(areaLine);
|
|
472
472
|
}
|
|
473
473
|
if (mean) {
|
|
474
|
-
textLines.push(`Mean: ${roundNumber(mean)} ${
|
|
474
|
+
textLines.push(`Mean: ${roundNumber(mean)} ${pixelValueUnits}`);
|
|
475
475
|
}
|
|
476
476
|
if (max) {
|
|
477
|
-
textLines.push(`Max: ${roundNumber(max)} ${
|
|
477
|
+
textLines.push(`Max: ${roundNumber(max)} ${pixelValueUnits}`);
|
|
478
478
|
}
|
|
479
479
|
if (stdDev) {
|
|
480
|
-
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${
|
|
480
|
+
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${pixelValueUnits}`);
|
|
481
481
|
}
|
|
482
482
|
if (perimeter) {
|
|
483
|
-
textLines.push(`Perimeter: ${roundNumber(perimeter)} ${
|
|
483
|
+
textLines.push(`Perimeter: ${roundNumber(perimeter)} ${pixelValueUnits}`);
|
|
484
484
|
}
|
|
485
485
|
return textLines;
|
|
486
486
|
}
|
|
@@ -11,7 +11,7 @@ import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'
|
|
|
11
11
|
import { roundNumber } from '../../utilities';
|
|
12
12
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
13
13
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
14
|
-
import {
|
|
14
|
+
import { getPixelValueUnits, } from '../../utilities/getPixelValueUnits';
|
|
15
15
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
16
16
|
const { transformWorldToIndex } = csUtils;
|
|
17
17
|
class ProbeTool extends AnnotationTool {
|
|
@@ -257,7 +257,7 @@ class ProbeTool extends AnnotationTool {
|
|
|
257
257
|
const targetIds = Object.keys(cachedStats);
|
|
258
258
|
for (let i = 0; i < targetIds.length; i++) {
|
|
259
259
|
const targetId = targetIds[i];
|
|
260
|
-
const
|
|
260
|
+
const pixelUnitsOptions = {
|
|
261
261
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
262
262
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
263
263
|
};
|
|
@@ -294,25 +294,25 @@ class ProbeTool extends AnnotationTool {
|
|
|
294
294
|
const viewport = viewports[0];
|
|
295
295
|
index[2] = viewport.getCurrentImageIdIndex();
|
|
296
296
|
}
|
|
297
|
-
let
|
|
297
|
+
let pixelValueUnits;
|
|
298
298
|
if (modality === 'US') {
|
|
299
299
|
const calibratedResults = getCalibratedProbeUnitsAndValue(image, [
|
|
300
300
|
index,
|
|
301
301
|
]);
|
|
302
302
|
const hasEnhancedRegionValues = calibratedResults.values.every((value) => value !== null);
|
|
303
303
|
value = hasEnhancedRegionValues ? calibratedResults.values : value;
|
|
304
|
-
|
|
304
|
+
pixelValueUnits = hasEnhancedRegionValues
|
|
305
305
|
? calibratedResults.units
|
|
306
306
|
: 'raw';
|
|
307
307
|
}
|
|
308
308
|
else {
|
|
309
|
-
|
|
309
|
+
pixelValueUnits = getPixelValueUnits(modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
|
|
310
310
|
}
|
|
311
311
|
cachedStats[targetId] = {
|
|
312
312
|
index,
|
|
313
313
|
value,
|
|
314
314
|
Modality: modality,
|
|
315
|
-
|
|
315
|
+
pixelValueUnits,
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
318
|
else {
|
|
@@ -330,19 +330,19 @@ class ProbeTool extends AnnotationTool {
|
|
|
330
330
|
}
|
|
331
331
|
function defaultGetTextLines(data, targetId) {
|
|
332
332
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
333
|
-
const { index, value,
|
|
333
|
+
const { index, value, pixelValueUnits } = cachedVolumeStats;
|
|
334
334
|
if (value === undefined) {
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
const textLines = [];
|
|
338
338
|
textLines.push(`(${index[0]}, ${index[1]}, ${index[2]})`);
|
|
339
|
-
if (value instanceof Array &&
|
|
339
|
+
if (value instanceof Array && pixelValueUnits instanceof Array) {
|
|
340
340
|
for (let i = 0; i < value.length; i++) {
|
|
341
|
-
textLines.push(`${roundNumber(value[i])} ${
|
|
341
|
+
textLines.push(`${roundNumber(value[i])} ${pixelValueUnits[i]}`);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
else {
|
|
345
|
-
textLines.push(`${roundNumber(value)} ${
|
|
345
|
+
textLines.push(`${roundNumber(value)} ${pixelValueUnits}`);
|
|
346
346
|
}
|
|
347
347
|
return textLines;
|
|
348
348
|
}
|
|
@@ -16,7 +16,7 @@ import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
|
16
16
|
import getWorldWidthAndHeightFromCorners from '../../utilities/planar/getWorldWidthAndHeightFromCorners';
|
|
17
17
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
18
18
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
19
|
-
import {
|
|
19
|
+
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
20
20
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
21
21
|
import { pointInShapeCallback } from '../../utilities/';
|
|
22
22
|
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
@@ -347,14 +347,14 @@ class RectangleROITool extends AnnotationTool {
|
|
|
347
347
|
});
|
|
348
348
|
const { viewPlaneNormal, viewUp } = viewport.getCamera();
|
|
349
349
|
if (!data.cachedStats[targetId] ||
|
|
350
|
-
data.cachedStats[targetId].
|
|
350
|
+
data.cachedStats[targetId].areaUnits == null) {
|
|
351
351
|
data.cachedStats[targetId] = {
|
|
352
352
|
Modality: null,
|
|
353
353
|
area: null,
|
|
354
354
|
max: null,
|
|
355
355
|
mean: null,
|
|
356
356
|
stdDev: null,
|
|
357
|
-
|
|
357
|
+
areaUnits: null,
|
|
358
358
|
};
|
|
359
359
|
this._calculateCachedStats(annotation, viewPlaneNormal, viewUp, renderingEngine, enabledElement);
|
|
360
360
|
}
|
|
@@ -490,11 +490,11 @@ class RectangleROITool extends AnnotationTool {
|
|
|
490
490
|
const handles = [pos1Index, pos2Index];
|
|
491
491
|
const { scale, areaUnits } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
492
492
|
const area = Math.abs(worldWidth * worldHeight) / (scale * scale);
|
|
493
|
-
const
|
|
493
|
+
const pixelUnitsOptions = {
|
|
494
494
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
495
495
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
496
496
|
};
|
|
497
|
-
const
|
|
497
|
+
const pixelValueUnits = getPixelValueUnits(metadata.Modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
|
|
498
498
|
const pointsInShape = pointInShapeCallback(imageData, () => true, this.configuration.statsCalculator.statsCallback, boundsIJK);
|
|
499
499
|
const stats = this.configuration.statsCalculator.getStatistics();
|
|
500
500
|
cachedStats[targetId] = {
|
|
@@ -505,8 +505,8 @@ class RectangleROITool extends AnnotationTool {
|
|
|
505
505
|
max: stats.max?.value,
|
|
506
506
|
statsArray: stats.array,
|
|
507
507
|
pointsInShape: pointsInShape,
|
|
508
|
-
|
|
509
|
-
|
|
508
|
+
areaUnits,
|
|
509
|
+
pixelValueUnits,
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
512
|
else {
|
|
@@ -529,15 +529,15 @@ class RectangleROITool extends AnnotationTool {
|
|
|
529
529
|
}
|
|
530
530
|
function defaultGetTextLines(data, targetId) {
|
|
531
531
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
532
|
-
const { area, mean, max, stdDev,
|
|
532
|
+
const { area, mean, max, stdDev, areaUnits, pixelValueUnits } = cachedVolumeStats;
|
|
533
533
|
if (mean === undefined) {
|
|
534
534
|
return;
|
|
535
535
|
}
|
|
536
536
|
const textLines = [];
|
|
537
|
-
textLines.push(`Area: ${roundNumber(area)} ${
|
|
538
|
-
textLines.push(`Mean: ${roundNumber(mean)} ${
|
|
539
|
-
textLines.push(`Max: ${roundNumber(max)} ${
|
|
540
|
-
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${
|
|
537
|
+
textLines.push(`Area: ${roundNumber(area)} ${areaUnits}`);
|
|
538
|
+
textLines.push(`Mean: ${roundNumber(mean)} ${pixelValueUnits}`);
|
|
539
|
+
textLines.push(`Max: ${roundNumber(max)} ${pixelValueUnits}`);
|
|
540
|
+
textLines.push(`Std Dev: ${roundNumber(stdDev)} ${pixelValueUnits}`);
|
|
541
541
|
return textLines;
|
|
542
542
|
}
|
|
543
543
|
RectangleROITool.toolName = 'RectangleROI';
|
|
@@ -468,7 +468,7 @@ class SplineROITool extends ContourSegmentationBaseTool {
|
|
|
468
468
|
cachedStats[targetId] = {
|
|
469
469
|
Modality: metadata.Modality,
|
|
470
470
|
area,
|
|
471
|
-
|
|
471
|
+
areaUnits,
|
|
472
472
|
};
|
|
473
473
|
}
|
|
474
474
|
this.triggerAnnotationModified(annotation, enabledElement, ChangeTypes.StatsUpdated);
|
|
@@ -557,11 +557,11 @@ class SplineROITool extends ContourSegmentationBaseTool {
|
|
|
557
557
|
});
|
|
558
558
|
super.renderAnnotationInstance(renderContext);
|
|
559
559
|
if (!data.cachedStats[targetId] ||
|
|
560
|
-
data.cachedStats[targetId].
|
|
560
|
+
data.cachedStats[targetId].areaUnits == null) {
|
|
561
561
|
data.cachedStats[targetId] = {
|
|
562
562
|
Modality: null,
|
|
563
563
|
area: null,
|
|
564
|
-
|
|
564
|
+
areaUnits: null,
|
|
565
565
|
};
|
|
566
566
|
this._calculateCachedStats(annotation, element);
|
|
567
567
|
}
|
|
@@ -712,12 +712,12 @@ class SplineROITool extends ContourSegmentationBaseTool {
|
|
|
712
712
|
}
|
|
713
713
|
function defaultGetTextLines(data, targetId) {
|
|
714
714
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
715
|
-
const { area, isEmptyArea,
|
|
715
|
+
const { area, isEmptyArea, areaUnits } = cachedVolumeStats;
|
|
716
716
|
const textLines = [];
|
|
717
717
|
if (area) {
|
|
718
718
|
const areaLine = isEmptyArea
|
|
719
719
|
? `Area: Oblique not supported`
|
|
720
|
-
: `Area: ${roundNumber(area)} ${
|
|
720
|
+
: `Area: ${roundNumber(area)} ${areaUnits}`;
|
|
721
721
|
textLines.push(areaLine);
|
|
722
722
|
}
|
|
723
723
|
return textLines;
|
|
@@ -7,7 +7,7 @@ interface ROICachedStats {
|
|
|
7
7
|
[targetId: string]: {
|
|
8
8
|
Modality: string;
|
|
9
9
|
area: number;
|
|
10
|
-
|
|
10
|
+
areaUnits: string;
|
|
11
11
|
max: number;
|
|
12
12
|
mean: number;
|
|
13
13
|
stdDev: number;
|
|
@@ -130,7 +130,7 @@ export type SplineROIAnnotation = ContourAnnotation & {
|
|
|
130
130
|
[targetId: string]: {
|
|
131
131
|
Modality: string;
|
|
132
132
|
area: number;
|
|
133
|
-
|
|
133
|
+
areaUnits: string;
|
|
134
134
|
};
|
|
135
135
|
};
|
|
136
136
|
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
declare const getCalibratedLengthUnitsAndScale: (image: any, handles: any) => {
|
|
2
|
+
lengthUnits: string;
|
|
3
|
+
areaUnits: string;
|
|
4
|
+
scale: number;
|
|
5
|
+
units?: undefined;
|
|
6
|
+
} | {
|
|
2
7
|
units: string;
|
|
3
8
|
areaUnits: string;
|
|
4
9
|
scale: number;
|
|
10
|
+
lengthUnits?: undefined;
|
|
5
11
|
};
|
|
6
12
|
declare const getCalibratedProbeUnitsAndValue: (image: any, handles: any) => {
|
|
7
13
|
units: string[];
|
|
@@ -18,13 +18,13 @@ const EPS = 1e-3;
|
|
|
18
18
|
const SQUARE = '\xb2';
|
|
19
19
|
const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
20
20
|
const { calibration, hasPixelSpacing } = image;
|
|
21
|
-
let
|
|
22
|
-
let areaUnits =
|
|
21
|
+
let lengthUnits = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
|
|
22
|
+
let areaUnits = lengthUnits + SQUARE;
|
|
23
23
|
let scale = 1;
|
|
24
24
|
let calibrationType = '';
|
|
25
25
|
if (!calibration ||
|
|
26
26
|
(!calibration.type && !calibration.sequenceOfUltrasoundRegions)) {
|
|
27
|
-
return {
|
|
27
|
+
return { lengthUnits, areaUnits, scale };
|
|
28
28
|
}
|
|
29
29
|
if (calibration.type === CalibrationTypes.UNCALIBRATED) {
|
|
30
30
|
return { units: PIXEL_UNITS, areaUnits: PIXEL_UNITS + SQUARE, scale };
|
|
@@ -48,12 +48,16 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
48
48
|
imageIndex2[1] >= region.regionLocationMinY0 &&
|
|
49
49
|
imageIndex2[1] <= region.regionLocationMaxY1);
|
|
50
50
|
if (!regions?.length) {
|
|
51
|
-
return {
|
|
51
|
+
return { lengthUnits, areaUnits, scale };
|
|
52
52
|
}
|
|
53
53
|
regions = regions.filter((region) => SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
|
|
54
54
|
SUPPORTED_LENGTH_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`));
|
|
55
55
|
if (!regions.length) {
|
|
56
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
lengthUnits: PIXEL_UNITS,
|
|
58
|
+
areaUnits: PIXEL_UNITS + SQUARE,
|
|
59
|
+
scale,
|
|
60
|
+
};
|
|
57
61
|
}
|
|
58
62
|
const region = regions[0];
|
|
59
63
|
const physicalDeltaX = Math.abs(region.physicalDeltaX);
|
|
@@ -62,11 +66,15 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
62
66
|
if (isSamePhysicalDelta) {
|
|
63
67
|
scale = 1 / (physicalDeltaX * 10);
|
|
64
68
|
calibrationType = 'US Region';
|
|
65
|
-
|
|
69
|
+
lengthUnits = 'mm';
|
|
66
70
|
areaUnits = 'mm' + SQUARE;
|
|
67
71
|
}
|
|
68
72
|
else {
|
|
69
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
lengthUnits: PIXEL_UNITS,
|
|
75
|
+
areaUnits: PIXEL_UNITS + SQUARE,
|
|
76
|
+
scale,
|
|
77
|
+
};
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
else if (calibration.scale) {
|
|
@@ -82,7 +90,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
82
90
|
calibrationType = calibration.type;
|
|
83
91
|
}
|
|
84
92
|
return {
|
|
85
|
-
|
|
93
|
+
lengthUnits: lengthUnits + (calibrationType ? ` ${calibrationType}` : ''),
|
|
86
94
|
areaUnits: areaUnits + (calibrationType ? ` ${calibrationType}` : ''),
|
|
87
95
|
scale,
|
|
88
96
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { metaData } from '@cornerstonejs/core';
|
|
2
|
-
function
|
|
2
|
+
function getPixelValueUnits(modality, imageId, options) {
|
|
3
3
|
if (modality === 'CT') {
|
|
4
4
|
return 'HU';
|
|
5
5
|
}
|
|
@@ -23,4 +23,4 @@ function _handlePTModality(imageId, options) {
|
|
|
23
23
|
return petSeriesModule?.units || 'unitless';
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export {
|
|
26
|
+
export { getPixelValueUnits };
|