@cornerstonejs/tools 1.59.0 → 1.59.1

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 (61) hide show
  1. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.js +14 -2
  2. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.js.map +1 -1
  3. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +8 -15
  4. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js.map +1 -1
  5. package/dist/cjs/types/ContourAnnotation.d.ts +1 -0
  6. package/dist/cjs/utilities/math/polyline/combinePolyline.js +2 -2
  7. package/dist/cjs/utilities/math/polyline/containsPoint.d.ts +4 -1
  8. package/dist/cjs/utilities/math/polyline/containsPoint.js +11 -1
  9. package/dist/cjs/utilities/math/polyline/containsPoint.js.map +1 -1
  10. package/dist/cjs/utilities/math/polyline/index.d.ts +2 -1
  11. package/dist/cjs/utilities/math/polyline/index.js +3 -1
  12. package/dist/cjs/utilities/math/polyline/index.js.map +1 -1
  13. package/dist/cjs/utilities/math/polyline/isPointInsidePolyline3D.d.ts +3 -1
  14. package/dist/cjs/utilities/math/polyline/isPointInsidePolyline3D.js +17 -18
  15. package/dist/cjs/utilities/math/polyline/isPointInsidePolyline3D.js.map +1 -1
  16. package/dist/cjs/utilities/math/polyline/projectTo2D.d.ts +5 -0
  17. package/dist/cjs/utilities/math/polyline/projectTo2D.js +30 -0
  18. package/dist/cjs/utilities/math/polyline/projectTo2D.js.map +1 -0
  19. package/dist/cjs/workers/polySegConverters.js +32 -6
  20. package/dist/cjs/workers/polySegConverters.js.map +1 -1
  21. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.js +13 -2
  22. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.js.map +1 -1
  23. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +8 -15
  24. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js.map +1 -1
  25. package/dist/esm/utilities/math/polyline/combinePolyline.js +2 -2
  26. package/dist/esm/utilities/math/polyline/containsPoint.js +11 -1
  27. package/dist/esm/utilities/math/polyline/containsPoint.js.map +1 -1
  28. package/dist/esm/utilities/math/polyline/index.js +2 -1
  29. package/dist/esm/utilities/math/polyline/index.js.map +1 -1
  30. package/dist/esm/utilities/math/polyline/isPointInsidePolyline3D.js +17 -18
  31. package/dist/esm/utilities/math/polyline/isPointInsidePolyline3D.js.map +1 -1
  32. package/dist/esm/utilities/math/polyline/projectTo2D.js +26 -0
  33. package/dist/esm/utilities/math/polyline/projectTo2D.js.map +1 -0
  34. package/dist/esm/workers/polySegConverters.js +33 -7
  35. package/dist/esm/workers/polySegConverters.js.map +1 -1
  36. package/dist/types/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.d.ts.map +1 -1
  37. package/dist/types/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.d.ts.map +1 -1
  38. package/dist/types/types/ContourAnnotation.d.ts +1 -0
  39. package/dist/types/types/ContourAnnotation.d.ts.map +1 -1
  40. package/dist/types/utilities/math/polyline/containsPoint.d.ts +4 -1
  41. package/dist/types/utilities/math/polyline/containsPoint.d.ts.map +1 -1
  42. package/dist/types/utilities/math/polyline/index.d.ts +2 -1
  43. package/dist/types/utilities/math/polyline/index.d.ts.map +1 -1
  44. package/dist/types/utilities/math/polyline/isPointInsidePolyline3D.d.ts +3 -1
  45. package/dist/types/utilities/math/polyline/isPointInsidePolyline3D.d.ts.map +1 -1
  46. package/dist/types/utilities/math/polyline/projectTo2D.d.ts +6 -0
  47. package/dist/types/utilities/math/polyline/projectTo2D.d.ts.map +1 -0
  48. package/dist/umd/985.index.js +1 -1
  49. package/dist/umd/985.index.js.map +1 -1
  50. package/dist/umd/index.js +1 -1
  51. package/dist/umd/index.js.map +1 -1
  52. package/package.json +3 -3
  53. package/src/stateManagement/segmentation/polySeg/Labelmap/convertContourToLabelmap.ts +27 -4
  54. package/src/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.ts +9 -15
  55. package/src/types/ContourAnnotation.ts +1 -0
  56. package/src/utilities/math/polyline/combinePolyline.ts +2 -2
  57. package/src/utilities/math/polyline/containsPoint.ts +16 -1
  58. package/src/utilities/math/polyline/index.ts +2 -0
  59. package/src/utilities/math/polyline/isPointInsidePolyline3D.ts +21 -27
  60. package/src/utilities/math/polyline/projectTo2D.ts +52 -0
  61. package/src/workers/polySegConverters.js +51 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.59.0",
3
+ "version": "1.59.1",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
30
30
  },
31
31
  "dependencies": {
32
- "@cornerstonejs/core": "^1.59.0",
32
+ "@cornerstonejs/core": "^1.59.1",
33
33
  "@icr/polyseg-wasm": "0.4.0",
34
34
  "@types/offscreencanvas": "2019.7.3",
35
35
  "comlink": "^4.4.1",
@@ -59,5 +59,5 @@
59
59
  "type": "individual",
60
60
  "url": "https://ohif.org/donate"
61
61
  },
62
- "gitHead": "85e398173df425f594c5ebd4fb5cd96da9d7dbd4"
62
+ "gitHead": "f8957765edd911ce0e78ed21f8de92ca4b01f516"
63
63
  }
@@ -13,6 +13,7 @@ import {
13
13
  } from '@cornerstonejs/core';
14
14
  import {
15
15
  Annotation,
16
+ ContourAnnotation,
16
17
  ContourSegmentationData,
17
18
  PolySegConversionOptions,
18
19
  } from '../../../../types';
@@ -256,14 +257,36 @@ function _getAnnotationMapFromSegmentation(
256
257
  ? options.segmentIndices
257
258
  : Array.from(annotationMap.keys());
258
259
 
259
- const annotationUIDsInSegmentMap = new Map<number, Annotation[]>();
260
+ const annotationUIDsInSegmentMap = new Map<number, any>();
260
261
  segmentIndices.forEach((index) => {
261
262
  const annotationUIDsInSegment = annotationMap.get(index);
262
263
 
263
- const annotations = Array.from(annotationUIDsInSegment).map((uid) => {
264
- const annotation = getAnnotation(uid);
264
+ // Todo: there is a bug right now where the annotationUIDsInSegment has both
265
+ // children and parent annotations, so we need to filter out the parent
266
+ // annotations only
265
267
 
266
- return annotation;
268
+ let uids = Array.from(annotationUIDsInSegment);
269
+
270
+ uids = uids.filter(
271
+ (uid) => !(getAnnotation(uid) as Annotation).parentAnnotationUID
272
+ );
273
+
274
+ const annotations = uids.map((uid) => {
275
+ const annotation = getAnnotation(uid) as ContourAnnotation;
276
+ const hasChildAnnotations = annotation.childAnnotationUIDs?.length;
277
+
278
+ return {
279
+ polyline: annotation.data.contour.polyline,
280
+ referencedImageId: annotation.metadata.referencedImageId,
281
+ holesPolyline:
282
+ hasChildAnnotations &&
283
+ annotation.childAnnotationUIDs.map((childUID) => {
284
+ const childAnnotation = getAnnotation(
285
+ childUID
286
+ ) as ContourAnnotation;
287
+ return childAnnotation.data.contour.polyline;
288
+ }),
289
+ };
267
290
  });
268
291
 
269
292
  annotationUIDsInSegmentMap.set(index, annotations);
@@ -86,21 +86,15 @@ async function computeLabelmapFromContourSegmentation(
86
86
  const segmentation = getSegmentation(segmentationId);
87
87
  const representationData = segmentation.representationData.CONTOUR;
88
88
 
89
- let result;
90
-
91
- if (isVolume) {
92
- result = await convertContourToVolumeLabelmap(representationData, {
93
- segmentIndices,
94
- segmentationRepresentationUID: options.segmentationRepresentationUID,
95
- viewport: options.viewport,
96
- });
97
- } else {
98
- result = await convertContourToStackLabelmap(representationData, {
99
- segmentIndices,
100
- segmentationRepresentationUID: options.segmentationRepresentationUID,
101
- viewport: options.viewport,
102
- });
103
- }
89
+ const convertFunction = isVolume
90
+ ? convertContourToVolumeLabelmap
91
+ : convertContourToStackLabelmap;
92
+
93
+ const result = await convertFunction(representationData, {
94
+ segmentIndices,
95
+ segmentationRepresentationUID: options.segmentationRepresentationUID,
96
+ viewport: options.viewport,
97
+ });
104
98
 
105
99
  return result;
106
100
  }
@@ -21,6 +21,7 @@ export type ContourAnnotationData = {
21
21
  windingDirection?: ContourWindingDirection;
22
22
  };
23
23
  };
24
+ onInterpolationComplete?: () => void;
24
25
  };
25
26
 
26
27
  export type ContourAnnotation = Annotation & ContourAnnotationData;
@@ -74,9 +74,9 @@ function getSourceAndTargetPointsList(
74
74
  PolylineIntersectionPoint[]
75
75
  >();
76
76
 
77
- const isFisrtPointInside = containsPoint(sourcePolyline, targetPolyline[0]);
77
+ const isFirstPointInside = containsPoint(sourcePolyline, targetPolyline[0]);
78
78
 
79
- let intersectionPointDirection = isFisrtPointInside
79
+ let intersectionPointDirection = isFirstPointInside
80
80
  ? PolylinePointDirection.Exiting
81
81
  : PolylinePointDirection.Entering;
82
82
 
@@ -17,7 +17,12 @@ import isClosed from './isClosed';
17
17
  export default function containsPoint(
18
18
  polyline: Types.Point2[],
19
19
  point: Types.Point2,
20
- closed?: boolean
20
+ options: {
21
+ closed?: boolean;
22
+ holes?: Types.Point2[][];
23
+ } = {
24
+ closed: undefined,
25
+ }
21
26
  ): boolean {
22
27
  if (polyline.length < 3) {
23
28
  return false;
@@ -26,6 +31,16 @@ export default function containsPoint(
26
31
  const numPolylinePoints = polyline.length;
27
32
  let numIntersections = 0;
28
33
 
34
+ const { closed, holes } = options;
35
+
36
+ if (holes?.length) {
37
+ for (const hole of holes) {
38
+ if (containsPoint(hole, point)) {
39
+ return false;
40
+ }
41
+ }
42
+ }
43
+
29
44
  // Test intersection against [end, start] line segment if it should be closed
30
45
  const shouldClose = !(closed === undefined ? isClosed(polyline) : closed);
31
46
  const maxSegmentIndex = polyline.length - (shouldClose ? 1 : 2);
@@ -19,6 +19,7 @@ import pointsAreWithinCloseContourProximity from './pointsAreWithinCloseContourP
19
19
  import addCanvasPointsToArray from './addCanvasPointsToArray';
20
20
  import pointCanProjectOnLine from './pointCanProjectOnLine';
21
21
  import { isPointInsidePolyline3D } from './isPointInsidePolyline3D';
22
+ import { projectTo2D } from './projectTo2D';
22
23
 
23
24
  export {
24
25
  isClosed,
@@ -43,4 +44,5 @@ export {
43
44
  mergePolylines,
44
45
  subtractPolylines,
45
46
  isPointInsidePolyline3D,
47
+ projectTo2D,
46
48
  };
@@ -1,5 +1,6 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import containsPoint from './containsPoint';
3
+ import { projectTo2D } from './projectTo2D';
3
4
 
4
5
  /**
5
6
  * Determines whether a 3D point is inside a polyline in 3D space.
@@ -9,42 +10,35 @@ import containsPoint from './containsPoint';
9
10
  *
10
11
  * @param point - The 3D point to test.
11
12
  * @param polyline - The polyline represented as an array of 3D points.
13
+ * @param options.holesPolyline - An array of polylines representing each hole, so it
14
+ * is an array of arrays of 3D points.
12
15
  * @returns A boolean indicating whether the point is inside the polyline.
13
16
  * @throws An error if a shared dimension index cannot be found for the polyline points.
14
17
  */
15
18
  export function isPointInsidePolyline3D(
16
19
  point: Types.Point3,
17
- polyline: Types.Point3[]
20
+ polyline: Types.Point3[],
21
+ options: { holes?: Types.Point3[][] } = {}
18
22
  ) {
19
- // Todo: handle oblique planes
23
+ const { sharedDimensionIndex, projectedPolyline } = projectTo2D(polyline);
20
24
 
21
- // We need to reduce one dimension to 2D, so basically
22
- // we need to find the dimension index that is shared by all points
23
- // Use the first three points, two is enough but three is more robust
24
- let sharedDimensionIndex;
25
+ const { holes } = options;
26
+ const projectedHoles = [] as Types.Point2[][];
25
27
 
26
- const testPoints = polyline.slice(0, 3);
27
- for (let i = 0; i < 3; i++) {
28
- if (testPoints.every((point, index, array) => point[i] === array[0][i])) {
29
- sharedDimensionIndex = i;
30
- break;
31
- }
32
- }
28
+ if (holes) {
29
+ for (let i = 0; i < holes.length; i++) {
30
+ const hole = holes[i];
31
+ const hole2D = [] as Types.Point2[];
33
32
 
34
- if (sharedDimensionIndex === undefined) {
35
- throw new Error(
36
- 'Cannot find a shared dimension index for polyline, probably oblique plane'
37
- );
38
- }
33
+ for (let j = 0; j < hole.length; j++) {
34
+ hole2D.push([
35
+ hole[j][(sharedDimensionIndex + 1) % 3],
36
+ hole[j][(sharedDimensionIndex + 2) % 3],
37
+ ]);
38
+ }
39
39
 
40
- // convert polyline list and point to 2D
41
- const points2D = [] as Types.Point2[];
42
-
43
- for (let i = 0; i < polyline.length; i++) {
44
- points2D.push([
45
- polyline[i][(sharedDimensionIndex + 1) % 3],
46
- polyline[i][(sharedDimensionIndex + 2) % 3],
47
- ]);
40
+ projectedHoles.push(hole2D);
41
+ }
48
42
  }
49
43
 
50
44
  const point2D = [
@@ -52,5 +46,5 @@ export function isPointInsidePolyline3D(
52
46
  point[(sharedDimensionIndex + 2) % 3],
53
47
  ] as Types.Point2;
54
48
 
55
- return containsPoint(points2D, point2D);
49
+ return containsPoint(projectedPolyline, point2D, { holes: projectedHoles });
56
50
  }
@@ -0,0 +1,52 @@
1
+ import { utilities } from '@cornerstonejs/core';
2
+ import type { Types } from '@cornerstonejs/core';
3
+
4
+ const epsilon = 1e-6;
5
+
6
+ /**
7
+ * Projects a polyline from 3D to 2D by reducing one dimension.
8
+ *
9
+ * @param polyline - The polyline to be projected.
10
+ * @returns An object containing the shared dimension index and the projected polyline in 2D.
11
+ * @throws Error if a shared dimension index cannot be found for the polyline.
12
+ */
13
+ export function projectTo2D(polyline: Types.Point3[]) {
14
+ // We need to reduce one dimension to 2D, so basically
15
+ // we need to find the dimension index that is shared by all points
16
+ // Use the first three points, two is enough but three is more robust
17
+ let sharedDimensionIndex;
18
+
19
+ const testPoints = utilities.getRandomSampleFromArray(polyline, 50);
20
+
21
+ for (let i = 0; i < 3; i++) {
22
+ if (
23
+ testPoints.every(
24
+ (point, index, array) => Math.abs(point[i] - array[0][i]) < epsilon
25
+ )
26
+ ) {
27
+ sharedDimensionIndex = i;
28
+ break;
29
+ }
30
+ }
31
+
32
+ if (sharedDimensionIndex === undefined) {
33
+ throw new Error(
34
+ 'Cannot find a shared dimension index for polyline, probably oblique plane'
35
+ );
36
+ }
37
+
38
+ // convert polyline list and point to 2D
39
+ const points2D = [] as Types.Point2[];
40
+
41
+ const firstDim = (sharedDimensionIndex + 1) % 3;
42
+ const secondDim = (sharedDimensionIndex + 2) % 3;
43
+
44
+ for (let i = 0; i < polyline.length; i++) {
45
+ points2D.push([polyline[i][firstDim], polyline[i][secondDim]]);
46
+ }
47
+
48
+ return {
49
+ sharedDimensionIndex,
50
+ projectedPolyline: points2D,
51
+ };
52
+ }
@@ -10,7 +10,12 @@ import vtkCutter from '@kitware/vtk.js/Filters/Core/Cutter';
10
10
 
11
11
  import { getBoundingBoxAroundShapeWorld } from '../utilities/boundingBox';
12
12
  import { pointInShapeCallback } from '../utilities';
13
- import { getAABB, isPointInsidePolyline3D } from '../utilities/math/polyline';
13
+ import {
14
+ containsPoint,
15
+ getAABB,
16
+ isPointInsidePolyline3D,
17
+ projectTo2D,
18
+ } from '../utilities/math/polyline';
14
19
  import { isPlaneIntersectingAABB } from '../utilities/planar';
15
20
 
16
21
  /**
@@ -161,9 +166,12 @@ const polySegConverters = {
161
166
  const annotations = annotationUIDsInSegmentMap.get(index);
162
167
 
163
168
  for (const annotation of annotations) {
164
- const bounds = getBoundingBoxAroundShapeWorld(
165
- annotation.data.contour.polyline
166
- );
169
+ if (!annotation.polyline) {
170
+ continue;
171
+ }
172
+
173
+ const { polyline, holesPolyline } = annotation;
174
+ const bounds = getBoundingBoxAroundShapeWorld(polyline);
167
175
 
168
176
  const [iMin, jMin, kMin] = utilities.transformWorldToIndex(imageData, [
169
177
  bounds[0][0],
@@ -177,15 +185,29 @@ const polySegConverters = {
177
185
  bounds[2][1],
178
186
  ]);
179
187
 
188
+ const { projectedPolyline, sharedDimensionIndex } =
189
+ projectTo2D(polyline);
190
+
191
+ const holes = holesPolyline?.map((hole) => {
192
+ const { projectedPolyline: projectedHole } = projectTo2D(hole);
193
+ return projectedHole;
194
+ });
195
+
196
+ const firstDim = (sharedDimensionIndex + 1) % 3;
197
+ const secondDim = (sharedDimensionIndex + 2) % 3;
198
+
180
199
  // Run the pointInShapeCallback for the combined bounding box
181
200
  pointInShapeCallback(
182
201
  imageData,
183
202
  (pointLPS) => {
203
+ const point2D = [pointLPS[firstDim], pointLPS[secondDim]];
204
+
184
205
  // Check if the point is inside any of the polylines for this segment
185
- return isPointInsidePolyline3D(
186
- pointLPS,
187
- annotation.data.contour.polyline
188
- );
206
+ const isInside = containsPoint(projectedPolyline, point2D, {
207
+ holes,
208
+ });
209
+
210
+ return isInside;
189
211
  },
190
212
  ({ pointIJK }) => {
191
213
  segmentationVoxelManager.setAtIJKPoint(pointIJK, index);
@@ -250,14 +272,12 @@ const polySegConverters = {
250
272
  const annotations = annotationUIDsInSegmentMap.get(index);
251
273
 
252
274
  for (const annotation of annotations) {
253
- if (!annotation?.data) {
275
+ if (!annotation.polyline) {
254
276
  continue;
255
277
  }
256
- const bounds = getBoundingBoxAroundShapeWorld(
257
- annotation.data.contour.polyline
258
- );
259
278
 
260
- const { referencedImageId } = annotation.metadata;
279
+ const { polyline, holesPolyline, referencedImageId } = annotation;
280
+ const bounds = getBoundingBoxAroundShapeWorld(polyline);
261
281
 
262
282
  const { manager: segmentationVoxelManager, imageData } =
263
283
  segmentationVoxelManagers.get(referencedImageId);
@@ -274,15 +294,29 @@ const polySegConverters = {
274
294
  bounds[2][1],
275
295
  ]);
276
296
 
297
+ const { projectedPolyline, sharedDimensionIndex } =
298
+ projectTo2D(polyline);
299
+
300
+ const holes = holesPolyline?.map((hole) => {
301
+ const { projectedPolyline: projectedHole } = projectTo2D(hole);
302
+ return projectedHole;
303
+ });
304
+
305
+ const firstDim = (sharedDimensionIndex + 1) % 3;
306
+ const secondDim = (sharedDimensionIndex + 2) % 3;
307
+
277
308
  // Run the pointInShapeCallback for the combined bounding box
278
309
  pointInShapeCallback(
279
310
  imageData,
280
311
  (pointLPS) => {
312
+ const point2D = [pointLPS[firstDim], pointLPS[secondDim]];
313
+
281
314
  // Check if the point is inside any of the polylines for this segment
282
- return isPointInsidePolyline3D(
283
- pointLPS,
284
- annotation.data.contour.polyline
285
- );
315
+ const isInside = containsPoint(projectedPolyline, point2D, {
316
+ holes,
317
+ });
318
+
319
+ return isInside;
286
320
  },
287
321
  ({ pointIJK }) => {
288
322
  segmentationVoxelManager.setAtIJKPoint(pointIJK, index);