@cornerstonejs/tools 0.66.7 → 0.67.0
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/cjs/tools/annotation/PlanarFreehandROITool.d.ts +5 -2
- package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +221 -22
- package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/closedContourEditLoop.js +1 -0
- package/dist/cjs/tools/annotation/planarFreehandROITool/closedContourEditLoop.js.map +1 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js +20 -6
- package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEditLoop.js +2 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEditLoop.js.map +1 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js +6 -1
- package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js.map +1 -1
- package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +1 -0
- package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.d.ts +3 -1
- package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.js +51 -1
- package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.js.map +1 -1
- package/dist/cjs/utilities/math/polyline/planarFreehandROIInternalTypes.d.ts +1 -0
- package/dist/cjs/utilities/math/polyline/pointInPolyline.d.ts +2 -0
- package/dist/cjs/utilities/math/polyline/pointInPolyline.js +14 -0
- package/dist/cjs/utilities/math/polyline/pointInPolyline.js.map +1 -0
- package/dist/esm/tools/annotation/PlanarFreehandROITool.d.ts +5 -2
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js +219 -22
- package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/closedContourEditLoop.js +1 -0
- package/dist/esm/tools/annotation/planarFreehandROITool/closedContourEditLoop.js.map +1 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js +20 -6
- package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/openContourEditLoop.js +2 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/openContourEditLoop.js.map +1 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js +6 -1
- package/dist/esm/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js.map +1 -1
- package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +1 -0
- package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.d.ts +3 -1
- package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.js +49 -1
- package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.js.map +1 -1
- package/dist/esm/utilities/math/polyline/planarFreehandROIInternalTypes.d.ts +1 -0
- package/dist/esm/utilities/math/polyline/pointInPolyline.d.ts +2 -0
- package/dist/esm/utilities/math/polyline/pointInPolyline.js +11 -0
- package/dist/esm/utilities/math/polyline/pointInPolyline.js.map +1 -0
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/tools/annotation/PlanarFreehandROITool.ts +356 -36
- package/src/tools/annotation/planarFreehandROITool/closedContourEditLoop.ts +1 -0
- package/src/tools/annotation/planarFreehandROITool/drawLoop.ts +39 -14
- package/src/tools/annotation/planarFreehandROITool/openContourEditLoop.ts +2 -1
- package/src/tools/annotation/planarFreehandROITool/openContourEndEditLoop.ts +14 -2
- package/src/types/ToolSpecificAnnotationTypes.ts +1 -0
- package/src/utilities/math/polyline/getIntersectionWithPolyline.ts +94 -1
- package/src/utilities/math/polyline/planarFreehandROIInternalTypes.ts +1 -0
- package/src/utilities/math/polyline/pointInPolyline.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"main": "dist/umd/index.js",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@cornerstonejs/core": "^0.
|
|
29
|
+
"@cornerstonejs/core": "^0.46.0",
|
|
30
30
|
"lodash.clonedeep": "4.5.0",
|
|
31
31
|
"lodash.get": "^4.4.2"
|
|
32
32
|
},
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"type": "individual",
|
|
50
50
|
"url": "https://ohif.org/donate"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "5856c935d5879c1efa3a50930044d6526ed5c2c2"
|
|
53
53
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../../stateManagement/annotation/annotationState';
|
|
18
18
|
import { polyline } from '../../utilities/math';
|
|
19
19
|
import { filterAnnotationsForDisplay } from '../../utilities/planar';
|
|
20
|
+
import throttle from '../../utilities/throttle';
|
|
20
21
|
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
21
22
|
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
22
23
|
import registerDrawLoop from './planarFreehandROITool/drawLoop';
|
|
@@ -34,13 +35,21 @@ import {
|
|
|
34
35
|
ToolHandle,
|
|
35
36
|
Annotation,
|
|
36
37
|
Annotations,
|
|
38
|
+
AnnotationStyle,
|
|
37
39
|
PublicToolProps,
|
|
38
40
|
ToolProps,
|
|
39
41
|
InteractionTypes,
|
|
40
42
|
SVGDrawingHelper,
|
|
41
43
|
} from '../../types';
|
|
44
|
+
import { drawLine, drawCircle, drawLinkedTextBox } from '../../drawingSvg';
|
|
42
45
|
import { PlanarFreehandROIAnnotation } from '../../types/ToolSpecificAnnotationTypes';
|
|
46
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
43
47
|
import { PlanarFreehandROICommonData } from '../../utilities/math/polyline/planarFreehandROIInternalTypes';
|
|
48
|
+
import pointInPolyline from '../../utilities/math/polyline/pointInPolyline';
|
|
49
|
+
import { getIntersectionCoordinatesWithPolyline } from '../../utilities/math/polyline/getIntersectionWithPolyline';
|
|
50
|
+
import pointInShapeCallback from '../../utilities/pointInShapeCallback';
|
|
51
|
+
import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
|
|
52
|
+
import { getModalityUnit } from '../../utilities/getModalityUnit';
|
|
44
53
|
|
|
45
54
|
const { pointCanProjectOnLine } = polyline;
|
|
46
55
|
const { EPSILON } = CONSTANTS;
|
|
@@ -135,7 +144,8 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
135
144
|
private activateOpenContourEndEdit: (
|
|
136
145
|
evt: EventTypes.InteractionEventType,
|
|
137
146
|
annotation: PlanarFreehandROIAnnotation,
|
|
138
|
-
viewportIdsToRender: string[]
|
|
147
|
+
viewportIdsToRender: string[],
|
|
148
|
+
handle: ToolHandle | null
|
|
139
149
|
) => void;
|
|
140
150
|
private cancelDrawing: (element: HTMLDivElement) => void;
|
|
141
151
|
private cancelClosedContourEdit: (element: HTMLDivElement) => void;
|
|
@@ -195,6 +205,7 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
195
205
|
knotsRatioPercentageOnAdd: 40,
|
|
196
206
|
knotsRatioPercentageOnEdit: 40,
|
|
197
207
|
},
|
|
208
|
+
calculateStats: false,
|
|
198
209
|
},
|
|
199
210
|
}
|
|
200
211
|
) {
|
|
@@ -208,6 +219,12 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
208
219
|
registerOpenContourEditLoop(this);
|
|
209
220
|
registerOpenContourEndEditLoop(this);
|
|
210
221
|
registerRenderMethods(this);
|
|
222
|
+
|
|
223
|
+
this._throttledCalculateCachedStats = throttle(
|
|
224
|
+
this._calculateCachedStats,
|
|
225
|
+
100,
|
|
226
|
+
{ trailing: true }
|
|
227
|
+
);
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
/**
|
|
@@ -268,6 +285,7 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
268
285
|
},
|
|
269
286
|
polyline: [<Types.Point3>[...worldPos]], // Polyline coordinates
|
|
270
287
|
label: '',
|
|
288
|
+
cachedStats: {},
|
|
271
289
|
},
|
|
272
290
|
};
|
|
273
291
|
|
|
@@ -293,7 +311,8 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
293
311
|
*/
|
|
294
312
|
handleSelectedCallback = (
|
|
295
313
|
evt: EventTypes.InteractionEventType,
|
|
296
|
-
annotation: PlanarFreehandROIAnnotation
|
|
314
|
+
annotation: PlanarFreehandROIAnnotation,
|
|
315
|
+
handle: ToolHandle
|
|
297
316
|
): void => {
|
|
298
317
|
const eventDetail = evt.detail;
|
|
299
318
|
const { element } = eventDetail;
|
|
@@ -303,7 +322,12 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
303
322
|
this.getToolName()
|
|
304
323
|
);
|
|
305
324
|
|
|
306
|
-
this.activateOpenContourEndEdit(
|
|
325
|
+
this.activateOpenContourEndEdit(
|
|
326
|
+
evt,
|
|
327
|
+
annotation,
|
|
328
|
+
viewportIdsToRender,
|
|
329
|
+
handle
|
|
330
|
+
);
|
|
307
331
|
};
|
|
308
332
|
|
|
309
333
|
/**
|
|
@@ -555,10 +579,12 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
555
579
|
enabledElement: Types.IEnabledElement,
|
|
556
580
|
svgDrawingHelper: SVGDrawingHelper
|
|
557
581
|
): boolean => {
|
|
558
|
-
|
|
559
|
-
const { viewport } = enabledElement;
|
|
582
|
+
let renderStatus = false;
|
|
583
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
560
584
|
const { element } = viewport;
|
|
561
585
|
|
|
586
|
+
const targetId = this.getTargetId(viewport);
|
|
587
|
+
|
|
562
588
|
let annotations = <PlanarFreehandROIAnnotation[]>(
|
|
563
589
|
getAnnotations(this.getToolName(), element)
|
|
564
590
|
);
|
|
@@ -585,50 +611,344 @@ class PlanarFreehandROITool extends AnnotationTool {
|
|
|
585
611
|
// No annotations are currently being modified, so we can just use the
|
|
586
612
|
// render contour method to render all of them
|
|
587
613
|
annotations.forEach((annotation) => {
|
|
588
|
-
if (!annotation) return;
|
|
589
614
|
this.renderContour(enabledElement, svgDrawingHelper, annotation);
|
|
590
615
|
});
|
|
616
|
+
} else {
|
|
617
|
+
// One of the annotations will need special rendering treatment, render all
|
|
618
|
+
// other annotations not being interacted with using the standard renderContour
|
|
619
|
+
// rendering path.
|
|
620
|
+
const activeAnnotationUID = this.commonData.annotation.annotationUID;
|
|
591
621
|
|
|
592
|
-
|
|
622
|
+
annotations.forEach((annotation) => {
|
|
623
|
+
if (annotation.annotationUID === activeAnnotationUID) {
|
|
624
|
+
if (isDrawing) {
|
|
625
|
+
this.renderContourBeingDrawn(
|
|
626
|
+
enabledElement,
|
|
627
|
+
svgDrawingHelper,
|
|
628
|
+
annotation
|
|
629
|
+
);
|
|
630
|
+
} else if (isEditingClosed) {
|
|
631
|
+
this.renderClosedContourBeingEdited(
|
|
632
|
+
enabledElement,
|
|
633
|
+
svgDrawingHelper,
|
|
634
|
+
annotation
|
|
635
|
+
);
|
|
636
|
+
} else if (isEditingOpen) {
|
|
637
|
+
this.renderOpenContourBeingEdited(
|
|
638
|
+
enabledElement,
|
|
639
|
+
svgDrawingHelper,
|
|
640
|
+
annotation
|
|
641
|
+
);
|
|
642
|
+
} else {
|
|
643
|
+
throw new Error(
|
|
644
|
+
`Unknown ${this.getToolName()} annotation rendering state`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
this.renderContour(enabledElement, svgDrawingHelper, annotation);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Todo: return boolean flag for each rendering route in the planar tool.
|
|
653
|
+
renderStatus = true;
|
|
593
654
|
}
|
|
594
655
|
|
|
595
|
-
|
|
596
|
-
// other annotations not being interacted with using the standard renderContour
|
|
597
|
-
// rendering path.
|
|
598
|
-
const activeAnnotationUID = this.commonData.annotation.annotationUID;
|
|
656
|
+
if (!this.configuration.calculateStats) return;
|
|
599
657
|
|
|
600
658
|
annotations.forEach((annotation) => {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
659
|
+
const activeAnnotationUID = this.commonData?.annotation.annotationUID;
|
|
660
|
+
if (
|
|
661
|
+
annotation.annotationUID === activeAnnotationUID &&
|
|
662
|
+
!this.commonData?.movingTextBox
|
|
663
|
+
)
|
|
664
|
+
return;
|
|
665
|
+
|
|
666
|
+
if (!this.commonData?.movingTextBox) {
|
|
667
|
+
const { data } = annotation;
|
|
668
|
+
if (
|
|
669
|
+
!data.cachedStats[targetId] ||
|
|
670
|
+
data.cachedStats[targetId].areaUnit === undefined
|
|
671
|
+
) {
|
|
672
|
+
data.cachedStats[targetId] = {
|
|
673
|
+
Modality: null,
|
|
674
|
+
area: null,
|
|
675
|
+
max: null,
|
|
676
|
+
mean: null,
|
|
677
|
+
stdDev: null,
|
|
678
|
+
areaUnit: null,
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
this._calculateCachedStats(
|
|
682
|
+
annotation,
|
|
683
|
+
viewport,
|
|
684
|
+
renderingEngine,
|
|
685
|
+
enabledElement
|
|
619
686
|
);
|
|
620
|
-
} else {
|
|
621
|
-
|
|
622
|
-
|
|
687
|
+
} else if (annotation.invalidated) {
|
|
688
|
+
this._throttledCalculateCachedStats(
|
|
689
|
+
annotation,
|
|
690
|
+
viewport,
|
|
691
|
+
renderingEngine,
|
|
692
|
+
enabledElement
|
|
623
693
|
);
|
|
624
694
|
}
|
|
625
|
-
} else {
|
|
626
|
-
this.renderContour(enabledElement, svgDrawingHelper, annotation);
|
|
627
695
|
}
|
|
696
|
+
|
|
697
|
+
this._renderStats(annotation, viewport, enabledElement, svgDrawingHelper);
|
|
628
698
|
});
|
|
629
699
|
|
|
630
|
-
|
|
631
|
-
|
|
700
|
+
return renderStatus;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
_calculateCachedStats = (
|
|
704
|
+
annotation,
|
|
705
|
+
viewport,
|
|
706
|
+
renderingEngine,
|
|
707
|
+
enabledElement
|
|
708
|
+
) => {
|
|
709
|
+
const data = annotation.data;
|
|
710
|
+
const { cachedStats, polyline: points } = data;
|
|
711
|
+
|
|
712
|
+
const targetIds = Object.keys(cachedStats);
|
|
713
|
+
|
|
714
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
715
|
+
const targetId = targetIds[i];
|
|
716
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
717
|
+
|
|
718
|
+
// If image does not exists for the targetId, skip. This can be due
|
|
719
|
+
// to various reasons such as if the target was a volumeViewport, and
|
|
720
|
+
// the volumeViewport has been decached in the meantime.
|
|
721
|
+
if (!image) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const { imageData, metadata, hasPixelSpacing } = image;
|
|
726
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
727
|
+
const area = polyline.calculateAreaOfPoints(canvasCoordinates);
|
|
728
|
+
|
|
729
|
+
const worldPosIndex = csUtils.transformWorldToIndex(imageData, points[0]);
|
|
730
|
+
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
731
|
+
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
732
|
+
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
733
|
+
|
|
734
|
+
let iMin = worldPosIndex[0];
|
|
735
|
+
let iMax = worldPosIndex[0];
|
|
736
|
+
|
|
737
|
+
let jMin = worldPosIndex[1];
|
|
738
|
+
let jMax = worldPosIndex[1];
|
|
739
|
+
|
|
740
|
+
let kMin = worldPosIndex[2];
|
|
741
|
+
let kMax = worldPosIndex[2];
|
|
742
|
+
|
|
743
|
+
for (let j = 1; j < points.length; j++) {
|
|
744
|
+
const worldPosIndex = csUtils.transformWorldToIndex(
|
|
745
|
+
imageData,
|
|
746
|
+
points[j]
|
|
747
|
+
);
|
|
748
|
+
worldPosIndex[0] = Math.floor(worldPosIndex[0]);
|
|
749
|
+
worldPosIndex[1] = Math.floor(worldPosIndex[1]);
|
|
750
|
+
worldPosIndex[2] = Math.floor(worldPosIndex[2]);
|
|
751
|
+
iMin = Math.min(iMin, worldPosIndex[0]);
|
|
752
|
+
iMax = Math.max(iMax, worldPosIndex[0]);
|
|
753
|
+
|
|
754
|
+
jMin = Math.min(jMin, worldPosIndex[1]);
|
|
755
|
+
jMax = Math.max(jMax, worldPosIndex[1]);
|
|
756
|
+
|
|
757
|
+
kMin = Math.min(kMin, worldPosIndex[2]);
|
|
758
|
+
kMax = Math.max(kMax, worldPosIndex[2]);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Expand bounding box
|
|
762
|
+
const iDelta = 0.01 * (iMax - iMin);
|
|
763
|
+
const jDelta = 0.01 * (jMax - jMin);
|
|
764
|
+
const kDelta = 0.01 * (kMax - kMin);
|
|
765
|
+
|
|
766
|
+
iMin = Math.floor(iMin - iDelta);
|
|
767
|
+
iMax = Math.ceil(iMax + iDelta);
|
|
768
|
+
jMin = Math.floor(jMin - jDelta);
|
|
769
|
+
jMax = Math.ceil(jMax + jDelta);
|
|
770
|
+
kMin = Math.floor(kMin - kDelta);
|
|
771
|
+
kMax = Math.ceil(kMax + kDelta);
|
|
772
|
+
|
|
773
|
+
const boundsIJK = [
|
|
774
|
+
[iMin, iMax],
|
|
775
|
+
[jMin, jMax],
|
|
776
|
+
[kMin, kMax],
|
|
777
|
+
] as [Types.Point2, Types.Point2, Types.Point2];
|
|
778
|
+
|
|
779
|
+
const worldPosEnd = imageData.indexToWorld([iMax, jMax, kMax]);
|
|
780
|
+
const canvasPosEnd = viewport.worldToCanvas(worldPosEnd);
|
|
781
|
+
|
|
782
|
+
let count = 0;
|
|
783
|
+
let sum = 0;
|
|
784
|
+
let sumSquares = 0;
|
|
785
|
+
let max = -Infinity;
|
|
786
|
+
|
|
787
|
+
const statCalculator = ({ value: newValue }) => {
|
|
788
|
+
if (newValue > max) {
|
|
789
|
+
max = newValue;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
sum += newValue;
|
|
793
|
+
sumSquares += newValue ** 2;
|
|
794
|
+
count += 1;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
let curRow = 0;
|
|
798
|
+
let intersections = [];
|
|
799
|
+
let intersectionCounter = 0;
|
|
800
|
+
pointInShapeCallback(
|
|
801
|
+
imageData,
|
|
802
|
+
(pointLPS, pointIJK) => {
|
|
803
|
+
let result = true;
|
|
804
|
+
const point = viewport.worldToCanvas(pointLPS);
|
|
805
|
+
if (point[1] != curRow) {
|
|
806
|
+
intersectionCounter = 0;
|
|
807
|
+
curRow = point[1];
|
|
808
|
+
intersections = getIntersectionCoordinatesWithPolyline(
|
|
809
|
+
canvasCoordinates,
|
|
810
|
+
point,
|
|
811
|
+
[canvasPosEnd[0], point[1]]
|
|
812
|
+
);
|
|
813
|
+
intersections.sort(
|
|
814
|
+
(function (index) {
|
|
815
|
+
return function (a, b) {
|
|
816
|
+
return a[index] === b[index]
|
|
817
|
+
? 0
|
|
818
|
+
: a[index] < b[index]
|
|
819
|
+
? -1
|
|
820
|
+
: 1;
|
|
821
|
+
};
|
|
822
|
+
})(0)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (intersections.length && point[0] > intersections[0][0]) {
|
|
826
|
+
intersections.shift();
|
|
827
|
+
intersectionCounter++;
|
|
828
|
+
}
|
|
829
|
+
if (intersectionCounter % 2 === 0) {
|
|
830
|
+
result = false;
|
|
831
|
+
}
|
|
832
|
+
return result;
|
|
833
|
+
},
|
|
834
|
+
statCalculator,
|
|
835
|
+
boundsIJK
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
const mean = sum / count;
|
|
839
|
+
|
|
840
|
+
// https://www.strchr.com/standard_deviation_in_one_pass?allcomments=1
|
|
841
|
+
let stdDev = sumSquares / count - mean ** 2;
|
|
842
|
+
stdDev = Math.sqrt(stdDev);
|
|
843
|
+
|
|
844
|
+
cachedStats[targetId] = {
|
|
845
|
+
Modality: metadata.Modality,
|
|
846
|
+
area,
|
|
847
|
+
mean,
|
|
848
|
+
max,
|
|
849
|
+
stdDev,
|
|
850
|
+
areaUnit: hasPixelSpacing ? 'mm' : 'px',
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
annotation.invalidated = false;
|
|
855
|
+
|
|
856
|
+
return cachedStats;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
_renderStats = (annotation, viewport, enabledElement, svgDrawingHelper) => {
|
|
860
|
+
const data = annotation.data;
|
|
861
|
+
const targetId = this.getTargetId(viewport);
|
|
862
|
+
const isPreScaled = isViewportPreScaled(viewport, targetId);
|
|
863
|
+
const isSuvScaled = this.isSuvScaled(
|
|
864
|
+
viewport,
|
|
865
|
+
targetId,
|
|
866
|
+
annotation.metadata.referencedImageId
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
const textLines = this._getTextLines(
|
|
870
|
+
data,
|
|
871
|
+
targetId,
|
|
872
|
+
isPreScaled,
|
|
873
|
+
isSuvScaled
|
|
874
|
+
);
|
|
875
|
+
if (!textLines || textLines.length === 0) return;
|
|
876
|
+
|
|
877
|
+
const canvasCoordinates = data.polyline.map((p) =>
|
|
878
|
+
viewport.worldToCanvas(p)
|
|
879
|
+
);
|
|
880
|
+
if (!data.handles.textBox.hasMoved) {
|
|
881
|
+
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
|
|
882
|
+
|
|
883
|
+
data.handles.textBox.worldPosition =
|
|
884
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const textBoxPosition = viewport.worldToCanvas(
|
|
888
|
+
data.handles.textBox.worldPosition
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
const styleSpecifier: AnnotationStyle.StyleSpecifier = {
|
|
892
|
+
toolGroupId: this.toolGroupId,
|
|
893
|
+
toolName: this.getToolName(),
|
|
894
|
+
viewportId: enabledElement.viewport.id,
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
const textBoxUID = '1';
|
|
898
|
+
const boundingBox = drawLinkedTextBox(
|
|
899
|
+
svgDrawingHelper,
|
|
900
|
+
annotation.annotationUID ?? '',
|
|
901
|
+
textBoxUID,
|
|
902
|
+
textLines,
|
|
903
|
+
textBoxPosition,
|
|
904
|
+
canvasCoordinates,
|
|
905
|
+
{},
|
|
906
|
+
this.getLinkedTextBoxStyle(styleSpecifier, annotation)
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
910
|
+
|
|
911
|
+
data.handles.textBox.worldBoundingBox = {
|
|
912
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
913
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
914
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
915
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
916
|
+
};
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
_getTextLines = (
|
|
920
|
+
data,
|
|
921
|
+
targetId: string,
|
|
922
|
+
isPreScaled: boolean,
|
|
923
|
+
isSuvScaled: boolean
|
|
924
|
+
): string[] => {
|
|
925
|
+
const cachedVolumeStats = data.cachedStats[targetId];
|
|
926
|
+
const { area, mean, stdDev, max, isEmptyArea, Modality, areaUnit } =
|
|
927
|
+
cachedVolumeStats;
|
|
928
|
+
|
|
929
|
+
const textLines: string[] = [];
|
|
930
|
+
const unit = getModalityUnit(Modality, isPreScaled, isSuvScaled);
|
|
931
|
+
|
|
932
|
+
if (area) {
|
|
933
|
+
const areaLine = isEmptyArea
|
|
934
|
+
? `Area: Oblique not supported`
|
|
935
|
+
: `Area: ${area.toFixed(2)} ${areaUnit}\xb2`;
|
|
936
|
+
textLines.push(areaLine);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (mean) {
|
|
940
|
+
textLines.push(`Mean: ${mean.toFixed(2)} ${unit}`);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (max) {
|
|
944
|
+
textLines.push(`Max: ${max.toFixed(2)} ${unit}`);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (stdDev) {
|
|
948
|
+
textLines.push(`Std Dev: ${stdDev.toFixed(2)} ${unit}`);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return textLines;
|
|
632
952
|
};
|
|
633
953
|
}
|
|
634
954
|
|
|
@@ -57,6 +57,7 @@ function activateDraw(
|
|
|
57
57
|
spacing,
|
|
58
58
|
xDir,
|
|
59
59
|
yDir,
|
|
60
|
+
movingTextBox: false,
|
|
60
61
|
};
|
|
61
62
|
|
|
62
63
|
state.isInteractingWithTool = true;
|
|
@@ -98,7 +99,14 @@ function mouseDragDrawCallback(evt: EventTypes.InteractionEventType): void {
|
|
|
98
99
|
const enabledElement = getEnabledElement(element);
|
|
99
100
|
const { renderingEngine, viewport } = enabledElement;
|
|
100
101
|
|
|
101
|
-
const {
|
|
102
|
+
const {
|
|
103
|
+
annotation,
|
|
104
|
+
viewportIdsToRender,
|
|
105
|
+
xDir,
|
|
106
|
+
yDir,
|
|
107
|
+
spacing,
|
|
108
|
+
movingTextBox,
|
|
109
|
+
} = this.commonData;
|
|
102
110
|
const { polylineIndex, canvasPoints } = this.drawData;
|
|
103
111
|
|
|
104
112
|
const lastCanvasPoint = canvasPoints[canvasPoints.length - 1];
|
|
@@ -117,21 +125,38 @@ function mouseDragDrawCallback(evt: EventTypes.InteractionEventType): void {
|
|
|
117
125
|
return;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
|
|
128
|
+
if (movingTextBox) {
|
|
129
|
+
this.isDrawing = false;
|
|
130
|
+
|
|
131
|
+
// Drag mode - Move the text boxes world position
|
|
132
|
+
const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
|
|
133
|
+
const worldPosDelta = deltaPoints.world;
|
|
134
|
+
|
|
135
|
+
const { textBox } = annotation.data.handles;
|
|
136
|
+
const { worldPosition } = textBox;
|
|
137
|
+
|
|
138
|
+
worldPosition[0] += worldPosDelta[0];
|
|
139
|
+
worldPosition[1] += worldPosDelta[1];
|
|
140
|
+
worldPosition[2] += worldPosDelta[2];
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
// If we have crossed our drawing line, create a closed contour and then
|
|
124
|
-
// start an edit.
|
|
125
|
-
this.applyCreateOnCross(evt, crossingIndex);
|
|
142
|
+
textBox.hasMoved = true;
|
|
126
143
|
} else {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
const crossingIndex = this.findCrossingIndexDuringCreate(evt);
|
|
145
|
+
|
|
146
|
+
if (crossingIndex !== undefined) {
|
|
147
|
+
// If we have crossed our drawing line, create a closed contour and then
|
|
148
|
+
// start an edit.
|
|
149
|
+
this.applyCreateOnCross(evt, crossingIndex);
|
|
150
|
+
} else {
|
|
151
|
+
const numPointsAdded = addCanvasPointsToArray(
|
|
152
|
+
element,
|
|
153
|
+
canvasPoints,
|
|
154
|
+
canvasPos,
|
|
155
|
+
this.commonData
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
this.drawData.polylineIndex = polylineIndex + numPointsAdded;
|
|
159
|
+
}
|
|
135
160
|
}
|
|
136
161
|
|
|
137
162
|
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
@@ -54,6 +54,7 @@ function activateOpenContourEdit(
|
|
|
54
54
|
spacing,
|
|
55
55
|
xDir,
|
|
56
56
|
yDir,
|
|
57
|
+
movingTextBox: false,
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
state.isInteractingWithTool = true;
|
|
@@ -230,7 +231,7 @@ function openContourEditOverwriteEnd(
|
|
|
230
231
|
|
|
231
232
|
// Jump to a normal line edit now.
|
|
232
233
|
this.deactivateOpenContourEdit(element);
|
|
233
|
-
this.activateOpenContourEndEdit(evt, annotation, viewportIdsToRender);
|
|
234
|
+
this.activateOpenContourEndEdit(evt, annotation, viewportIdsToRender, null);
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
/**
|
|
@@ -2,7 +2,12 @@ import { getEnabledElement } from '@cornerstonejs/core';
|
|
|
2
2
|
import { state } from '../../../store';
|
|
3
3
|
import { Events } from '../../../enums';
|
|
4
4
|
import { hideElementCursor } from '../../../cursors/elementCursor';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
EventTypes,
|
|
7
|
+
Annotation,
|
|
8
|
+
ToolHandle,
|
|
9
|
+
TextBoxHandle,
|
|
10
|
+
} from '../../../types';
|
|
6
11
|
import { polyline } from '../../../utilities/math';
|
|
7
12
|
|
|
8
13
|
const { getSubPixelSpacingAndXYDirections } = polyline;
|
|
@@ -14,7 +19,8 @@ const { getSubPixelSpacingAndXYDirections } = polyline;
|
|
|
14
19
|
function activateOpenContourEndEdit(
|
|
15
20
|
evt: EventTypes.InteractionEventType,
|
|
16
21
|
annotation: Annotation,
|
|
17
|
-
viewportIdsToRender: string[]
|
|
22
|
+
viewportIdsToRender: string[],
|
|
23
|
+
handle: ToolHandle | null
|
|
18
24
|
): void {
|
|
19
25
|
this.isDrawing = true;
|
|
20
26
|
|
|
@@ -37,6 +43,11 @@ function activateOpenContourEndEdit(
|
|
|
37
43
|
canvasPoints.reverse();
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
let movingTextBox = false;
|
|
47
|
+
if ((handle as TextBoxHandle).worldPosition) {
|
|
48
|
+
movingTextBox = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
this.drawData = {
|
|
41
52
|
canvasPoints: canvasPoints,
|
|
42
53
|
polylineIndex: canvasPoints.length - 1,
|
|
@@ -48,6 +59,7 @@ function activateOpenContourEndEdit(
|
|
|
48
59
|
spacing,
|
|
49
60
|
xDir,
|
|
50
61
|
yDir,
|
|
62
|
+
movingTextBox,
|
|
51
63
|
};
|
|
52
64
|
|
|
53
65
|
state.isInteractingWithTool = true;
|