@cornerstonejs/tools 4.12.4 → 4.12.6
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/enums/MeasurementType.d.ts +6 -0
- package/dist/esm/enums/MeasurementType.js +7 -0
- package/dist/esm/enums/index.d.ts +1 -0
- package/dist/esm/enums/index.js +1 -0
- package/dist/esm/tools/annotation/BidirectionalTool.d.ts +0 -2
- package/dist/esm/tools/annotation/BidirectionalTool.js +24 -28
- package/dist/esm/tools/annotation/CircleROITool.d.ts +1 -2
- package/dist/esm/tools/annotation/CircleROITool.js +51 -44
- package/dist/esm/tools/annotation/EllipticalROITool.js +1 -1
- package/dist/esm/tools/annotation/LengthTool.d.ts +0 -2
- package/dist/esm/tools/annotation/LengthTool.js +13 -25
- package/dist/esm/tools/annotation/PlanarFreehandROITool.d.ts +2 -1
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js +66 -64
- package/dist/esm/tools/base/BaseTool.d.ts +4 -2
- package/dist/esm/tools/base/BaseTool.js +38 -11
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +4 -1
- package/dist/esm/types/CalculatorTypes.d.ts +4 -3
- package/dist/esm/utilities/contours/index.d.ts +1 -2
- package/dist/esm/utilities/contours/index.js +1 -2
- package/dist/esm/utilities/getCalibratedUnits.d.ts +2 -0
- package/dist/esm/utilities/getCalibratedUnits.js +32 -63
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
- package/dist/esm/utilities/contours/calculatePerimeter.d.ts +0 -2
- package/dist/esm/utilities/contours/calculatePerimeter.js +0 -16
package/dist/esm/enums/index.js
CHANGED
|
@@ -7,4 +7,5 @@ import { Swipe } from './Touch';
|
|
|
7
7
|
import StrategyCallbacks from './StrategyCallbacks';
|
|
8
8
|
import ChangeTypes from './ChangeTypes';
|
|
9
9
|
import WorkerTypes from './WorkerTypes';
|
|
10
|
+
export * from './MeasurementType';
|
|
10
11
|
export { MouseBindings, KeyboardBindings, ToolModes, AnnotationStyleStates, Events, SegmentationRepresentations, Swipe, StrategyCallbacks, ChangeTypes, WorkerTypes, };
|
|
@@ -39,9 +39,7 @@ declare class BidirectionalTool extends AnnotationTool {
|
|
|
39
39
|
_deactivateModify: (element: any) => void;
|
|
40
40
|
renderAnnotation: (enabledElement: Types.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean;
|
|
41
41
|
_movingLongAxisWouldPutItThroughShortAxis: (firstLineSegment: any, secondLineSegment: any) => boolean;
|
|
42
|
-
_calculateLength(pos1: any, pos2: any): number;
|
|
43
42
|
_calculateCachedStats: (annotation: any, renderingEngine: any, enabledElement: any) => any;
|
|
44
|
-
_isInsideVolume: (index1: any, index2: any, index3: any, index4: any, dimensions: any) => boolean;
|
|
45
43
|
_getSignedAngle: (vector1: any, vector2: any) => number;
|
|
46
44
|
}
|
|
47
45
|
export default BidirectionalTool;
|
|
@@ -9,7 +9,7 @@ import { isAnnotationVisible } from '../../stateManagement/annotation/annotation
|
|
|
9
9
|
import { triggerAnnotationCompleted, triggerAnnotationModified, } from '../../stateManagement/annotation/helpers/state';
|
|
10
10
|
import { drawLine as drawLineSvg, drawHandles as drawHandlesSvg, drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg';
|
|
11
11
|
import { state } from '../../store/state';
|
|
12
|
-
import { ChangeTypes, Events } from '../../enums';
|
|
12
|
+
import { ChangeTypes, Events, MeasurementType } from '../../enums';
|
|
13
13
|
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
14
14
|
import * as lineSegment from '../../utilities/math/line';
|
|
15
15
|
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
@@ -610,28 +610,36 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
610
610
|
continue;
|
|
611
611
|
}
|
|
612
612
|
const { imageData, dimensions } = image;
|
|
613
|
-
const
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
const {
|
|
620
|
-
const { scale: scale2, unit: units2 } = getCalibratedLengthUnitsAndScale(image, handles2);
|
|
621
|
-
const dist1 = this._calculateLength(worldPos1, worldPos2) / scale1;
|
|
622
|
-
const dist2 = this._calculateLength(worldPos3, worldPos4) / scale2;
|
|
613
|
+
const handles = data.handles.points.map((point) => imageData.worldToIndex(point));
|
|
614
|
+
const handles1 = handles.slice(0, 2);
|
|
615
|
+
const handles2 = handles.slice(2, 4);
|
|
616
|
+
const calibrate = getCalibratedLengthUnitsAndScale(image, handles);
|
|
617
|
+
const dist1 = BidirectionalTool.calculateLengthInIndex(calibrate, handles1);
|
|
618
|
+
const dist2 = BidirectionalTool.calculateLengthInIndex(calibrate, handles2);
|
|
619
|
+
const { unit } = calibrate;
|
|
623
620
|
const length = dist1 > dist2 ? dist1 : dist2;
|
|
624
621
|
const width = dist1 > dist2 ? dist2 : dist1;
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
this._isInsideVolume(index1, index2, index3, index4, dimensions)
|
|
628
|
-
? (this.isHandleOutsideImage = false)
|
|
629
|
-
: (this.isHandleOutsideImage = true);
|
|
622
|
+
const widthUnit = unit;
|
|
623
|
+
this.isHandleOutsideImage = !BidirectionalTool.isInsideVolume(dimensions, handles);
|
|
630
624
|
cachedStats[targetId] = {
|
|
631
625
|
length,
|
|
632
626
|
width,
|
|
633
627
|
unit,
|
|
634
628
|
widthUnit,
|
|
629
|
+
statsArray: [
|
|
630
|
+
{
|
|
631
|
+
value: length,
|
|
632
|
+
name: 'height',
|
|
633
|
+
unit,
|
|
634
|
+
type: MeasurementType.Linear,
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
value: width,
|
|
638
|
+
name: 'width',
|
|
639
|
+
unit,
|
|
640
|
+
type: MeasurementType.Linear,
|
|
641
|
+
},
|
|
642
|
+
],
|
|
635
643
|
};
|
|
636
644
|
}
|
|
637
645
|
const invalidated = annotation.invalidated;
|
|
@@ -641,12 +649,6 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
641
649
|
}
|
|
642
650
|
return cachedStats;
|
|
643
651
|
};
|
|
644
|
-
this._isInsideVolume = (index1, index2, index3, index4, dimensions) => {
|
|
645
|
-
return (csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
646
|
-
csUtils.indexWithinDimensions(index2, dimensions) &&
|
|
647
|
-
csUtils.indexWithinDimensions(index3, dimensions) &&
|
|
648
|
-
csUtils.indexWithinDimensions(index4, dimensions));
|
|
649
|
-
};
|
|
650
652
|
this._getSignedAngle = (vector1, vector2) => {
|
|
651
653
|
return Math.atan2(vector1[0] * vector2[1] - vector1[1] * vector2[0], vector1[0] * vector2[0] + vector1[1] * vector2[1]);
|
|
652
654
|
};
|
|
@@ -726,12 +728,6 @@ class BidirectionalTool extends AnnotationTool {
|
|
|
726
728
|
triggerAnnotationRenderForViewportIds([viewport.id]);
|
|
727
729
|
return annotation;
|
|
728
730
|
}; }
|
|
729
|
-
_calculateLength(pos1, pos2) {
|
|
730
|
-
const dx = pos1[0] - pos2[0];
|
|
731
|
-
const dy = pos1[1] - pos2[1];
|
|
732
|
-
const dz = pos1[2] - pos2[2];
|
|
733
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
734
|
-
}
|
|
735
731
|
}
|
|
736
732
|
function defaultGetTextLines(data, targetId) {
|
|
737
733
|
const { cachedStats, label } = data;
|
|
@@ -30,8 +30,7 @@ declare class CircleROITool extends AnnotationTool {
|
|
|
30
30
|
_activateDraw: (element: any) => void;
|
|
31
31
|
_deactivateDraw: (element: any) => void;
|
|
32
32
|
renderAnnotation: (enabledElement: Types.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean;
|
|
33
|
-
_calculateCachedStats: (annotation: any, viewport: any,
|
|
34
|
-
_isInsideVolume: (index1: any, index2: any, dimensions: any) => boolean;
|
|
33
|
+
_calculateCachedStats: (annotation: any, viewport: any, _renderingEngine: any, _enabledElement: any) => any;
|
|
35
34
|
static hydrate: (viewportId: string, points: Types.Point3[], options?: {
|
|
36
35
|
annotationUID?: string;
|
|
37
36
|
toolInstance?: CircleROITool;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { AnnotationTool } from '../base';
|
|
1
|
+
import { AnnotationTool, BaseTool } from '../base';
|
|
2
|
+
import { vec2, vec3 } from 'gl-matrix';
|
|
2
3
|
import { getEnabledElement, VolumeViewport, utilities as csUtils, getEnabledElementByViewportId, EPSILON, } from '@cornerstonejs/core';
|
|
3
|
-
import {
|
|
4
|
+
import { getCalibratedLengthUnitsAndScale } from '../../utilities/getCalibratedUnits';
|
|
4
5
|
import throttle from '../../utilities/throttle';
|
|
5
6
|
import { addAnnotation, getAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
|
|
6
7
|
import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
|
|
@@ -8,10 +9,9 @@ import { isAnnotationVisible } from '../../stateManagement/annotation/annotation
|
|
|
8
9
|
import { triggerAnnotationCompleted, triggerAnnotationModified, } from '../../stateManagement/annotation/helpers/state';
|
|
9
10
|
import { drawCircle as drawCircleSvg, drawHandles as drawHandlesSvg, drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg';
|
|
10
11
|
import { state } from '../../store/state';
|
|
11
|
-
import { ChangeTypes, Events } from '../../enums';
|
|
12
|
+
import { ChangeTypes, Events, MeasurementType } from '../../enums';
|
|
12
13
|
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
13
14
|
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
14
|
-
import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints';
|
|
15
15
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
16
16
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
17
17
|
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
@@ -19,7 +19,6 @@ import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScale
|
|
|
19
19
|
import { getCanvasCircleCorners, getCanvasCircleRadius, } from '../../utilities/math/circle';
|
|
20
20
|
import { pointInEllipse } from '../../utilities/math/ellipse';
|
|
21
21
|
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
22
|
-
import { vec2, vec3 } from 'gl-matrix';
|
|
23
22
|
import { getStyleProperty } from '../../stateManagement/annotation/config/helpers';
|
|
24
23
|
const { transformWorldToIndex } = csUtils;
|
|
25
24
|
class CircleROITool extends AnnotationTool {
|
|
@@ -473,25 +472,22 @@ class CircleROITool extends AnnotationTool {
|
|
|
473
472
|
}
|
|
474
473
|
return renderStatus;
|
|
475
474
|
};
|
|
476
|
-
this._calculateCachedStats = (annotation, viewport,
|
|
475
|
+
this._calculateCachedStats = (annotation, viewport, _renderingEngine, _enabledElement) => {
|
|
477
476
|
if (!this.configuration.calculateStats) {
|
|
478
477
|
return;
|
|
479
478
|
}
|
|
480
|
-
const data = annotation
|
|
479
|
+
const { data } = annotation;
|
|
481
480
|
const { element } = viewport;
|
|
482
481
|
const wasInvalidated = annotation.invalidated;
|
|
483
482
|
const { points } = data.handles;
|
|
484
483
|
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
485
484
|
const canvasCenter = canvasCoordinates[0];
|
|
486
485
|
const canvasTop = canvasCoordinates[1];
|
|
487
|
-
const { viewPlaneNormal, viewUp } = viewport.getCamera();
|
|
488
486
|
const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners([canvasCenter, canvasTop]));
|
|
489
487
|
const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
|
|
490
488
|
const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
|
|
491
489
|
const { cachedStats } = data;
|
|
492
490
|
const targetIds = Object.keys(cachedStats);
|
|
493
|
-
const worldPos1 = topLeftWorld;
|
|
494
|
-
const worldPos2 = bottomRightWorld;
|
|
495
491
|
for (let i = 0; i < targetIds.length; i++) {
|
|
496
492
|
const targetId = targetIds[i];
|
|
497
493
|
const image = this.getTargetImageData(targetId);
|
|
@@ -499,15 +495,49 @@ class CircleROITool extends AnnotationTool {
|
|
|
499
495
|
continue;
|
|
500
496
|
}
|
|
501
497
|
const { dimensions, imageData, metadata, voxelManager } = image;
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
498
|
+
const handles = points.map((point) => imageData.worldToIndex(point));
|
|
499
|
+
const calibrate = getCalibratedLengthUnitsAndScale(image, handles);
|
|
500
|
+
const radius = CircleROITool.calculateLengthInIndex(calibrate, handles);
|
|
501
|
+
const area = Math.PI * radius * radius;
|
|
502
|
+
const perimeter = 2 * Math.PI * radius;
|
|
503
|
+
const isEmptyArea = radius === 0;
|
|
504
|
+
const { unit, areaUnit } = calibrate;
|
|
505
|
+
const namedArea = {
|
|
506
|
+
name: 'area',
|
|
507
|
+
value: area,
|
|
508
|
+
unit: areaUnit,
|
|
509
|
+
type: MeasurementType.Area,
|
|
510
|
+
};
|
|
511
|
+
const namedCircumference = {
|
|
512
|
+
name: 'circumference',
|
|
513
|
+
value: perimeter,
|
|
514
|
+
unit,
|
|
515
|
+
type: MeasurementType.Linear,
|
|
516
|
+
};
|
|
517
|
+
const namedRadius = {
|
|
518
|
+
name: 'radius',
|
|
519
|
+
value: radius,
|
|
520
|
+
unit,
|
|
521
|
+
type: MeasurementType.Linear,
|
|
522
|
+
};
|
|
523
|
+
const statsArray = [namedArea, namedRadius, namedCircumference];
|
|
524
|
+
cachedStats[targetId] = {
|
|
525
|
+
Modality: metadata.Modality,
|
|
526
|
+
area,
|
|
527
|
+
isEmptyArea,
|
|
528
|
+
areaUnit,
|
|
529
|
+
radius,
|
|
530
|
+
radiusUnit: unit,
|
|
531
|
+
perimeter,
|
|
532
|
+
statsArray,
|
|
533
|
+
};
|
|
534
|
+
const pos1Index = transformWorldToIndex(imageData, topLeftWorld);
|
|
535
|
+
const pos2Index = transformWorldToIndex(imageData, bottomRightWorld);
|
|
536
|
+
this.isHandleOutsideImage = !BaseTool.isInsideVolume(dimensions, [
|
|
537
|
+
pos1Index,
|
|
538
|
+
pos2Index,
|
|
539
|
+
]);
|
|
540
|
+
if (!this.isHandleOutsideImage) {
|
|
511
541
|
const iMin = Math.min(pos1Index[0], pos2Index[0]);
|
|
512
542
|
const iMax = Math.max(pos1Index[0], pos2Index[0]);
|
|
513
543
|
const jMin = Math.min(pos1Index[1], pos2Index[1]);
|
|
@@ -529,14 +559,6 @@ class CircleROITool extends AnnotationTool {
|
|
|
529
559
|
yRadius: yRadius < EPSILON / 2 ? 0 : yRadius,
|
|
530
560
|
zRadius: zRadius < EPSILON / 2 ? 0 : zRadius,
|
|
531
561
|
};
|
|
532
|
-
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(viewPlaneNormal, viewUp, worldPos1, worldPos2);
|
|
533
|
-
const isEmptyArea = worldWidth === 0 && worldHeight === 0;
|
|
534
|
-
const handles = [pos1Index, pos2Index];
|
|
535
|
-
const { scale, unit, areaUnit } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
536
|
-
const aspect = getCalibratedAspect(image);
|
|
537
|
-
const area = Math.abs(Math.PI *
|
|
538
|
-
(worldWidth / scale / 2) *
|
|
539
|
-
(worldHeight / aspect / scale / 2));
|
|
540
562
|
const pixelUnitsOptions = {
|
|
541
563
|
isPreScaled: isViewportPreScaled(viewport, targetId),
|
|
542
564
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
@@ -553,26 +575,15 @@ class CircleROITool extends AnnotationTool {
|
|
|
553
575
|
}
|
|
554
576
|
const stats = this.configuration.statsCalculator.getStatistics();
|
|
555
577
|
cachedStats[targetId] = {
|
|
578
|
+
...cachedStats[targetId],
|
|
556
579
|
Modality: metadata.Modality,
|
|
557
|
-
area,
|
|
558
580
|
mean: stats.mean?.value,
|
|
559
581
|
max: stats.max?.value,
|
|
560
582
|
min: stats.min?.value,
|
|
561
583
|
pointsInShape,
|
|
562
584
|
stdDev: stats.stdDev?.value,
|
|
563
|
-
statsArray: stats.array,
|
|
564
|
-
isEmptyArea,
|
|
565
|
-
areaUnit,
|
|
566
|
-
radius: worldWidth / 2 / scale,
|
|
567
|
-
radiusUnit: unit,
|
|
568
|
-
perimeter: (2 * Math.PI * (worldWidth / 2)) / scale,
|
|
569
585
|
modalityUnit,
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
this.isHandleOutsideImage = true;
|
|
574
|
-
cachedStats[targetId] = {
|
|
575
|
-
Modality: metadata.Modality,
|
|
586
|
+
statsArray: [...statsArray, ...stats.array],
|
|
576
587
|
};
|
|
577
588
|
}
|
|
578
589
|
}
|
|
@@ -582,10 +593,6 @@ class CircleROITool extends AnnotationTool {
|
|
|
582
593
|
}
|
|
583
594
|
return cachedStats;
|
|
584
595
|
};
|
|
585
|
-
this._isInsideVolume = (index1, index2, dimensions) => {
|
|
586
|
-
return (csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
587
|
-
csUtils.indexWithinDimensions(index2, dimensions));
|
|
588
|
-
};
|
|
589
596
|
this._throttledCalculateCachedStats = throttle(this._calculateCachedStats, 100, { trailing: true });
|
|
590
597
|
}
|
|
591
598
|
static { this.hydrate = (viewportId, points, options) => {
|
|
@@ -568,7 +568,7 @@ class EllipticalROITool extends AnnotationTool {
|
|
|
568
568
|
const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(viewPlaneNormal, viewUp, worldPos1, worldPos2);
|
|
569
569
|
const isEmptyArea = worldWidth === 0 && worldHeight === 0;
|
|
570
570
|
const handles = [pos1Index, pos2Index];
|
|
571
|
-
const { scale,
|
|
571
|
+
const { scale, areaUnit } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
572
572
|
const aspect = getCalibratedAspect(image);
|
|
573
573
|
const area = Math.abs(Math.PI *
|
|
574
574
|
(worldWidth / scale / 2) *
|
|
@@ -35,8 +35,6 @@ declare class LengthTool extends AnnotationTool {
|
|
|
35
35
|
_activateDraw: (element: HTMLDivElement) => void;
|
|
36
36
|
_deactivateDraw: (element: HTMLDivElement) => void;
|
|
37
37
|
renderAnnotation: (enabledElement: Types.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean;
|
|
38
|
-
_calculateLength(pos1: any, pos2: any): number;
|
|
39
38
|
_calculateCachedStats(annotation: any, renderingEngine: any, enabledElement: any): any;
|
|
40
|
-
_isInsideVolume(index1: any, index2: any, dimensions: any): boolean;
|
|
41
39
|
}
|
|
42
40
|
export default LengthTool;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Events, ChangeTypes } from '../../enums';
|
|
1
|
+
import { Events, ChangeTypes, MeasurementType } from '../../enums';
|
|
2
2
|
import { getEnabledElement, utilities as csUtils, utilities, getEnabledElementByViewportId, } from '@cornerstonejs/core';
|
|
3
3
|
import { getCalibratedLengthUnitsAndScale } from '../../utilities/getCalibratedUnits';
|
|
4
4
|
import { AnnotationTool } from '../base';
|
|
@@ -15,7 +15,6 @@ import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
|
15
15
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
16
16
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
17
17
|
import { getStyleProperty } from '../../stateManagement/annotation/config/helpers';
|
|
18
|
-
const { transformWorldToIndex } = csUtils;
|
|
19
18
|
class LengthTool extends AnnotationTool {
|
|
20
19
|
static { this.toolName = 'Length'; }
|
|
21
20
|
constructor(toolProps = {}, defaultToolProps = {
|
|
@@ -384,17 +383,9 @@ class LengthTool extends AnnotationTool {
|
|
|
384
383
|
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
385
384
|
evt.preventDefault();
|
|
386
385
|
}
|
|
387
|
-
_calculateLength(pos1, pos2) {
|
|
388
|
-
const dx = pos1[0] - pos2[0];
|
|
389
|
-
const dy = pos1[1] - pos2[1];
|
|
390
|
-
const dz = pos1[2] - pos2[2];
|
|
391
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
392
|
-
}
|
|
393
386
|
_calculateCachedStats(annotation, renderingEngine, enabledElement) {
|
|
394
387
|
const data = annotation.data;
|
|
395
388
|
const { element } = enabledElement.viewport;
|
|
396
|
-
const worldPos1 = data.handles.points[0];
|
|
397
|
-
const worldPos2 = data.handles.points[1];
|
|
398
389
|
const { cachedStats } = data;
|
|
399
390
|
const targetIds = Object.keys(cachedStats);
|
|
400
391
|
for (let i = 0; i < targetIds.length; i++) {
|
|
@@ -404,20 +395,21 @@ class LengthTool extends AnnotationTool {
|
|
|
404
395
|
continue;
|
|
405
396
|
}
|
|
406
397
|
const { imageData, dimensions } = image;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
398
|
+
const handles = data.handles.points.map((point) => imageData.worldToIndex(point));
|
|
399
|
+
const calibrate = getCalibratedLengthUnitsAndScale(image, handles);
|
|
400
|
+
const { unit } = calibrate;
|
|
401
|
+
const length = LengthTool.calculateLengthInIndex(calibrate, handles);
|
|
402
|
+
this.isHandleOutsideImage = !LengthTool.isInsideVolume(dimensions, handles);
|
|
403
|
+
const namedLength = {
|
|
404
|
+
name: 'length',
|
|
405
|
+
value: length,
|
|
406
|
+
unit,
|
|
407
|
+
type: MeasurementType.Linear,
|
|
408
|
+
};
|
|
418
409
|
cachedStats[targetId] = {
|
|
419
410
|
length,
|
|
420
411
|
unit,
|
|
412
|
+
statsArray: [namedLength],
|
|
421
413
|
};
|
|
422
414
|
}
|
|
423
415
|
const invalidated = annotation.invalidated;
|
|
@@ -427,10 +419,6 @@ class LengthTool extends AnnotationTool {
|
|
|
427
419
|
}
|
|
428
420
|
return cachedStats;
|
|
429
421
|
}
|
|
430
|
-
_isInsideVolume(index1, index2, dimensions) {
|
|
431
|
-
return (csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
432
|
-
csUtils.indexWithinDimensions(index2, dimensions));
|
|
433
|
-
}
|
|
434
422
|
}
|
|
435
423
|
function defaultGetTextLines(data, targetId) {
|
|
436
424
|
const cachedVolumeStats = data.cachedStats[targetId];
|
|
@@ -48,12 +48,13 @@ declare class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
48
48
|
deltaInX: any;
|
|
49
49
|
deltaInY: any;
|
|
50
50
|
}): void;
|
|
51
|
-
protected updateOpenCachedStats({ targetId, metadata, cachedStats, modalityUnit, calibratedScale, points, }: {
|
|
51
|
+
protected updateOpenCachedStats({ targetId, metadata, cachedStats, modalityUnit, calibratedScale, imageData, points, }: {
|
|
52
52
|
targetId: any;
|
|
53
53
|
metadata: any;
|
|
54
54
|
cachedStats: any;
|
|
55
55
|
modalityUnit: any;
|
|
56
56
|
calibratedScale: any;
|
|
57
|
+
imageData: any;
|
|
57
58
|
points: any;
|
|
58
59
|
}): void;
|
|
59
60
|
private _renderStats;
|
|
@@ -19,9 +19,8 @@ import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
|
19
19
|
import { getLineSegmentIntersectionsCoordinates } from '../../utilities/math/polyline';
|
|
20
20
|
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
21
21
|
import { BasicStatsCalculator } from '../../utilities/math/basic';
|
|
22
|
-
import calculatePerimeter from '../../utilities/contours/calculatePerimeter';
|
|
23
22
|
import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
|
|
24
|
-
import { KeyboardBindings, ChangeTypes } from '../../enums';
|
|
23
|
+
import { KeyboardBindings, ChangeTypes, MeasurementType } from '../../enums';
|
|
25
24
|
import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
|
|
26
25
|
const { pointCanProjectOnLine } = polyline;
|
|
27
26
|
const { EPSILON } = CONSTANTS;
|
|
@@ -150,23 +149,22 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
150
149
|
isSuvScaled: this.isSuvScaled(viewport, targetId, annotation.metadata.referencedImageId),
|
|
151
150
|
};
|
|
152
151
|
const modalityUnit = getPixelValueUnits(metadata.Modality, annotation.metadata.referencedImageId, modalityUnitOptions);
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
152
|
+
const polyline = data.contour.polyline;
|
|
153
|
+
const numPoints = polyline.length;
|
|
154
|
+
const projectedPolyline = new Array(numPoints);
|
|
155
|
+
for (let i = 0; i < numPoints; i++) {
|
|
156
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
157
|
+
}
|
|
158
|
+
const { maxX: canvasMaxX, maxY: canvasMaxY, minX: canvasMinX, minY: canvasMinY, } = math.polyline.getAABB(projectedPolyline);
|
|
159
|
+
const topLeftBBWorld = viewport.canvasToWorld([canvasMinX, canvasMinY]);
|
|
160
|
+
const topLeftBBIndex = csUtils.transformWorldToIndex(imageData, topLeftBBWorld);
|
|
161
|
+
const bottomRightBBWorld = viewport.canvasToWorld([
|
|
162
|
+
canvasMaxX,
|
|
163
|
+
canvasMaxY,
|
|
164
|
+
]);
|
|
165
|
+
const bottomRightBBIndex = csUtils.transformWorldToIndex(imageData, bottomRightBBWorld);
|
|
166
|
+
const handles = [topLeftBBIndex, bottomRightBBIndex];
|
|
167
|
+
const calibratedScale = getCalibratedLengthUnitsAndScale(image, handles);
|
|
170
168
|
const canvasPoint = canvasCoordinates[0];
|
|
171
169
|
const originalWorldPoint = viewport.canvasToWorld(canvasPoint);
|
|
172
170
|
const deltaXPoint = viewport.canvasToWorld([
|
|
@@ -179,30 +177,24 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
179
177
|
]);
|
|
180
178
|
const deltaInX = vec3.distance(originalWorldPoint, deltaXPoint);
|
|
181
179
|
const deltaInY = vec3.distance(originalWorldPoint, deltaYPoint);
|
|
180
|
+
const statsArgs = {
|
|
181
|
+
targetId,
|
|
182
|
+
viewport,
|
|
183
|
+
canvasCoordinates,
|
|
184
|
+
points,
|
|
185
|
+
imageData,
|
|
186
|
+
metadata,
|
|
187
|
+
cachedStats,
|
|
188
|
+
modalityUnit,
|
|
189
|
+
calibratedScale,
|
|
190
|
+
deltaInX,
|
|
191
|
+
deltaInY,
|
|
192
|
+
};
|
|
182
193
|
if (closed) {
|
|
183
|
-
this.updateClosedCachedStats(
|
|
184
|
-
targetId,
|
|
185
|
-
viewport,
|
|
186
|
-
canvasCoordinates,
|
|
187
|
-
points,
|
|
188
|
-
imageData,
|
|
189
|
-
metadata,
|
|
190
|
-
cachedStats,
|
|
191
|
-
modalityUnit,
|
|
192
|
-
calibratedScale,
|
|
193
|
-
deltaInX,
|
|
194
|
-
deltaInY,
|
|
195
|
-
});
|
|
194
|
+
this.updateClosedCachedStats(statsArgs);
|
|
196
195
|
}
|
|
197
196
|
else {
|
|
198
|
-
this.updateOpenCachedStats(
|
|
199
|
-
metadata,
|
|
200
|
-
targetId,
|
|
201
|
-
cachedStats,
|
|
202
|
-
modalityUnit,
|
|
203
|
-
calibratedScale,
|
|
204
|
-
points,
|
|
205
|
-
});
|
|
197
|
+
this.updateOpenCachedStats(statsArgs);
|
|
206
198
|
}
|
|
207
199
|
}
|
|
208
200
|
const invalidated = annotation.invalidated;
|
|
@@ -432,21 +424,15 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
432
424
|
updateClosedCachedStats({ viewport, points, imageData, metadata, cachedStats, targetId, modalityUnit, canvasCoordinates, calibratedScale, deltaInX, deltaInY, }) {
|
|
433
425
|
const { scale, areaUnit, unit } = calibratedScale;
|
|
434
426
|
const { voxelManager } = viewport.getImageData();
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let
|
|
440
|
-
let
|
|
441
|
-
let
|
|
442
|
-
let
|
|
443
|
-
|
|
444
|
-
let kMax = worldPosIndex[2];
|
|
445
|
-
for (let j = 1; j < points.length; j++) {
|
|
446
|
-
const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[j]);
|
|
447
|
-
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
448
|
-
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
449
|
-
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
427
|
+
const indexPoints = points.map((point) => imageData.worldToIndex(point));
|
|
428
|
+
let iMin = Number.MAX_SAFE_INTEGER;
|
|
429
|
+
let iMax = Number.MIN_SAFE_INTEGER;
|
|
430
|
+
let jMin = Number.MAX_SAFE_INTEGER;
|
|
431
|
+
let jMax = Number.MIN_SAFE_INTEGER;
|
|
432
|
+
let kMin = Number.MAX_SAFE_INTEGER;
|
|
433
|
+
let kMax = Number.MIN_SAFE_INTEGER;
|
|
434
|
+
for (let j = 0; j < points.length; j++) {
|
|
435
|
+
const worldPosIndex = indexPoints[j].map(Math.floor);
|
|
450
436
|
iMin = Math.min(iMin, worldPosIndex[0]);
|
|
451
437
|
iMax = Math.max(iMax, worldPosIndex[0]);
|
|
452
438
|
jMin = Math.min(jMin, worldPosIndex[1]);
|
|
@@ -454,13 +440,9 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
454
440
|
kMin = Math.min(kMin, worldPosIndex[2]);
|
|
455
441
|
kMax = Math.max(kMax, worldPosIndex[2]);
|
|
456
442
|
}
|
|
457
|
-
const worldPosIndex2 = csUtils.transformWorldToIndex(imageData, points[1]);
|
|
458
|
-
worldPosIndex2[0] = Math.floor(worldPosIndex2[0]);
|
|
459
|
-
worldPosIndex2[1] = Math.floor(worldPosIndex2[1]);
|
|
460
|
-
worldPosIndex2[2] = Math.floor(worldPosIndex2[2]);
|
|
461
443
|
let area = polyline.getArea(canvasCoordinates) / scale / scale;
|
|
462
444
|
area *= deltaInX * deltaInY;
|
|
463
|
-
const perimeter =
|
|
445
|
+
const perimeter = PlanarFreehandROITool.calculateLengthInIndex(calibratedScale, indexPoints, closed);
|
|
464
446
|
const iDelta = 0.01 * (iMax - iMin);
|
|
465
447
|
const jDelta = 0.01 * (jMax - jMin);
|
|
466
448
|
const kDelta = 0.01 * (kMax - kMin);
|
|
@@ -515,6 +497,18 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
515
497
|
});
|
|
516
498
|
}
|
|
517
499
|
const stats = this.configuration.statsCalculator.getStatistics();
|
|
500
|
+
const namedArea = {
|
|
501
|
+
name: 'area',
|
|
502
|
+
value: area,
|
|
503
|
+
unit: areaUnit,
|
|
504
|
+
type: MeasurementType.Area,
|
|
505
|
+
};
|
|
506
|
+
const namedPerimeter = {
|
|
507
|
+
name: 'perimeter',
|
|
508
|
+
value: perimeter,
|
|
509
|
+
unit,
|
|
510
|
+
type: MeasurementType.Linear,
|
|
511
|
+
};
|
|
518
512
|
cachedStats[targetId] = {
|
|
519
513
|
Modality: metadata.Modality,
|
|
520
514
|
area,
|
|
@@ -523,21 +517,29 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
|
|
|
523
517
|
max: stats.max?.value,
|
|
524
518
|
min: stats.min?.value,
|
|
525
519
|
stdDev: stats.stdDev?.value,
|
|
526
|
-
statsArray: stats.array,
|
|
520
|
+
statsArray: [namedArea, namedPerimeter, ...stats.array],
|
|
527
521
|
pointsInShape: pointsInShape,
|
|
528
522
|
areaUnit,
|
|
529
523
|
modalityUnit,
|
|
530
524
|
unit,
|
|
531
525
|
};
|
|
532
526
|
}
|
|
533
|
-
updateOpenCachedStats({ targetId, metadata, cachedStats, modalityUnit, calibratedScale, points, }) {
|
|
534
|
-
const {
|
|
535
|
-
const
|
|
527
|
+
updateOpenCachedStats({ targetId, metadata, cachedStats, modalityUnit, calibratedScale, imageData, points, }) {
|
|
528
|
+
const { unit } = calibratedScale;
|
|
529
|
+
const indexPoints = points.map((point) => imageData.worldToIndex(point));
|
|
530
|
+
const length = PlanarFreehandROITool.calculateLengthInIndex(calibratedScale, indexPoints);
|
|
531
|
+
const namedLength = {
|
|
532
|
+
name: 'length',
|
|
533
|
+
value: length,
|
|
534
|
+
unit,
|
|
535
|
+
type: MeasurementType.Linear,
|
|
536
|
+
};
|
|
536
537
|
cachedStats[targetId] = {
|
|
537
538
|
Modality: metadata.Modality,
|
|
538
539
|
length,
|
|
539
540
|
modalityUnit,
|
|
540
541
|
unit,
|
|
542
|
+
statArray: [namedLength],
|
|
541
543
|
};
|
|
542
544
|
}
|
|
543
545
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { utilities } from '@cornerstonejs/core';
|
|
1
|
+
import { utilities as csUtils } from '@cornerstonejs/core';
|
|
2
2
|
import type { Types } from '@cornerstonejs/core';
|
|
3
3
|
import ToolModes from '../../enums/ToolModes';
|
|
4
4
|
import type StrategyCallbacks from '../../enums/StrategyCallbacks';
|
|
@@ -9,7 +9,7 @@ declare abstract class BaseTool {
|
|
|
9
9
|
configuration: Record<string, any>;
|
|
10
10
|
toolGroupId: string;
|
|
11
11
|
mode: ToolModes;
|
|
12
|
-
protected memo:
|
|
12
|
+
protected memo: csUtils.HistoryMemo.Memo;
|
|
13
13
|
static defaults: {
|
|
14
14
|
configuration: {
|
|
15
15
|
strategies: {};
|
|
@@ -36,5 +36,7 @@ declare abstract class BaseTool {
|
|
|
36
36
|
doneEditMemo(): void;
|
|
37
37
|
static startGroupRecording(): void;
|
|
38
38
|
static endGroupRecording(): void;
|
|
39
|
+
static calculateLengthInIndex(calibrate: any, indexPoints: any, closed?: boolean): number;
|
|
40
|
+
static isInsideVolume(dimensions: any, indexPoints: any): boolean;
|
|
39
41
|
}
|
|
40
42
|
export default BaseTool;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { utilities } from '@cornerstonejs/core';
|
|
1
|
+
import { utilities as csUtils } from '@cornerstonejs/core';
|
|
2
2
|
import ToolModes from '../../enums/ToolModes';
|
|
3
|
-
const { DefaultHistoryMemo } =
|
|
3
|
+
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
|
4
4
|
class BaseTool {
|
|
5
5
|
static { this.defaults = {
|
|
6
6
|
configuration: {
|
|
@@ -12,7 +12,7 @@ class BaseTool {
|
|
|
12
12
|
}; }
|
|
13
13
|
constructor(toolProps, defaultToolProps) {
|
|
14
14
|
const mergedDefaults = BaseTool.mergeDefaultProps(BaseTool.defaults, defaultToolProps);
|
|
15
|
-
const initialProps =
|
|
15
|
+
const initialProps = csUtils.deepMerge(mergedDefaults, toolProps);
|
|
16
16
|
const { configuration = {}, supportedInteractionTypes, toolGroupId, } = initialProps;
|
|
17
17
|
this.toolGroupId = toolGroupId;
|
|
18
18
|
this.supportedInteractionTypes = supportedInteractionTypes || [];
|
|
@@ -23,7 +23,7 @@ class BaseTool {
|
|
|
23
23
|
if (!additionalProps) {
|
|
24
24
|
return defaultProps;
|
|
25
25
|
}
|
|
26
|
-
return
|
|
26
|
+
return csUtils.deepMerge(defaultProps, additionalProps);
|
|
27
27
|
}
|
|
28
28
|
get toolName() {
|
|
29
29
|
return this.getToolName();
|
|
@@ -43,7 +43,7 @@ class BaseTool {
|
|
|
43
43
|
return strategies[activeStrategy][callbackType]?.call(this, enabledElement, operationData, ...extraArgs);
|
|
44
44
|
}
|
|
45
45
|
setConfiguration(newConfiguration) {
|
|
46
|
-
this.configuration =
|
|
46
|
+
this.configuration = csUtils.deepMerge(this.configuration, newConfiguration);
|
|
47
47
|
}
|
|
48
48
|
setActiveStrategy(strategyName) {
|
|
49
49
|
this.setConfiguration({ activeStrategy: strategyName });
|
|
@@ -51,8 +51,8 @@ class BaseTool {
|
|
|
51
51
|
getTargetImageData(targetId) {
|
|
52
52
|
if (targetId.startsWith('imageId:')) {
|
|
53
53
|
const imageId = targetId.split('imageId:')[1];
|
|
54
|
-
const imageURI =
|
|
55
|
-
let viewports =
|
|
54
|
+
const imageURI = csUtils.imageIdToURI(imageId);
|
|
55
|
+
let viewports = csUtils.getViewportsWithImageURI(imageURI);
|
|
56
56
|
if (!viewports || !viewports.length) {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
@@ -65,16 +65,16 @@ class BaseTool {
|
|
|
65
65
|
return viewports[0].getImageData();
|
|
66
66
|
}
|
|
67
67
|
else if (targetId.startsWith('volumeId:')) {
|
|
68
|
-
const volumeId =
|
|
69
|
-
const viewports =
|
|
68
|
+
const volumeId = csUtils.getVolumeId(targetId);
|
|
69
|
+
const viewports = csUtils.getViewportsWithVolumeId(volumeId);
|
|
70
70
|
if (!viewports || !viewports.length) {
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
return viewports[0].getImageData();
|
|
74
74
|
}
|
|
75
75
|
else if (targetId.startsWith('videoId:')) {
|
|
76
|
-
const imageURI =
|
|
77
|
-
const viewports =
|
|
76
|
+
const imageURI = csUtils.imageIdToURI(targetId);
|
|
77
|
+
const viewports = csUtils.getViewportsWithImageURI(imageURI);
|
|
78
78
|
if (!viewports || !viewports.length) {
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
@@ -129,6 +129,33 @@ class BaseTool {
|
|
|
129
129
|
static endGroupRecording() {
|
|
130
130
|
DefaultHistoryMemo.endGroupRecording();
|
|
131
131
|
}
|
|
132
|
+
static calculateLengthInIndex(calibrate, indexPoints, closed = false) {
|
|
133
|
+
const scale = calibrate?.scale || 1;
|
|
134
|
+
const scaleY = calibrate?.scaleY || scale;
|
|
135
|
+
const scaleZ = calibrate?.scaleZ || scale;
|
|
136
|
+
let length = 0;
|
|
137
|
+
const count = indexPoints.length;
|
|
138
|
+
const start = closed ? 0 : 1;
|
|
139
|
+
let lastPoint = closed ? indexPoints[count - 1] : indexPoints[0];
|
|
140
|
+
for (let i = start; i < count; i++) {
|
|
141
|
+
const point = indexPoints[i];
|
|
142
|
+
const dx = (point[0] - lastPoint[0]) / scale;
|
|
143
|
+
const dy = (point[1] - lastPoint[1]) / scaleY;
|
|
144
|
+
const dz = (point[2] - lastPoint[2]) / scaleZ;
|
|
145
|
+
length += Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
146
|
+
lastPoint = point;
|
|
147
|
+
}
|
|
148
|
+
return length;
|
|
149
|
+
}
|
|
150
|
+
static isInsideVolume(dimensions, indexPoints) {
|
|
151
|
+
const { length: count } = indexPoints;
|
|
152
|
+
for (let i = 0; i < count; i++) {
|
|
153
|
+
if (!csUtils.indexWithinDimensions(indexPoints[i], dimensions)) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
132
159
|
}
|
|
133
160
|
BaseTool.toolName = 'BaseTool';
|
|
134
161
|
export default BaseTool;
|
|
@@ -418,7 +418,10 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
|
|
|
418
418
|
worldPos2Index[2] = Math.floor(worldPos2Index[2]);
|
|
419
419
|
worldPos2Index[indexOfProjection] =
|
|
420
420
|
worldProjectionPointIndex[indexOfProjection];
|
|
421
|
-
if (
|
|
421
|
+
if (CircleROITool.isInsideVolume(dimensions, [
|
|
422
|
+
worldPos1Index,
|
|
423
|
+
worldPos2Index,
|
|
424
|
+
])) {
|
|
422
425
|
const iMin = Math.min(worldPos1Index[0], worldPos2Index[0]);
|
|
423
426
|
const iMax = Math.max(worldPos1Index[0], worldPos2Index[0]);
|
|
424
427
|
const jMin = Math.min(worldPos1Index[1], worldPos2Index[1]);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
|
-
type
|
|
2
|
+
import type { MeasurementType } from '../enums/MeasurementType';
|
|
3
|
+
export type Statistics = {
|
|
3
4
|
name: string;
|
|
4
5
|
label?: string;
|
|
5
6
|
value: number | number[];
|
|
6
7
|
unit: null | string;
|
|
7
8
|
pointIJK?: Types.Point3;
|
|
8
9
|
pointLPS?: Types.Point3;
|
|
10
|
+
type?: MeasurementType;
|
|
9
11
|
};
|
|
10
|
-
type NamedStatistics = {
|
|
12
|
+
export type NamedStatistics = {
|
|
11
13
|
mean: Statistics & {
|
|
12
14
|
name: 'mean';
|
|
13
15
|
};
|
|
@@ -60,4 +62,3 @@ type NamedStatistics = {
|
|
|
60
62
|
}>;
|
|
61
63
|
array: Statistics[];
|
|
62
64
|
};
|
|
63
|
-
export type { Statistics, NamedStatistics };
|
|
@@ -10,6 +10,5 @@ import getContourHolesDataCanvas from './getContourHolesDataCanvas';
|
|
|
10
10
|
import updateContourPolyline from './updateContourPolyline';
|
|
11
11
|
import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
|
|
12
12
|
import findHandlePolylineIndex from './findHandlePolylineIndex';
|
|
13
|
-
import calculatePerimeter from './calculatePerimeter';
|
|
14
13
|
import findIslands from './findIslands';
|
|
15
|
-
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex,
|
|
14
|
+
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex, findIslands, };
|
|
@@ -10,6 +10,5 @@ import getContourHolesDataCanvas from './getContourHolesDataCanvas';
|
|
|
10
10
|
import updateContourPolyline from './updateContourPolyline';
|
|
11
11
|
import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
|
|
12
12
|
import findHandlePolylineIndex from './findHandlePolylineIndex';
|
|
13
|
-
import calculatePerimeter from './calculatePerimeter';
|
|
14
13
|
import findIslands from './findIslands';
|
|
15
|
-
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex,
|
|
14
|
+
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex, findIslands, };
|
|
@@ -2,6 +2,8 @@ declare const getCalibratedLengthUnitsAndScale: (image: any, handles: any) => {
|
|
|
2
2
|
unit: string;
|
|
3
3
|
areaUnit: string;
|
|
4
4
|
scale: number;
|
|
5
|
+
scaleY: number;
|
|
6
|
+
scaleZ: number;
|
|
5
7
|
volumeUnit: string;
|
|
6
8
|
};
|
|
7
9
|
declare const getCalibratedProbeUnitsAndValue: (image: any, handles: any) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Enums
|
|
1
|
+
import { Enums } from '@cornerstonejs/core';
|
|
2
2
|
const { CalibrationTypes } = Enums;
|
|
3
3
|
const PIXEL_UNITS = 'px';
|
|
4
4
|
const VOXEL_UNITS = 'voxels';
|
|
@@ -8,10 +8,6 @@ const SUPPORTED_REGION_DATA_TYPES = [
|
|
|
8
8
|
3,
|
|
9
9
|
4,
|
|
10
10
|
];
|
|
11
|
-
const SUPPORTED_LENGTH_VARIANT = [
|
|
12
|
-
'3,3',
|
|
13
|
-
'4,7',
|
|
14
|
-
];
|
|
15
11
|
const SUPPORTED_PROBE_VARIANT = [
|
|
16
12
|
'4,3',
|
|
17
13
|
'4,7',
|
|
@@ -31,94 +27,67 @@ const UNIT_MAPPING = {
|
|
|
31
27
|
};
|
|
32
28
|
const EPS = 1e-3;
|
|
33
29
|
const SQUARE = '\xb2';
|
|
30
|
+
const types = [
|
|
31
|
+
CalibrationTypes.ERMF,
|
|
32
|
+
CalibrationTypes.USER,
|
|
33
|
+
CalibrationTypes.ERROR,
|
|
34
|
+
CalibrationTypes.PROJECTION,
|
|
35
|
+
CalibrationTypes.CALIBRATED,
|
|
36
|
+
CalibrationTypes.UNKNOWN,
|
|
37
|
+
];
|
|
34
38
|
const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
35
|
-
const { calibration, hasPixelSpacing } = image;
|
|
39
|
+
const { calibration, hasPixelSpacing, spacing = [1, 1, 1] } = image;
|
|
36
40
|
let unit = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
|
|
37
41
|
const volumeUnit = hasPixelSpacing ? 'mm\xb3' : VOXEL_UNITS;
|
|
38
42
|
let areaUnit = unit + SQUARE;
|
|
39
|
-
|
|
43
|
+
const baseScale = calibration?.scale || 1;
|
|
44
|
+
let scale = baseScale / (calibration?.columnPixelSpacing || spacing[0]);
|
|
45
|
+
let scaleY = baseScale / (calibration?.rowPixelSpacing || spacing[1]);
|
|
46
|
+
let scaleZ = baseScale / spacing[2];
|
|
40
47
|
let calibrationType = '';
|
|
41
48
|
if (!calibration ||
|
|
42
49
|
(!calibration.type && !calibration.sequenceOfUltrasoundRegions)) {
|
|
43
|
-
return { unit, areaUnit, scale, volumeUnit };
|
|
50
|
+
return { unit, areaUnit, scale, scaleY, scaleZ, volumeUnit };
|
|
51
|
+
}
|
|
52
|
+
if (types.includes(calibration?.type)) {
|
|
53
|
+
calibrationType = calibration.type;
|
|
44
54
|
}
|
|
45
55
|
if (calibration.type === CalibrationTypes.UNCALIBRATED) {
|
|
46
56
|
return {
|
|
47
57
|
unit: PIXEL_UNITS,
|
|
48
58
|
areaUnit: PIXEL_UNITS + SQUARE,
|
|
49
59
|
scale,
|
|
60
|
+
scaleY,
|
|
61
|
+
scaleZ,
|
|
50
62
|
volumeUnit: VOXEL_UNITS,
|
|
51
63
|
};
|
|
52
64
|
}
|
|
53
65
|
if (calibration.sequenceOfUltrasoundRegions) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
[
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
let regions = calibration.sequenceOfUltrasoundRegions.filter((region) => imageIndex1[0] >= region.regionLocationMinX0 &&
|
|
64
|
-
imageIndex1[0] <= region.regionLocationMaxX1 &&
|
|
65
|
-
imageIndex1[1] >= region.regionLocationMinY0 &&
|
|
66
|
-
imageIndex1[1] <= region.regionLocationMaxY1 &&
|
|
67
|
-
imageIndex2[0] >= region.regionLocationMinX0 &&
|
|
68
|
-
imageIndex2[0] <= region.regionLocationMaxX1 &&
|
|
69
|
-
imageIndex2[1] >= region.regionLocationMinY0 &&
|
|
70
|
-
imageIndex2[1] <= region.regionLocationMaxY1);
|
|
71
|
-
if (!regions?.length) {
|
|
72
|
-
return { unit, areaUnit, scale, volumeUnit };
|
|
73
|
-
}
|
|
74
|
-
regions = regions.filter((region) => SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
|
|
75
|
-
SUPPORTED_LENGTH_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`));
|
|
76
|
-
if (!regions.length) {
|
|
77
|
-
return {
|
|
78
|
-
unit: PIXEL_UNITS,
|
|
79
|
-
areaUnit: PIXEL_UNITS + SQUARE,
|
|
80
|
-
scale,
|
|
81
|
-
volumeUnit: VOXEL_UNITS,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const region = regions[0];
|
|
85
|
-
const physicalDeltaX = Math.abs(region.physicalDeltaX);
|
|
86
|
-
const physicalDeltaY = Math.abs(region.physicalDeltaY);
|
|
87
|
-
const isSamePhysicalDelta = utilities.isEqual(physicalDeltaX, physicalDeltaY, EPS);
|
|
88
|
-
if (isSamePhysicalDelta) {
|
|
66
|
+
const region = calibration.sequenceOfUltrasoundRegions.find((region) => handles.every((handle) => handle[0] >= region.regionLocationMinX0 &&
|
|
67
|
+
handle[0] <= region.regionLocationMaxX1 &&
|
|
68
|
+
handle[1] >= region.regionLocationMinY0 &&
|
|
69
|
+
handle[1] <= region.regionLocationMaxY1) && SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType));
|
|
70
|
+
if (region &&
|
|
71
|
+
region.physicalUnitsXDirection === region.physicalUnitsYDirection) {
|
|
72
|
+
const physicalDeltaX = Math.abs(region.physicalDeltaX);
|
|
73
|
+
const physicalDeltaY = Math.abs(region.physicalDeltaY);
|
|
89
74
|
scale = 1 / physicalDeltaX;
|
|
75
|
+
scaleY = 1 / physicalDeltaY;
|
|
90
76
|
calibrationType = 'US Region';
|
|
91
77
|
unit = UNIT_MAPPING[region.physicalUnitsXDirection] || 'unknown';
|
|
92
78
|
areaUnit = unit + SQUARE;
|
|
93
79
|
}
|
|
94
|
-
else {
|
|
95
|
-
return {
|
|
96
|
-
unit: PIXEL_UNITS,
|
|
97
|
-
areaUnit: PIXEL_UNITS + SQUARE,
|
|
98
|
-
scale,
|
|
99
|
-
volumeUnit: VOXEL_UNITS,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
80
|
}
|
|
103
81
|
else if (calibration.scale) {
|
|
104
82
|
scale = calibration.scale;
|
|
105
83
|
}
|
|
106
|
-
const types = [
|
|
107
|
-
CalibrationTypes.ERMF,
|
|
108
|
-
CalibrationTypes.USER,
|
|
109
|
-
CalibrationTypes.ERROR,
|
|
110
|
-
CalibrationTypes.PROJECTION,
|
|
111
|
-
CalibrationTypes.CALIBRATED,
|
|
112
|
-
CalibrationTypes.UNKNOWN,
|
|
113
|
-
];
|
|
114
|
-
if (types.includes(calibration?.type)) {
|
|
115
|
-
calibrationType = calibration.type;
|
|
116
|
-
}
|
|
117
84
|
return {
|
|
118
85
|
unit: unit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
119
86
|
areaUnit: areaUnit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
120
|
-
scale,
|
|
121
87
|
volumeUnit: volumeUnit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
88
|
+
scale,
|
|
89
|
+
scaleY,
|
|
90
|
+
scaleZ,
|
|
122
91
|
};
|
|
123
92
|
};
|
|
124
93
|
const getCalibratedProbeUnitsAndValue = (image, handles) => {
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "4.12.
|
|
1
|
+
export declare const version = "4.12.6";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '4.12.
|
|
1
|
+
export const version = '4.12.6';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.6",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"canvas": "3.2.0"
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|
|
111
|
-
"@cornerstonejs/core": "4.12.
|
|
111
|
+
"@cornerstonejs/core": "4.12.6",
|
|
112
112
|
"@kitware/vtk.js": "34.15.1",
|
|
113
113
|
"@types/d3-array": "3.2.1",
|
|
114
114
|
"@types/d3-interpolate": "3.0.4",
|
|
@@ -127,5 +127,5 @@
|
|
|
127
127
|
"type": "individual",
|
|
128
128
|
"url": "https://ohif.org/donate"
|
|
129
129
|
},
|
|
130
|
-
"gitHead": "
|
|
130
|
+
"gitHead": "67da5ec35cb711e4310f0f1971bd8ed090377338"
|
|
131
131
|
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { vec3 } from 'gl-matrix';
|
|
2
|
-
export function calculatePerimeter(polyline, closed) {
|
|
3
|
-
let perimeter = 0;
|
|
4
|
-
for (let i = 0; i < polyline.length - 1; i++) {
|
|
5
|
-
const point1 = polyline[i];
|
|
6
|
-
const point2 = polyline[i + 1];
|
|
7
|
-
perimeter += vec3.dist(point1, point2);
|
|
8
|
-
}
|
|
9
|
-
if (closed) {
|
|
10
|
-
const firstPoint = polyline[0];
|
|
11
|
-
const lastPoint = polyline[polyline.length - 1];
|
|
12
|
-
perimeter += vec3.dist(firstPoint, lastPoint);
|
|
13
|
-
}
|
|
14
|
-
return perimeter;
|
|
15
|
-
}
|
|
16
|
-
export default calculatePerimeter;
|