@cornerstonejs/tools 0.66.6 → 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.
Files changed (55) hide show
  1. package/dist/cjs/tools/annotation/PlanarFreehandROITool.d.ts +5 -2
  2. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +221 -22
  3. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  4. package/dist/cjs/tools/annotation/planarFreehandROITool/closedContourEditLoop.js +1 -0
  5. package/dist/cjs/tools/annotation/planarFreehandROITool/closedContourEditLoop.js.map +1 -1
  6. package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js +20 -6
  7. package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
  8. package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEditLoop.js +2 -1
  9. package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEditLoop.js.map +1 -1
  10. package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js +6 -1
  11. package/dist/cjs/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js.map +1 -1
  12. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +1 -0
  13. package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.d.ts +3 -1
  14. package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.js +51 -1
  15. package/dist/cjs/utilities/math/polyline/getIntersectionWithPolyline.js.map +1 -1
  16. package/dist/cjs/utilities/math/polyline/planarFreehandROIInternalTypes.d.ts +1 -0
  17. package/dist/cjs/utilities/math/polyline/pointInPolyline.d.ts +2 -0
  18. package/dist/cjs/utilities/math/polyline/pointInPolyline.js +14 -0
  19. package/dist/cjs/utilities/math/polyline/pointInPolyline.js.map +1 -0
  20. package/dist/cjs/utilities/viewport/isViewportPreScaled.js +3 -3
  21. package/dist/cjs/utilities/viewport/isViewportPreScaled.js.map +1 -1
  22. package/dist/esm/tools/annotation/PlanarFreehandROITool.d.ts +5 -2
  23. package/dist/esm/tools/annotation/PlanarFreehandROITool.js +219 -22
  24. package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  25. package/dist/esm/tools/annotation/planarFreehandROITool/closedContourEditLoop.js +1 -0
  26. package/dist/esm/tools/annotation/planarFreehandROITool/closedContourEditLoop.js.map +1 -1
  27. package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js +20 -6
  28. package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
  29. package/dist/esm/tools/annotation/planarFreehandROITool/openContourEditLoop.js +2 -1
  30. package/dist/esm/tools/annotation/planarFreehandROITool/openContourEditLoop.js.map +1 -1
  31. package/dist/esm/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js +6 -1
  32. package/dist/esm/tools/annotation/planarFreehandROITool/openContourEndEditLoop.js.map +1 -1
  33. package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +1 -0
  34. package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.d.ts +3 -1
  35. package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.js +49 -1
  36. package/dist/esm/utilities/math/polyline/getIntersectionWithPolyline.js.map +1 -1
  37. package/dist/esm/utilities/math/polyline/planarFreehandROIInternalTypes.d.ts +1 -0
  38. package/dist/esm/utilities/math/polyline/pointInPolyline.d.ts +2 -0
  39. package/dist/esm/utilities/math/polyline/pointInPolyline.js +11 -0
  40. package/dist/esm/utilities/math/polyline/pointInPolyline.js.map +1 -0
  41. package/dist/esm/utilities/viewport/isViewportPreScaled.js +3 -3
  42. package/dist/esm/utilities/viewport/isViewportPreScaled.js.map +1 -1
  43. package/dist/umd/index.js +1 -1
  44. package/dist/umd/index.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/tools/annotation/PlanarFreehandROITool.ts +356 -36
  47. package/src/tools/annotation/planarFreehandROITool/closedContourEditLoop.ts +1 -0
  48. package/src/tools/annotation/planarFreehandROITool/drawLoop.ts +39 -14
  49. package/src/tools/annotation/planarFreehandROITool/openContourEditLoop.ts +2 -1
  50. package/src/tools/annotation/planarFreehandROITool/openContourEndEditLoop.ts +14 -2
  51. package/src/types/ToolSpecificAnnotationTypes.ts +1 -0
  52. package/src/utilities/math/polyline/getIntersectionWithPolyline.ts +94 -1
  53. package/src/utilities/math/polyline/planarFreehandROIInternalTypes.ts +1 -0
  54. package/src/utilities/math/polyline/pointInPolyline.ts +17 -0
  55. package/src/utilities/viewport/isViewportPreScaled.ts +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "0.66.6",
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.45.1",
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": "2a678fa8a71841b0a241357999d737a7b7f98126"
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(evt, annotation, viewportIdsToRender);
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
- const renderStatus = false;
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
- return renderStatus;
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
- // One of the annotations will need special rendering treatment, render all
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
- if (annotation.annotationUID === activeAnnotationUID) {
602
- if (isDrawing) {
603
- this.renderContourBeingDrawn(
604
- enabledElement,
605
- svgDrawingHelper,
606
- annotation
607
- );
608
- } else if (isEditingClosed) {
609
- this.renderClosedContourBeingEdited(
610
- enabledElement,
611
- svgDrawingHelper,
612
- annotation
613
- );
614
- } else if (isEditingOpen) {
615
- this.renderOpenContourBeingEdited(
616
- enabledElement,
617
- svgDrawingHelper,
618
- annotation
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
- throw new Error(
622
- `Unknown ${this.getToolName()} annotation rendering state`
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
- // Todo: return boolean flag for each rendering route in the planar tool.
631
- return true;
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
 
@@ -58,6 +58,7 @@ function activateClosedContourEdit(
58
58
  spacing,
59
59
  xDir,
60
60
  yDir,
61
+ movingTextBox: false,
61
62
  };
62
63
 
63
64
  state.isInteractingWithTool = true;
@@ -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 { viewportIdsToRender, xDir, yDir, spacing } = this.commonData;
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
- const crossingIndex = this.findCrossingIndexDuringCreate(evt);
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
- if (crossingIndex !== undefined) {
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 numPointsAdded = addCanvasPointsToArray(
128
- element,
129
- canvasPoints,
130
- canvasPos,
131
- this.commonData
132
- );
133
-
134
- this.drawData.polylineIndex = polylineIndex + numPointsAdded;
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 { EventTypes, Annotation } from '../../../types';
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;
@@ -237,6 +237,7 @@ export interface PlanarFreehandROIAnnotation extends Annotation {
237
237
  };
238
238
  };
239
239
  };
240
+ cachedStats?: ROICachedStats;
240
241
  };
241
242
  }
242
243