@cornerstonejs/tools 1.41.0 → 1.42.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 (137) hide show
  1. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js +2 -1
  2. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  3. package/dist/cjs/stateManagement/annotation/annotationState.js +3 -0
  4. package/dist/cjs/stateManagement/annotation/annotationState.js.map +1 -1
  5. package/dist/cjs/tools/annotation/BidirectionalTool.js +7 -6
  6. package/dist/cjs/tools/annotation/BidirectionalTool.js.map +1 -1
  7. package/dist/cjs/types/index.d.ts +2 -1
  8. package/dist/cjs/utilities/contours/AnnotationToPointData.d.ts +11 -0
  9. package/dist/cjs/utilities/contours/AnnotationToPointData.js +44 -0
  10. package/dist/cjs/utilities/contours/AnnotationToPointData.js.map +1 -0
  11. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.d.ts +6 -0
  12. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.js +43 -0
  13. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.js.map +1 -0
  14. package/dist/cjs/utilities/contours/contourFinder.d.ts +7 -0
  15. package/dist/cjs/utilities/contours/contourFinder.js +68 -0
  16. package/dist/cjs/utilities/contours/contourFinder.js.map +1 -0
  17. package/dist/cjs/utilities/contours/detectContourHoles.d.ts +5 -0
  18. package/dist/cjs/utilities/contours/detectContourHoles.js +78 -0
  19. package/dist/cjs/utilities/contours/detectContourHoles.js.map +1 -0
  20. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.d.ts +4 -0
  21. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.js +124 -0
  22. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.js.map +1 -0
  23. package/dist/cjs/utilities/contours/index.d.ts +6 -0
  24. package/dist/cjs/utilities/contours/index.js +17 -0
  25. package/dist/cjs/utilities/contours/index.js.map +1 -0
  26. package/dist/cjs/utilities/contours/mergePoints.d.ts +8 -0
  27. package/dist/cjs/utilities/contours/mergePoints.js +77 -0
  28. package/dist/cjs/utilities/contours/mergePoints.js.map +1 -0
  29. package/dist/cjs/utilities/index.d.ts +2 -1
  30. package/dist/cjs/utilities/index.js +3 -1
  31. package/dist/cjs/utilities/index.js.map +1 -1
  32. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.d.ts +1 -0
  33. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.js +31 -0
  34. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.js.map +1 -0
  35. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.d.ts +14 -0
  36. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.js +43 -0
  37. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.js.map +1 -0
  38. package/dist/cjs/utilities/segmentation/findLargestBidirectional.d.ts +1 -0
  39. package/dist/cjs/utilities/segmentation/findLargestBidirectional.js +94 -0
  40. package/dist/cjs/utilities/segmentation/findLargestBidirectional.js.map +1 -0
  41. package/dist/cjs/utilities/segmentation/index.d.ts +4 -1
  42. package/dist/cjs/utilities/segmentation/index.js +7 -1
  43. package/dist/cjs/utilities/segmentation/index.js.map +1 -1
  44. package/dist/cjs/utilities/segmentation/isLineInSegment.d.ts +9 -0
  45. package/dist/cjs/utilities/segmentation/isLineInSegment.js +55 -0
  46. package/dist/cjs/utilities/segmentation/isLineInSegment.js.map +1 -0
  47. package/dist/cjs/utilities/segmentation/segmentContourAction.d.ts +17 -0
  48. package/dist/cjs/utilities/segmentation/segmentContourAction.js +122 -0
  49. package/dist/cjs/utilities/segmentation/segmentContourAction.js.map +1 -0
  50. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js +2 -1
  51. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  52. package/dist/esm/stateManagement/annotation/annotationState.js +3 -0
  53. package/dist/esm/stateManagement/annotation/annotationState.js.map +1 -1
  54. package/dist/esm/tools/annotation/BidirectionalTool.js +7 -6
  55. package/dist/esm/tools/annotation/BidirectionalTool.js.map +1 -1
  56. package/dist/esm/utilities/contours/AnnotationToPointData.js +39 -0
  57. package/dist/esm/utilities/contours/AnnotationToPointData.js.map +1 -0
  58. package/dist/esm/utilities/contours/RectangleROIStartEndThreshold.js +41 -0
  59. package/dist/esm/utilities/contours/RectangleROIStartEndThreshold.js.map +1 -0
  60. package/dist/esm/utilities/contours/contourFinder.js +63 -0
  61. package/dist/esm/utilities/contours/contourFinder.js.map +1 -0
  62. package/dist/esm/utilities/contours/detectContourHoles.js +74 -0
  63. package/dist/esm/utilities/contours/detectContourHoles.js.map +1 -0
  64. package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +117 -0
  65. package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js.map +1 -0
  66. package/dist/esm/utilities/contours/index.js +7 -0
  67. package/dist/esm/utilities/contours/index.js.map +1 -0
  68. package/dist/esm/utilities/contours/mergePoints.js +73 -0
  69. package/dist/esm/utilities/contours/mergePoints.js.map +1 -0
  70. package/dist/esm/utilities/index.js +2 -1
  71. package/dist/esm/utilities/index.js.map +1 -1
  72. package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js +25 -0
  73. package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js.map +1 -0
  74. package/dist/esm/utilities/segmentation/createBidirectionalToolData.js +40 -0
  75. package/dist/esm/utilities/segmentation/createBidirectionalToolData.js.map +1 -0
  76. package/dist/esm/utilities/segmentation/findLargestBidirectional.js +96 -0
  77. package/dist/esm/utilities/segmentation/findLargestBidirectional.js.map +1 -0
  78. package/dist/esm/utilities/segmentation/index.js +4 -1
  79. package/dist/esm/utilities/segmentation/index.js.map +1 -1
  80. package/dist/esm/utilities/segmentation/isLineInSegment.js +50 -0
  81. package/dist/esm/utilities/segmentation/isLineInSegment.js.map +1 -0
  82. package/dist/esm/utilities/segmentation/segmentContourAction.js +98 -0
  83. package/dist/esm/utilities/segmentation/segmentContourAction.js.map +1 -0
  84. package/dist/types/eventDispatchers/keyboardEventHandlers/keyDown.d.ts.map +1 -1
  85. package/dist/types/stateManagement/annotation/annotationState.d.ts.map +1 -1
  86. package/dist/types/tools/annotation/BidirectionalTool.d.ts.map +1 -1
  87. package/dist/types/types/index.d.ts +2 -1
  88. package/dist/types/types/index.d.ts.map +1 -1
  89. package/dist/types/utilities/contours/AnnotationToPointData.d.ts +12 -0
  90. package/dist/types/utilities/contours/AnnotationToPointData.d.ts.map +1 -0
  91. package/dist/types/utilities/contours/RectangleROIStartEndThreshold.d.ts +7 -0
  92. package/dist/types/utilities/contours/RectangleROIStartEndThreshold.d.ts.map +1 -0
  93. package/dist/types/utilities/contours/contourFinder.d.ts +8 -0
  94. package/dist/types/utilities/contours/contourFinder.d.ts.map +1 -0
  95. package/dist/types/utilities/contours/detectContourHoles.d.ts +6 -0
  96. package/dist/types/utilities/contours/detectContourHoles.d.ts.map +1 -0
  97. package/dist/types/utilities/contours/generateContourSetsFromLabelmap.d.ts +5 -0
  98. package/dist/types/utilities/contours/generateContourSetsFromLabelmap.d.ts.map +1 -0
  99. package/dist/types/utilities/contours/index.d.ts +7 -0
  100. package/dist/types/utilities/contours/index.d.ts.map +1 -0
  101. package/dist/types/utilities/contours/mergePoints.d.ts +9 -0
  102. package/dist/types/utilities/contours/mergePoints.d.ts.map +1 -0
  103. package/dist/types/utilities/index.d.ts +2 -1
  104. package/dist/types/utilities/index.d.ts.map +1 -1
  105. package/dist/types/utilities/segmentation/contourAndFindLargestBidirectional.d.ts +2 -0
  106. package/dist/types/utilities/segmentation/contourAndFindLargestBidirectional.d.ts.map +1 -0
  107. package/dist/types/utilities/segmentation/createBidirectionalToolData.d.ts +15 -0
  108. package/dist/types/utilities/segmentation/createBidirectionalToolData.d.ts.map +1 -0
  109. package/dist/types/utilities/segmentation/findLargestBidirectional.d.ts +2 -0
  110. package/dist/types/utilities/segmentation/findLargestBidirectional.d.ts.map +1 -0
  111. package/dist/types/utilities/segmentation/index.d.ts +4 -1
  112. package/dist/types/utilities/segmentation/index.d.ts.map +1 -1
  113. package/dist/types/utilities/segmentation/isLineInSegment.d.ts +10 -0
  114. package/dist/types/utilities/segmentation/isLineInSegment.d.ts.map +1 -0
  115. package/dist/types/utilities/segmentation/segmentContourAction.d.ts +18 -0
  116. package/dist/types/utilities/segmentation/segmentContourAction.d.ts.map +1 -0
  117. package/dist/umd/index.js +1 -1
  118. package/dist/umd/index.js.map +1 -1
  119. package/package.json +3 -3
  120. package/src/eventDispatchers/keyboardEventHandlers/keyDown.ts +7 -1
  121. package/src/stateManagement/annotation/annotationState.ts +3 -0
  122. package/src/tools/annotation/BidirectionalTool.ts +9 -5
  123. package/src/types/index.ts +2 -0
  124. package/src/utilities/contours/AnnotationToPointData.ts +61 -0
  125. package/src/utilities/contours/RectangleROIStartEndThreshold.ts +60 -0
  126. package/src/utilities/contours/contourFinder.ts +78 -0
  127. package/src/utilities/contours/detectContourHoles.ts +147 -0
  128. package/src/utilities/contours/generateContourSetsFromLabelmap.ts +160 -0
  129. package/src/utilities/contours/index.ts +14 -0
  130. package/src/utilities/contours/mergePoints.ts +108 -0
  131. package/src/utilities/index.ts +2 -0
  132. package/src/utilities/segmentation/contourAndFindLargestBidirectional.ts +46 -0
  133. package/src/utilities/segmentation/createBidirectionalToolData.ts +68 -0
  134. package/src/utilities/segmentation/findLargestBidirectional.ts +159 -0
  135. package/src/utilities/segmentation/index.ts +6 -0
  136. package/src/utilities/segmentation/isLineInSegment.ts +84 -0
  137. package/src/utilities/segmentation/segmentContourAction.ts +169 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Checks if point is within array
3
+ * @param {*} array
4
+ * @param {*} pt
5
+ * @returns
6
+ */
7
+ function ptInArray(array, pt) {
8
+ let index = -1;
9
+ for (let i = 0; i < array.length; i++) {
10
+ if (isSamePoint(pt, array[i])) {
11
+ index = i;
12
+ }
13
+ }
14
+ return index;
15
+ }
16
+
17
+ /**
18
+ * Checks if point A and point B contain same values
19
+ * @param {*} ptA
20
+ * @param {*} ptB
21
+ * @returns
22
+ */
23
+ function isSamePoint(ptA, ptB) {
24
+ if (ptA[0] == ptB[0] && ptA[1] == ptB[1] && ptA[2] == ptB[2]) {
25
+ return true;
26
+ } else {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Goes through linesArray and replaces all references of old index with new index
33
+ * @param {*} linesArray
34
+ * @param {*} oldIndex
35
+ * @param {*} newIndex
36
+ */
37
+ function replacePointIndexReferences(linesArray, oldIndex, newIndex) {
38
+ for (let i = 0; i < linesArray.length; i++) {
39
+ const line = linesArray[i];
40
+ if (line.a == oldIndex) {
41
+ line.a = newIndex;
42
+ } else if (line.b == oldIndex) {
43
+ line.b = newIndex;
44
+ }
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Iterate through polyData from vtkjs and merge any points that are the same
50
+ * then update merged point references within lines array
51
+ * @param {*} polyData
52
+ * @param {*} bypass
53
+ * @returns
54
+ */
55
+ export function removeDuplicatePoints(polyData, bypass = false) {
56
+ const points = polyData.getPoints();
57
+ const lines = polyData.getLines();
58
+
59
+ const pointsArray = [];
60
+ for (let i = 0; i < points.getNumberOfPoints(); i++) {
61
+ const pt = points.getPoint(i).slice();
62
+ pointsArray.push(pt);
63
+ }
64
+ const linesArray = [];
65
+ for (let i = 0; i < lines.getNumberOfCells(); i++) {
66
+ const cell = lines.getCell(i * 3).slice();
67
+ //console.log(JSON.stringify(cell));
68
+ const a = cell[0];
69
+ const b = cell[1];
70
+ const line = {
71
+ a,
72
+ b,
73
+ };
74
+ linesArray.push(line);
75
+ }
76
+
77
+ if (bypass) {
78
+ return { points: pointsArray, lines: linesArray };
79
+ }
80
+
81
+ // Iterate through points and replace any duplicates
82
+ const newPoints = [];
83
+ for (let i = 0; i < pointsArray.length; i++) {
84
+ const pt = pointsArray[i];
85
+ let index = ptInArray(newPoints, pt);
86
+
87
+ if (index >= 0) {
88
+ // Duplicate Point -> replace references in lines
89
+ replacePointIndexReferences(linesArray, i, index);
90
+ } else {
91
+ index = newPoints.length;
92
+ newPoints.push(pt);
93
+ replacePointIndexReferences(linesArray, i, index);
94
+ }
95
+ }
96
+
97
+ // Final pass through lines, remove any that refer to exact same point
98
+ const newLines = [];
99
+ linesArray.forEach((line) => {
100
+ if (line.a != line.b) {
101
+ newLines.push(line);
102
+ }
103
+ });
104
+
105
+ return { points: newPoints, lines: newLines };
106
+ }
107
+
108
+ export default { removeDuplicatePoints };
@@ -26,6 +26,7 @@ import { pointToString } from './pointToString';
26
26
  import annotationFrameRange from './annotationFrameRange';
27
27
 
28
28
  // name spaces
29
+ import * as contours from './contours';
29
30
  import * as segmentation from './segmentation';
30
31
  import * as drawing from './drawing';
31
32
  import * as math from './math';
@@ -63,6 +64,7 @@ export {
63
64
  getCalibratedAreaUnits,
64
65
  getCalibratedScale,
65
66
  segmentation,
67
+ contours,
66
68
  triggerAnnotationRenderForViewportIds,
67
69
  triggerAnnotationRender,
68
70
  pointInShapeCallback,
@@ -0,0 +1,46 @@
1
+ import { generateContourSetsFromLabelmap } from '../contours';
2
+ import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
3
+ import findLargestBidirectional from './findLargestBidirectional';
4
+
5
+ const EPSILON = 1e-2;
6
+ const { Labelmap } = SegmentationRepresentations;
7
+
8
+ /**
9
+ * Generates a contour object over the segment, and then uses the contouring to
10
+ * find the largest bidirectional object that can be applied within the acquisition
11
+ * plane that is within the segment index, or the contained segment indices.
12
+ *
13
+ * @param segmentation.segments - a list of segments to apply the contour to.
14
+ * @param segmentation.segments.containedSegmentIndices - a set of segment indexes equivalent to the primary segment
15
+ * @param segmentation.segments.label - the label for the segment
16
+ * @param segmentation.segments.color - the color to use for the segment label
17
+ */
18
+ export default function contourAndFindLargestBidirectional(segmentation) {
19
+ const contours = generateContourSetsFromLabelmap({
20
+ segmentations: segmentation,
21
+ });
22
+
23
+ if (!contours?.length || !contours[0].sliceContours.length) {
24
+ return;
25
+ }
26
+
27
+ const {
28
+ representationData,
29
+ segments = [
30
+ null,
31
+ { label: 'Unspecified', color: null, containedSegmentIndices: null },
32
+ ],
33
+ } = segmentation;
34
+ const { volumeId: segVolumeId } = representationData[Labelmap];
35
+
36
+ const segmentIndex = segments.findIndex((it) => !!it);
37
+ if (segmentIndex === -1) {
38
+ return;
39
+ }
40
+ segments[segmentIndex].segmentIndex = segmentIndex;
41
+ return findLargestBidirectional(
42
+ contours[0],
43
+ segVolumeId,
44
+ segments[segmentIndex]
45
+ );
46
+ }
@@ -0,0 +1,68 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import type { Annotation } from '../../types/AnnotationTypes';
3
+
4
+ export type BidirectionalData = {
5
+ majorAxis: [Types.Point3, Types.Point3];
6
+ minorAxis: [Types.Point3, Types.Point3];
7
+ maxMajor: number;
8
+ maxMinor: number;
9
+ segmentIndex: number;
10
+ label?: string;
11
+ color?: string | number[];
12
+ referencedImageId: string;
13
+ FrameOfReferenceUID: string;
14
+ };
15
+
16
+ /**
17
+ * Creates data suitable for the BidirectionalTool from the basic bidirectional
18
+ * data object.
19
+ */
20
+ export default function createBidirectionalToolData(
21
+ bidirectionalData: BidirectionalData,
22
+ viewport
23
+ ): Annotation {
24
+ const {
25
+ majorAxis,
26
+ minorAxis,
27
+ label = '',
28
+ FrameOfReferenceUID,
29
+ referencedImageId,
30
+ } = bidirectionalData;
31
+ const [major0, major1] = majorAxis;
32
+ const [minor0, minor1] = minorAxis;
33
+
34
+ const { viewUp, viewPlaneNormal } = viewport.getCamera();
35
+ const points = [major0, major1, minor0, minor1];
36
+ const bidirectionalToolData = {
37
+ highlighted: true,
38
+ invalidated: true,
39
+ metadata: {
40
+ toolName: 'Bidirectional',
41
+ viewPlaneNormal,
42
+ viewUp,
43
+ FrameOfReferenceUID,
44
+ referencedImageId,
45
+ },
46
+ data: {
47
+ handles: {
48
+ points,
49
+ textBox: {
50
+ hasMoved: false,
51
+ worldPosition: [0, 0, 0] as Types.Point3,
52
+ worldBoundingBox: {
53
+ topLeft: [0, 0, 0] as Types.Point3,
54
+ topRight: [0, 0, 0] as Types.Point3,
55
+ bottomLeft: [0, 0, 0] as Types.Point3,
56
+ bottomRight: [0, 0, 0] as Types.Point3,
57
+ },
58
+ },
59
+ activeHandleIndex: null,
60
+ },
61
+ label,
62
+ cachedStats: {},
63
+ },
64
+ isLocked: false,
65
+ isVisible: true,
66
+ };
67
+ return bidirectionalToolData;
68
+ }
@@ -0,0 +1,159 @@
1
+ import { vec3 } from 'gl-matrix';
2
+
3
+ import { createIsInSegment, isLineInSegment } from './isLineInSegment';
4
+ import type { BidirectionalData } from './createBidirectionalToolData';
5
+
6
+ const EPSILON = 1e-2;
7
+
8
+ /**
9
+ * Search in the contours for the given segment to find the largest bidirectional
10
+ * that will fit entirely within the slice contours inside the contours object.
11
+ * Assumptions/implementation details:
12
+ *
13
+ * 1. The major and minor bidirectional lines must not cross the contour
14
+ * 2. The center point for both major and minor bidirectional lines must be
15
+ * within the segment, or the contained segment index.
16
+ * 3. The major/minor axis must be orthogonal
17
+ *
18
+ * Note this does NOT test that the major/minor axis intersect. Normally they will, but
19
+ * it isn't a hard requirement.
20
+ *
21
+ * The way that islands within the contours are handled is to allow the island to be
22
+ * coloured with something that is contained - that way both open and closed islands
23
+ * can be handled correctly for finding the bidirectional (an open island is a section
24
+ * inside the segment that is open to the outside - this can happen at bone endpoints or when
25
+ * one region flows into another)
26
+ */
27
+ export default function findLargestBidirectional(
28
+ contours,
29
+ segVolumeId: string,
30
+ segment
31
+ ) {
32
+ const { sliceContours } = contours;
33
+ const { segmentIndex, containedSegmentIndices } = segment;
34
+ let maxBidirectional;
35
+ const isInSegment = createIsInSegment(
36
+ segVolumeId,
37
+ segmentIndex,
38
+ containedSegmentIndices
39
+ );
40
+ for (const sliceContour of sliceContours) {
41
+ const bidirectional = createBidirectionalForSlice(
42
+ sliceContour,
43
+ isInSegment,
44
+ maxBidirectional
45
+ );
46
+ if (!bidirectional) {
47
+ continue;
48
+ }
49
+ maxBidirectional = bidirectional;
50
+ }
51
+ if (maxBidirectional) {
52
+ Object.assign(maxBidirectional, segment);
53
+ }
54
+ return maxBidirectional;
55
+ }
56
+
57
+ /**
58
+ * This function creates a bidirectional data object for the given slice and
59
+ * slice contour, only when the major distance is larger than currentMax, or
60
+ * equal to current max and the minor is larger than currentMax's minor.
61
+ * It does this by looking at every pair of distances in sliceCountour to find
62
+ * those larger than the currentMax, and then finds the minor distance for those
63
+ * major distances.
64
+ *
65
+ */
66
+ function createBidirectionalForSlice(
67
+ sliceContour,
68
+ isInSegment,
69
+ currentMax = { maxMajor: 0, maxMinor: 0 }
70
+ ) {
71
+ const { points } = sliceContour.polyData;
72
+ const { maxMinor: currentMaxMinor, maxMajor: currentMaxMajor } = currentMax;
73
+ let maxMajor = currentMaxMajor * currentMaxMajor;
74
+ let maxMinor = currentMaxMinor * currentMaxMinor;
75
+ let maxMajorPoints;
76
+ for (let index1 = 0; index1 < points.length; index1++) {
77
+ for (let index2 = index1 + 1; index2 < points.length; index2++) {
78
+ const point1 = points[index1];
79
+ const point2 = points[index2];
80
+ const distance2 = vec3.sqrDist(point1, point2);
81
+ if (distance2 < maxMajor) {
82
+ continue;
83
+ }
84
+ if (distance2 - EPSILON < maxMajor + EPSILON && maxMajorPoints) {
85
+ // Consider adding to the set of points rather than continuing here
86
+ // so that all minor axis can be tested
87
+ continue;
88
+ }
89
+ if (!isInSegment.testCenter(point1, point2)) {
90
+ // Center between the two points has to be in the segment, otherwise
91
+ // this is out of bounds.
92
+ continue;
93
+ }
94
+ if (!isLineInSegment(point1, point2, isInSegment)) {
95
+ // If the line intersects the segment boundary, then skip it
96
+ continue;
97
+ }
98
+ maxMajor = distance2 - EPSILON;
99
+ maxMajorPoints = [index1, index2];
100
+ maxMinor = 0;
101
+ }
102
+ }
103
+ if (!maxMajorPoints) {
104
+ return;
105
+ }
106
+
107
+ maxMajor = Math.sqrt(maxMajor + EPSILON);
108
+ const handle0 = points[maxMajorPoints[0]];
109
+ const handle1 = points[maxMajorPoints[1]];
110
+ const unitMajor = vec3.sub(vec3.create(), handle0, handle1);
111
+ vec3.scale(unitMajor, unitMajor, 1 / maxMajor);
112
+
113
+ let maxMinorPoints;
114
+
115
+ for (let index1 = 0; index1 < points.length; index1++) {
116
+ for (let index2 = index1 + 1; index2 < points.length; index2++) {
117
+ const point1 = points[index1];
118
+ const point2 = points[index2];
119
+ const distance2 = vec3.sqrDist(point1, point2);
120
+ if (distance2 <= maxMinor) {
121
+ continue;
122
+ }
123
+ const delta = vec3.sub(vec3.create(), point1, point2);
124
+
125
+ const dot = Math.abs(vec3.dot(delta, unitMajor)) / Math.sqrt(distance2);
126
+ if (dot > EPSILON) {
127
+ continue;
128
+ }
129
+
130
+ if (!isInSegment.testCenter(point1, point2)) {
131
+ // Center between the two points has to be in the segment, otherwise
132
+ // this is out of bounds.
133
+ continue;
134
+ }
135
+ if (!isLineInSegment(point1, point2, isInSegment)) {
136
+ continue;
137
+ }
138
+ maxMinor = distance2;
139
+ maxMinorPoints = [index1, index2];
140
+ }
141
+ }
142
+
143
+ if (!maxMinorPoints) {
144
+ // Didn't find a larger minor distance
145
+ return;
146
+ }
147
+ maxMinor = Math.sqrt(maxMinor);
148
+ const handle2 = points[maxMinorPoints[0]];
149
+ const handle3 = points[maxMinorPoints[1]];
150
+
151
+ const bidirectional = {
152
+ majorAxis: [handle0, handle1],
153
+ minorAxis: [handle2, handle3],
154
+ maxMajor,
155
+ maxMinor,
156
+ ...sliceContour,
157
+ } as BidirectionalData;
158
+ return bidirectional;
159
+ }
@@ -15,6 +15,9 @@ import {
15
15
  setBrushThresholdForToolGroup,
16
16
  } from './brushThresholdForToolGroup';
17
17
  import thresholdSegmentationByRange from './thresholdSegmentationByRange';
18
+ import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
19
+ import createBidirectionalToolData from './createBidirectionalToolData';
20
+ import segmentContourAction from './segmentContourAction';
18
21
 
19
22
  export {
20
23
  thresholdVolumeByRange,
@@ -30,4 +33,7 @@ export {
30
33
  getBrushThresholdForToolGroup,
31
34
  setBrushThresholdForToolGroup,
32
35
  thresholdSegmentationByRange,
36
+ contourAndFindLargestBidirectional,
37
+ createBidirectionalToolData,
38
+ segmentContourAction,
33
39
  };
@@ -0,0 +1,84 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import { cache } from '@cornerstonejs/core';
3
+ import { vec3 } from 'gl-matrix';
4
+
5
+ /**
6
+ * Determines if there is a point between point1 and point2 which is not
7
+ * contained in the segmentation
8
+ */
9
+ export default function isLineInSegment(
10
+ point1: Types.Point3,
11
+ point2: Types.Point3,
12
+ isInSegment
13
+ ) {
14
+ const ijk1 = isInSegment.toIJK(point1);
15
+ const ijk2 = isInSegment.toIJK(point2);
16
+ const testPoint = vec3.create();
17
+ const { testIJK } = isInSegment;
18
+ const delta = vec3.sub(vec3.create(), ijk1, ijk2);
19
+
20
+ // Test once for index value between the two points, so the max of the
21
+ // difference in IJK values
22
+ const testSize = Math.round(Math.max(...delta.map(Math.abs)));
23
+ if (testSize < 2) {
24
+ // No need to test when there are only two points
25
+ return true;
26
+ }
27
+ const unitDelta = vec3.scale(vec3.create(), delta, 1 / testSize);
28
+
29
+ for (let i = 1; i < testSize; i++) {
30
+ vec3.scaleAndAdd(testPoint, ijk2, unitDelta, i);
31
+ if (!testIJK(testPoint)) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Creates a function that tests to see if the provided line segment, specified
40
+ * in LPS space (as endpoints) is contained in the segment
41
+ */
42
+ function createIsInSegment(
43
+ segVolumeId: string,
44
+ segmentIndex: number,
45
+ containedSegmentIndices?: Set<number>
46
+ ) {
47
+ // Get segmentation volume
48
+ const vol = cache.getVolume(segVolumeId);
49
+ if (!vol) {
50
+ console.warn(`No volume found for ${segVolumeId}`);
51
+ return;
52
+ }
53
+
54
+ const segData = vol.imageData.getPointData().getScalars().getData();
55
+ const width = vol.dimensions[0];
56
+ const pixelsPerSlice = width * vol.dimensions[1];
57
+
58
+ return {
59
+ /**
60
+ * Find the center point between point1 and point2, convert it to IJK space
61
+ * and test if the value at that location is in the segment
62
+ */
63
+ testCenter: (point1, point2) => {
64
+ const point = vec3.add(vec3.create(), point1, point2).map((it) => it / 2);
65
+ const ijk = vol.imageData.worldToIndex(point as vec3).map(Math.round);
66
+ const [i, j, k] = ijk;
67
+ const index = i + j * width + k * pixelsPerSlice;
68
+ const value = segData[index];
69
+ return value === segmentIndex || containedSegmentIndices?.has(value);
70
+ },
71
+
72
+ toIJK: (point) => vol.imageData.worldToIndex(point as vec3),
73
+
74
+ testIJK: (ijk) => {
75
+ const [i, j, k] = ijk;
76
+ const index =
77
+ Math.round(i) + Math.round(j) * width + Math.round(k) * pixelsPerSlice;
78
+ const value = segData[index];
79
+ return value === segmentIndex || containedSegmentIndices?.has(value);
80
+ },
81
+ };
82
+ }
83
+
84
+ export { createIsInSegment, isLineInSegment };
@@ -0,0 +1,169 @@
1
+ import { getEnabledElement, type Types } from '@cornerstonejs/core';
2
+
3
+ import type { Annotation } from '../../types/AnnotationTypes';
4
+ import * as segmentation from '../../stateManagement/segmentation';
5
+ import {
6
+ state as annotationState,
7
+ config as annotationConfig,
8
+ } from '../../stateManagement/annotation';
9
+ import { jumpToSlice } from '../viewport';
10
+ import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
11
+ import createBidirectionalToolData from './createBidirectionalToolData';
12
+ import BidirectionalTool from '../../tools/annotation/BidirectionalTool';
13
+
14
+ export type Segment = {
15
+ segmentationId: string;
16
+ segmentIndex: number;
17
+ label: string;
18
+
19
+ style?: any;
20
+ containedSegmentIndices?: (number) => boolean;
21
+ };
22
+
23
+ export type SegmentContourActionConfiguration = {
24
+ getSegment?: (
25
+ enabledElement: Types.IEnabledElement,
26
+ configuration: SegmentContourActionConfiguration
27
+ ) => Segment;
28
+
29
+ /**
30
+ * Optional map for data about each segment
31
+ */
32
+ segmentationId?: string;
33
+ segmentIndex?: number;
34
+ segmentData?: Map<number, Segment>;
35
+ toolGroupId?: string;
36
+ };
37
+
38
+ export default function segmentContourAction(
39
+ element: HTMLDivElement,
40
+ configuration
41
+ ) {
42
+ const { data: configurationData } = configuration;
43
+ const enabledElement = getEnabledElement(element);
44
+ const segment = (configurationData.getSegment || defaultGetSegment)(
45
+ enabledElement,
46
+ configurationData
47
+ );
48
+ if (!segment) {
49
+ return;
50
+ }
51
+ const FrameOfReferenceUID = enabledElement.viewport.getFrameOfReferenceUID();
52
+ const segmentationsList = segmentation.state.getSegmentations();
53
+ const { segmentIndex, segmentationId } = segment;
54
+ const bidirectionals = annotationState.getAnnotations(
55
+ this.toolName || BidirectionalTool.toolName,
56
+ FrameOfReferenceUID
57
+ );
58
+ let hasExistingActiveSegment = false;
59
+ const existingLargestBidirectionals = bidirectionals.filter(
60
+ (existingBidirectionalItem) => {
61
+ const { segment } = existingBidirectionalItem.data;
62
+ if (!segment) {
63
+ return;
64
+ }
65
+ if (
66
+ segment.segmentationId === segmentationId &&
67
+ segment.segmentIndex === segmentIndex
68
+ ) {
69
+ hasExistingActiveSegment = true;
70
+ existingBidirectionalItem.data.segment = segment;
71
+ }
72
+ return !!segment;
73
+ }
74
+ );
75
+ if (!hasExistingActiveSegment) {
76
+ // Just create a dummy annotation object containing just enough information
77
+ // to create a real one.
78
+ existingLargestBidirectionals.push({
79
+ data: { segment },
80
+ } as unknown as Annotation);
81
+ }
82
+
83
+ let newBidirectional;
84
+ existingLargestBidirectionals.forEach((existingLargestBidirectional) => {
85
+ const segments = [];
86
+ const { segment: updateSegment } = existingLargestBidirectional.data;
87
+ const { segmentIndex, segmentationId } = updateSegment;
88
+ segments[segmentIndex] = updateSegment;
89
+ annotationState.removeAnnotation(
90
+ existingLargestBidirectional.annotationUID
91
+ );
92
+ const bidirectionalData = contourAndFindLargestBidirectional({
93
+ ...segmentationsList.find(
94
+ (segmentation) => segmentation.segmentationId === segmentationId
95
+ ),
96
+ segments,
97
+ });
98
+
99
+ if (!bidirectionalData) {
100
+ return;
101
+ }
102
+ const bidirectionalToolData = createBidirectionalToolData(
103
+ bidirectionalData,
104
+ enabledElement.viewport
105
+ );
106
+ bidirectionalToolData.annotationUID =
107
+ existingLargestBidirectional.annotationUID;
108
+ bidirectionalToolData.data.segment = updateSegment;
109
+
110
+ const annotationUID = annotationState.addAnnotation(
111
+ bidirectionalToolData,
112
+ FrameOfReferenceUID
113
+ );
114
+
115
+ if (
116
+ updateSegment.segmentIndex === segment.segmentIndex &&
117
+ updateSegment.segmentationId === segment.segmentationId
118
+ ) {
119
+ newBidirectional = bidirectionalData;
120
+ const { style } = segment;
121
+ if (style) {
122
+ annotationConfig.style.setAnnotationStyles(annotationUID, style);
123
+ }
124
+ }
125
+ });
126
+
127
+ if (newBidirectional) {
128
+ const { referencedImageId } = newBidirectional;
129
+ const imageIds = enabledElement.viewport.getImageIds();
130
+ const imageIndex = imageIds.findIndex(
131
+ (imageId) => imageId === referencedImageId
132
+ );
133
+
134
+ // TODO - figure out why this is reversed
135
+ jumpToSlice(element, {
136
+ imageIndex: imageIds.length - 1 - imageIndex,
137
+ });
138
+ enabledElement.viewport.render();
139
+ } else {
140
+ console.warn('No bidirectional found');
141
+ }
142
+
143
+ return newBidirectional;
144
+ }
145
+
146
+ export function defaultGetSegment(
147
+ enabledElement: Types.IEnabledElement,
148
+ configuration: SegmentContourActionConfiguration
149
+ ): Segment {
150
+ const segmentationsList = segmentation.state.getSegmentations();
151
+ if (!segmentationsList.length) {
152
+ return;
153
+ }
154
+ const segmentationId =
155
+ configuration.segmentationId || segmentationsList[0].segmentationId;
156
+ const segmentIndex =
157
+ configuration.segmentIndex ??
158
+ segmentation.segmentIndex.getActiveSegmentIndex(segmentationId);
159
+ if (!segmentIndex) {
160
+ return;
161
+ }
162
+ const segmentData = configuration.segmentData?.get(segmentIndex);
163
+ return {
164
+ label: `Segment ${segmentIndex}`,
165
+ segmentIndex,
166
+ segmentationId,
167
+ ...segmentData,
168
+ };
169
+ }