@cornerstonejs/tools 1.40.3 → 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 (211) hide show
  1. package/dist/cjs/drawingSvg/drawHandle.d.ts +4 -0
  2. package/dist/cjs/drawingSvg/drawHandle.js +66 -0
  3. package/dist/cjs/drawingSvg/drawHandle.js.map +1 -0
  4. package/dist/cjs/drawingSvg/drawHandles.js +4 -60
  5. package/dist/cjs/drawingSvg/drawHandles.js.map +1 -1
  6. package/dist/cjs/drawingSvg/index.d.ts +2 -1
  7. package/dist/cjs/drawingSvg/index.js +3 -1
  8. package/dist/cjs/drawingSvg/index.js.map +1 -1
  9. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js +2 -1
  10. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  11. package/dist/cjs/index.d.ts +2 -2
  12. package/dist/cjs/index.js +3 -2
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/stateManagement/annotation/annotationState.js +3 -0
  15. package/dist/cjs/stateManagement/annotation/annotationState.js.map +1 -1
  16. package/dist/cjs/tools/annotation/AngleTool.js.map +1 -1
  17. package/dist/cjs/tools/annotation/BidirectionalTool.js +7 -6
  18. package/dist/cjs/tools/annotation/BidirectionalTool.js.map +1 -1
  19. package/dist/cjs/tools/annotation/LengthTool.js +4 -3
  20. package/dist/cjs/tools/annotation/LengthTool.js.map +1 -1
  21. package/dist/cjs/tools/annotation/ProbeTool.js +24 -3
  22. package/dist/cjs/tools/annotation/ProbeTool.js.map +1 -1
  23. package/dist/cjs/tools/annotation/UltrasoundDirectionalTool.d.ts +36 -0
  24. package/dist/cjs/tools/annotation/UltrasoundDirectionalTool.js +483 -0
  25. package/dist/cjs/tools/annotation/UltrasoundDirectionalTool.js.map +1 -0
  26. package/dist/cjs/tools/index.d.ts +2 -1
  27. package/dist/cjs/tools/index.js +3 -1
  28. package/dist/cjs/tools/index.js.map +1 -1
  29. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +28 -0
  30. package/dist/cjs/types/index.d.ts +2 -1
  31. package/dist/cjs/utilities/contours/AnnotationToPointData.d.ts +11 -0
  32. package/dist/cjs/utilities/contours/AnnotationToPointData.js +44 -0
  33. package/dist/cjs/utilities/contours/AnnotationToPointData.js.map +1 -0
  34. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.d.ts +6 -0
  35. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.js +43 -0
  36. package/dist/cjs/utilities/contours/RectangleROIStartEndThreshold.js.map +1 -0
  37. package/dist/cjs/utilities/contours/contourFinder.d.ts +7 -0
  38. package/dist/cjs/utilities/contours/contourFinder.js +68 -0
  39. package/dist/cjs/utilities/contours/contourFinder.js.map +1 -0
  40. package/dist/cjs/utilities/contours/detectContourHoles.d.ts +5 -0
  41. package/dist/cjs/utilities/contours/detectContourHoles.js +78 -0
  42. package/dist/cjs/utilities/contours/detectContourHoles.js.map +1 -0
  43. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.d.ts +4 -0
  44. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.js +124 -0
  45. package/dist/cjs/utilities/contours/generateContourSetsFromLabelmap.js.map +1 -0
  46. package/dist/cjs/utilities/contours/index.d.ts +6 -0
  47. package/dist/cjs/utilities/contours/index.js +17 -0
  48. package/dist/cjs/utilities/contours/index.js.map +1 -0
  49. package/dist/cjs/utilities/contours/mergePoints.d.ts +8 -0
  50. package/dist/cjs/utilities/contours/mergePoints.js +77 -0
  51. package/dist/cjs/utilities/contours/mergePoints.js.map +1 -0
  52. package/dist/cjs/utilities/getCalibratedUnits.d.ts +16 -2
  53. package/dist/cjs/utilities/getCalibratedUnits.js +127 -5
  54. package/dist/cjs/utilities/getCalibratedUnits.js.map +1 -1
  55. package/dist/cjs/utilities/index.d.ts +2 -1
  56. package/dist/cjs/utilities/index.js +3 -1
  57. package/dist/cjs/utilities/index.js.map +1 -1
  58. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.d.ts +1 -0
  59. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.js +31 -0
  60. package/dist/cjs/utilities/segmentation/contourAndFindLargestBidirectional.js.map +1 -0
  61. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.d.ts +14 -0
  62. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.js +43 -0
  63. package/dist/cjs/utilities/segmentation/createBidirectionalToolData.js.map +1 -0
  64. package/dist/cjs/utilities/segmentation/findLargestBidirectional.d.ts +1 -0
  65. package/dist/cjs/utilities/segmentation/findLargestBidirectional.js +94 -0
  66. package/dist/cjs/utilities/segmentation/findLargestBidirectional.js.map +1 -0
  67. package/dist/cjs/utilities/segmentation/index.d.ts +4 -1
  68. package/dist/cjs/utilities/segmentation/index.js +7 -1
  69. package/dist/cjs/utilities/segmentation/index.js.map +1 -1
  70. package/dist/cjs/utilities/segmentation/isLineInSegment.d.ts +9 -0
  71. package/dist/cjs/utilities/segmentation/isLineInSegment.js +55 -0
  72. package/dist/cjs/utilities/segmentation/isLineInSegment.js.map +1 -0
  73. package/dist/cjs/utilities/segmentation/segmentContourAction.d.ts +17 -0
  74. package/dist/cjs/utilities/segmentation/segmentContourAction.js +122 -0
  75. package/dist/cjs/utilities/segmentation/segmentContourAction.js.map +1 -0
  76. package/dist/esm/drawingSvg/drawHandle.js +61 -0
  77. package/dist/esm/drawingSvg/drawHandle.js.map +1 -0
  78. package/dist/esm/drawingSvg/drawHandles.js +4 -60
  79. package/dist/esm/drawingSvg/drawHandles.js.map +1 -1
  80. package/dist/esm/drawingSvg/index.js +2 -1
  81. package/dist/esm/drawingSvg/index.js.map +1 -1
  82. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js +2 -1
  83. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  84. package/dist/esm/index.js +2 -2
  85. package/dist/esm/index.js.map +1 -1
  86. package/dist/esm/stateManagement/annotation/annotationState.js +3 -0
  87. package/dist/esm/stateManagement/annotation/annotationState.js.map +1 -1
  88. package/dist/esm/tools/annotation/AngleTool.js.map +1 -1
  89. package/dist/esm/tools/annotation/BidirectionalTool.js +7 -6
  90. package/dist/esm/tools/annotation/BidirectionalTool.js.map +1 -1
  91. package/dist/esm/tools/annotation/LengthTool.js +5 -4
  92. package/dist/esm/tools/annotation/LengthTool.js.map +1 -1
  93. package/dist/esm/tools/annotation/ProbeTool.js +24 -3
  94. package/dist/esm/tools/annotation/ProbeTool.js.map +1 -1
  95. package/dist/esm/tools/annotation/UltrasoundDirectionalTool.js +478 -0
  96. package/dist/esm/tools/annotation/UltrasoundDirectionalTool.js.map +1 -0
  97. package/dist/esm/tools/index.js +2 -1
  98. package/dist/esm/tools/index.js.map +1 -1
  99. package/dist/esm/utilities/contours/AnnotationToPointData.js +39 -0
  100. package/dist/esm/utilities/contours/AnnotationToPointData.js.map +1 -0
  101. package/dist/esm/utilities/contours/RectangleROIStartEndThreshold.js +41 -0
  102. package/dist/esm/utilities/contours/RectangleROIStartEndThreshold.js.map +1 -0
  103. package/dist/esm/utilities/contours/contourFinder.js +63 -0
  104. package/dist/esm/utilities/contours/contourFinder.js.map +1 -0
  105. package/dist/esm/utilities/contours/detectContourHoles.js +74 -0
  106. package/dist/esm/utilities/contours/detectContourHoles.js.map +1 -0
  107. package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +117 -0
  108. package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js.map +1 -0
  109. package/dist/esm/utilities/contours/index.js +7 -0
  110. package/dist/esm/utilities/contours/index.js.map +1 -0
  111. package/dist/esm/utilities/contours/mergePoints.js +73 -0
  112. package/dist/esm/utilities/contours/mergePoints.js.map +1 -0
  113. package/dist/esm/utilities/getCalibratedUnits.js +125 -6
  114. package/dist/esm/utilities/getCalibratedUnits.js.map +1 -1
  115. package/dist/esm/utilities/index.js +2 -1
  116. package/dist/esm/utilities/index.js.map +1 -1
  117. package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js +25 -0
  118. package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js.map +1 -0
  119. package/dist/esm/utilities/segmentation/createBidirectionalToolData.js +40 -0
  120. package/dist/esm/utilities/segmentation/createBidirectionalToolData.js.map +1 -0
  121. package/dist/esm/utilities/segmentation/findLargestBidirectional.js +96 -0
  122. package/dist/esm/utilities/segmentation/findLargestBidirectional.js.map +1 -0
  123. package/dist/esm/utilities/segmentation/index.js +4 -1
  124. package/dist/esm/utilities/segmentation/index.js.map +1 -1
  125. package/dist/esm/utilities/segmentation/isLineInSegment.js +50 -0
  126. package/dist/esm/utilities/segmentation/isLineInSegment.js.map +1 -0
  127. package/dist/esm/utilities/segmentation/segmentContourAction.js +98 -0
  128. package/dist/esm/utilities/segmentation/segmentContourAction.js.map +1 -0
  129. package/dist/types/drawingSvg/drawHandle.d.ts +5 -0
  130. package/dist/types/drawingSvg/drawHandle.d.ts.map +1 -0
  131. package/dist/types/drawingSvg/drawHandles.d.ts.map +1 -1
  132. package/dist/types/drawingSvg/index.d.ts +2 -1
  133. package/dist/types/drawingSvg/index.d.ts.map +1 -1
  134. package/dist/types/eventDispatchers/keyboardEventHandlers/keyDown.d.ts.map +1 -1
  135. package/dist/types/index.d.ts +2 -2
  136. package/dist/types/index.d.ts.map +1 -1
  137. package/dist/types/stateManagement/annotation/annotationState.d.ts.map +1 -1
  138. package/dist/types/tools/annotation/AngleTool.d.ts.map +1 -1
  139. package/dist/types/tools/annotation/BidirectionalTool.d.ts.map +1 -1
  140. package/dist/types/tools/annotation/LengthTool.d.ts.map +1 -1
  141. package/dist/types/tools/annotation/ProbeTool.d.ts.map +1 -1
  142. package/dist/types/tools/annotation/UltrasoundDirectionalTool.d.ts +37 -0
  143. package/dist/types/tools/annotation/UltrasoundDirectionalTool.d.ts.map +1 -0
  144. package/dist/types/tools/index.d.ts +2 -1
  145. package/dist/types/tools/index.d.ts.map +1 -1
  146. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +28 -0
  147. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
  148. package/dist/types/types/index.d.ts +2 -1
  149. package/dist/types/types/index.d.ts.map +1 -1
  150. package/dist/types/utilities/contours/AnnotationToPointData.d.ts +12 -0
  151. package/dist/types/utilities/contours/AnnotationToPointData.d.ts.map +1 -0
  152. package/dist/types/utilities/contours/RectangleROIStartEndThreshold.d.ts +7 -0
  153. package/dist/types/utilities/contours/RectangleROIStartEndThreshold.d.ts.map +1 -0
  154. package/dist/types/utilities/contours/contourFinder.d.ts +8 -0
  155. package/dist/types/utilities/contours/contourFinder.d.ts.map +1 -0
  156. package/dist/types/utilities/contours/detectContourHoles.d.ts +6 -0
  157. package/dist/types/utilities/contours/detectContourHoles.d.ts.map +1 -0
  158. package/dist/types/utilities/contours/generateContourSetsFromLabelmap.d.ts +5 -0
  159. package/dist/types/utilities/contours/generateContourSetsFromLabelmap.d.ts.map +1 -0
  160. package/dist/types/utilities/contours/index.d.ts +7 -0
  161. package/dist/types/utilities/contours/index.d.ts.map +1 -0
  162. package/dist/types/utilities/contours/mergePoints.d.ts +9 -0
  163. package/dist/types/utilities/contours/mergePoints.d.ts.map +1 -0
  164. package/dist/types/utilities/getCalibratedUnits.d.ts +16 -2
  165. package/dist/types/utilities/getCalibratedUnits.d.ts.map +1 -1
  166. package/dist/types/utilities/index.d.ts +2 -1
  167. package/dist/types/utilities/index.d.ts.map +1 -1
  168. package/dist/types/utilities/segmentation/contourAndFindLargestBidirectional.d.ts +2 -0
  169. package/dist/types/utilities/segmentation/contourAndFindLargestBidirectional.d.ts.map +1 -0
  170. package/dist/types/utilities/segmentation/createBidirectionalToolData.d.ts +15 -0
  171. package/dist/types/utilities/segmentation/createBidirectionalToolData.d.ts.map +1 -0
  172. package/dist/types/utilities/segmentation/findLargestBidirectional.d.ts +2 -0
  173. package/dist/types/utilities/segmentation/findLargestBidirectional.d.ts.map +1 -0
  174. package/dist/types/utilities/segmentation/index.d.ts +4 -1
  175. package/dist/types/utilities/segmentation/index.d.ts.map +1 -1
  176. package/dist/types/utilities/segmentation/isLineInSegment.d.ts +10 -0
  177. package/dist/types/utilities/segmentation/isLineInSegment.d.ts.map +1 -0
  178. package/dist/types/utilities/segmentation/segmentContourAction.d.ts +18 -0
  179. package/dist/types/utilities/segmentation/segmentContourAction.d.ts.map +1 -0
  180. package/dist/umd/index.js +1 -1
  181. package/dist/umd/index.js.map +1 -1
  182. package/package.json +3 -3
  183. package/src/drawingSvg/drawHandle.ts +88 -0
  184. package/src/drawingSvg/drawHandles.ts +9 -75
  185. package/src/drawingSvg/index.ts +2 -0
  186. package/src/eventDispatchers/keyboardEventHandlers/keyDown.ts +7 -1
  187. package/src/index.ts +2 -0
  188. package/src/stateManagement/annotation/annotationState.ts +3 -0
  189. package/src/tools/annotation/AngleTool.ts +0 -1
  190. package/src/tools/annotation/BidirectionalTool.ts +9 -5
  191. package/src/tools/annotation/LengthTool.ts +6 -8
  192. package/src/tools/annotation/ProbeTool.ts +31 -7
  193. package/src/tools/annotation/UltrasoundDirectionalTool.ts +916 -0
  194. package/src/tools/index.ts +2 -0
  195. package/src/types/ToolSpecificAnnotationTypes.ts +29 -0
  196. package/src/types/index.ts +2 -0
  197. package/src/utilities/contours/AnnotationToPointData.ts +61 -0
  198. package/src/utilities/contours/RectangleROIStartEndThreshold.ts +60 -0
  199. package/src/utilities/contours/contourFinder.ts +78 -0
  200. package/src/utilities/contours/detectContourHoles.ts +147 -0
  201. package/src/utilities/contours/generateContourSetsFromLabelmap.ts +160 -0
  202. package/src/utilities/contours/index.ts +14 -0
  203. package/src/utilities/contours/mergePoints.ts +108 -0
  204. package/src/utilities/getCalibratedUnits.ts +203 -7
  205. package/src/utilities/index.ts +2 -0
  206. package/src/utilities/segmentation/contourAndFindLargestBidirectional.ts +46 -0
  207. package/src/utilities/segmentation/createBidirectionalToolData.ts +68 -0
  208. package/src/utilities/segmentation/findLargestBidirectional.ts +159 -0
  209. package/src/utilities/segmentation/index.ts +6 -0
  210. package/src/utilities/segmentation/isLineInSegment.ts +84 -0
  211. package/src/utilities/segmentation/segmentContourAction.ts +169 -0
@@ -1,12 +1,31 @@
1
- import { Enums } from '@cornerstonejs/core';
1
+ import { Enums, utilities } from '@cornerstonejs/core';
2
2
 
3
3
  const { CalibrationTypes } = Enums;
4
4
  const PIXEL_UNITS = 'px';
5
5
 
6
+ const SUPPORTED_REGION_DATA_TYPES = [
7
+ 1, // Tissue
8
+ ];
9
+
10
+ const SUPPORTED_LENGTH_VARIANT = [
11
+ '3,3', // x: cm & y:cm
12
+ ];
13
+
14
+ const SUPPORTED_PROBE_VARIANT = [
15
+ '4,3', // x: seconds & y : cm
16
+ ];
17
+
18
+ const UNIT_MAPPING = {
19
+ 3: 'cm',
20
+ 4: 'seconds',
21
+ };
22
+
23
+ const EPS = 1e-3;
24
+
6
25
  /**
7
26
  * Extracts the length units and the type of calibration for those units
8
27
  * into the response. The length units will typically be either mm or px
9
- * while the calibration type can be any of a number of different calibraiton types.
28
+ * while the calibration type can be any of a number of different calibration types.
10
29
  *
11
30
  * Volumetric images have no calibration type, so are just the raw mm.
12
31
  *
@@ -23,14 +42,16 @@ const getCalibratedLengthUnits = (handles, image): string => {
23
42
  const { calibration, hasPixelSpacing } = image;
24
43
  // Anachronistic - moving to using calibration consistently, but not completed yet
25
44
  const units = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
26
- if (!calibration || !calibration.type) {
45
+ if (
46
+ !calibration ||
47
+ (!calibration.type && !calibration.sequenceOfUltrasoundRegions)
48
+ ) {
27
49
  return units;
28
50
  }
29
51
  if (calibration.type === CalibrationTypes.UNCALIBRATED) {
30
52
  return PIXEL_UNITS;
31
53
  }
32
- // TODO - handle US regions properly
33
- if (calibration.SequenceOfUltrasoundRegions) {
54
+ if (calibration.sequenceOfUltrasoundRegions) {
34
55
  return 'US Region';
35
56
  }
36
57
  return `${units} ${calibration.type}`;
@@ -46,7 +67,7 @@ const getCalibratedAreaUnits = (handles, image): string => {
46
67
  if (!calibration || !calibration.type) {
47
68
  return units;
48
69
  }
49
- if (calibration.SequenceOfUltrasoundRegions) {
70
+ if (calibration.sequenceOfUltrasoundRegions) {
50
71
  return 'US Region';
51
72
  }
52
73
  return `${units} ${calibration.type}`;
@@ -56,7 +77,180 @@ const getCalibratedAreaUnits = (handles, image): string => {
56
77
  * Gets the scale divisor for converting from internal spacing to
57
78
  * image spacing for calibrated images.
58
79
  */
59
- const getCalibratedScale = (image) => image.calibration?.scale || 1;
80
+ const getCalibratedScale = (image, handles = []) => {
81
+ if (image.calibration?.sequenceOfUltrasoundRegions) {
82
+ // image.spacing / image.us.space
83
+ } else if (image.calibration?.scale) {
84
+ return image.calibration.scale;
85
+ } else {
86
+ return 1;
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Extracts the calibrated length units, area units, and the scale
92
+ * for converting from internal spacing to image spacing.
93
+ *
94
+ * @param handles - to detect if spacing information is different between points
95
+ * @param image - to extract the calibration from
96
+ * @returns Object containing the units, area units, and scale
97
+ */
98
+ const getCalibratedLengthUnitsAndScale = (image, handles) => {
99
+ const [imageIndex1, imageIndex2] = handles;
100
+ const { calibration, hasPixelSpacing } = image;
101
+ let units = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
102
+ const areaUnits = units + SQUARE;
103
+ let scale = 1;
104
+ let calibrationType = '';
105
+
106
+ if (
107
+ !calibration ||
108
+ (!calibration.type && !calibration.sequenceOfUltrasoundRegions)
109
+ ) {
110
+ return { units, areaUnits, scale };
111
+ }
112
+
113
+ if (calibration.type === CalibrationTypes.UNCALIBRATED) {
114
+ return { units: PIXEL_UNITS, areaUnits: PIXEL_UNITS + SQUARE, scale };
115
+ }
116
+
117
+ if (calibration.sequenceOfUltrasoundRegions) {
118
+ let regions = calibration.sequenceOfUltrasoundRegions.filter(
119
+ (region) =>
120
+ imageIndex1[0] >= region.regionLocationMinX0 &&
121
+ imageIndex1[0] <= region.regionLocationMaxX1 &&
122
+ imageIndex1[1] >= region.regionLocationMinY0 &&
123
+ imageIndex1[1] <= region.regionLocationMaxY1 &&
124
+ imageIndex2[0] >= region.regionLocationMinX0 &&
125
+ imageIndex2[0] <= region.regionLocationMaxX1 &&
126
+ imageIndex2[1] >= region.regionLocationMinY0 &&
127
+ imageIndex2[1] <= region.regionLocationMaxY1
128
+ );
129
+
130
+ // If we are not in a region at all we should show the underlying calibration
131
+ // which might be the mm spacing for the image
132
+ if (!regions?.length) {
133
+ return { units, areaUnits, scale };
134
+ }
135
+
136
+ // if we are in a region then it is the question of whether we support it
137
+ // or not. If we do not support it we should show px
138
+
139
+ regions = regions.filter(
140
+ (region) =>
141
+ SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
142
+ SUPPORTED_LENGTH_VARIANT.includes(
143
+ `${region.physicalUnitXDirection},${region.physicalUnitYDirection}`
144
+ )
145
+ );
146
+
147
+ if (!regions.length) {
148
+ return { units: PIXEL_UNITS, areaUnits: PIXEL_UNITS + SQUARE, scale };
149
+ }
150
+
151
+ // Todo: expand on this logic
152
+ const region = regions[0];
153
+
154
+ const physicalDeltaX = Math.abs(region.physicalDeltaX);
155
+ const physicalDeltaY = Math.abs(region.physicalDeltaY);
156
+
157
+ // if we are in a supported region then we should check if the
158
+ // physicalDeltaX and physicalDeltaY are the same. If they are not
159
+ // then we should show px again, but if they are the same then we should
160
+ // show the units
161
+ const isSamePhysicalDelta = utilities.isEqual(
162
+ physicalDeltaX,
163
+ physicalDeltaY,
164
+ EPS
165
+ );
166
+
167
+ if (isSamePhysicalDelta) {
168
+ scale = 1 / (physicalDeltaX * physicalDeltaY * 100);
169
+ calibrationType = 'US Region';
170
+ units = 'mm';
171
+ } else {
172
+ return { units: PIXEL_UNITS, areaUnits: PIXEL_UNITS + SQUARE, scale };
173
+ }
174
+ } else if (calibration.scale) {
175
+ scale = calibration.scale;
176
+ }
177
+
178
+ return {
179
+ units: units + (calibrationType ? ` ${calibrationType}` : ''),
180
+ areaUnits: areaUnits + (calibrationType ? ` ${calibrationType}` : ''),
181
+ scale,
182
+ };
183
+ };
184
+
185
+ const getCalibratedProbeUnitsAndValue = (image, handles) => {
186
+ const [imageIndex] = handles;
187
+ const { calibration } = image;
188
+ let units = ['raw'];
189
+ let values = [null];
190
+ let calibrationType = '';
191
+
192
+ if (
193
+ !calibration ||
194
+ (!calibration.type && !calibration.sequenceOfUltrasoundRegions)
195
+ ) {
196
+ return { units, values };
197
+ // Todo: add support for other scenarios
198
+ }
199
+
200
+ if (calibration.sequenceOfUltrasoundRegions) {
201
+ // for Probe tool
202
+ const supportedRegionsMetadata =
203
+ calibration.sequenceOfUltrasoundRegions.filter(
204
+ (region) =>
205
+ SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
206
+ SUPPORTED_PROBE_VARIANT.includes(
207
+ `${region.physicalUnitXDirection},${region.physicalUnitYDirection}`
208
+ )
209
+ );
210
+
211
+ if (!supportedRegionsMetadata?.length) {
212
+ return { units, values };
213
+ }
214
+
215
+ const region = supportedRegionsMetadata.find(
216
+ (region) =>
217
+ imageIndex[0] >= region.regionLocationMinX0 &&
218
+ imageIndex[0] <= region.regionLocationMaxX1 &&
219
+ imageIndex[1] >= region.regionLocationMinY0 &&
220
+ imageIndex[1] <= region.regionLocationMaxY1
221
+ );
222
+
223
+ if (!region) {
224
+ return { units, values };
225
+ }
226
+
227
+ // Todo: I think this is a ok assumption for now that if the referencePixelX0 and referencePixelY0
228
+ // are not defined, then we can assume 0 for them
229
+ const { referencePixelX0 = 0, referencePixelY0 = 0 } = region;
230
+ const { physicalDeltaX, physicalDeltaY } = region;
231
+
232
+ const yValue =
233
+ (imageIndex[1] - region.regionLocationMinY0 - referencePixelY0) *
234
+ physicalDeltaY;
235
+
236
+ const xValue =
237
+ (imageIndex[0] - region.regionLocationMinX0 - referencePixelX0) *
238
+ physicalDeltaX;
239
+
240
+ calibrationType = 'US Region';
241
+ values = [xValue, yValue];
242
+ units = [
243
+ UNIT_MAPPING[region.physicalUnitXDirection],
244
+ UNIT_MAPPING[region.physicalUnitYDirection],
245
+ ];
246
+ }
247
+
248
+ return {
249
+ units,
250
+ values,
251
+ calibrationType,
252
+ };
253
+ };
60
254
 
61
255
  /** Gets the aspect ratio of the screen display relative to the image
62
256
  * display in order to square up measurement values.
@@ -71,6 +265,8 @@ export default getCalibratedLengthUnits;
71
265
  export {
72
266
  getCalibratedAreaUnits,
73
267
  getCalibratedLengthUnits,
268
+ getCalibratedLengthUnitsAndScale,
74
269
  getCalibratedScale,
75
270
  getCalibratedAspect,
271
+ getCalibratedProbeUnitsAndValue,
76
272
  };
@@ -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 };