@cornerstonejs/tools 1.54.2 → 1.55.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 (68) hide show
  1. package/dist/cjs/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js +72 -74
  2. package/dist/cjs/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js.map +1 -1
  3. package/dist/cjs/tools/annotation/LivewireContourTool.js +5 -2
  4. package/dist/cjs/tools/annotation/LivewireContourTool.js.map +1 -1
  5. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +4 -0
  6. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  7. package/dist/cjs/tools/annotation/SplineROITool.js +5 -2
  8. package/dist/cjs/tools/annotation/SplineROITool.js.map +1 -1
  9. package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js +2 -3
  10. package/dist/cjs/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
  11. package/dist/cjs/tools/base/ContourBaseTool.d.ts +8 -0
  12. package/dist/cjs/tools/base/ContourBaseTool.js +13 -2
  13. package/dist/cjs/tools/base/ContourBaseTool.js.map +1 -1
  14. package/dist/cjs/utilities/contours/updateContourPolyline.d.ts +5 -0
  15. package/dist/cjs/utilities/contours/updateContourPolyline.js +7 -2
  16. package/dist/cjs/utilities/contours/updateContourPolyline.js.map +1 -1
  17. package/dist/cjs/utilities/math/line/distanceToPointSquaredInfo.js.map +1 -1
  18. package/dist/cjs/utilities/math/polyline/decimate.d.ts +2 -0
  19. package/dist/cjs/utilities/math/polyline/decimate.js +73 -0
  20. package/dist/cjs/utilities/math/polyline/decimate.js.map +1 -0
  21. package/dist/cjs/utilities/math/polyline/index.d.ts +2 -1
  22. package/dist/cjs/utilities/math/polyline/index.js +3 -1
  23. package/dist/cjs/utilities/math/polyline/index.js.map +1 -1
  24. package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js +1 -1
  25. package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js.map +1 -1
  26. package/dist/esm/tools/annotation/LivewireContourTool.js +5 -2
  27. package/dist/esm/tools/annotation/LivewireContourTool.js.map +1 -1
  28. package/dist/esm/tools/annotation/PlanarFreehandROITool.js +4 -0
  29. package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  30. package/dist/esm/tools/annotation/SplineROITool.js +5 -2
  31. package/dist/esm/tools/annotation/SplineROITool.js.map +1 -1
  32. package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js +2 -3
  33. package/dist/esm/tools/annotation/planarFreehandROITool/drawLoop.js.map +1 -1
  34. package/dist/esm/tools/base/ContourBaseTool.js +10 -0
  35. package/dist/esm/tools/base/ContourBaseTool.js.map +1 -1
  36. package/dist/esm/utilities/contours/updateContourPolyline.js +6 -2
  37. package/dist/esm/utilities/contours/updateContourPolyline.js.map +1 -1
  38. package/dist/esm/utilities/math/line/distanceToPointSquaredInfo.js.map +1 -1
  39. package/dist/esm/utilities/math/polyline/decimate.js +47 -0
  40. package/dist/esm/utilities/math/polyline/decimate.js.map +1 -0
  41. package/dist/esm/utilities/math/polyline/index.js +2 -1
  42. package/dist/esm/utilities/math/polyline/index.js.map +1 -1
  43. package/dist/types/tools/annotation/LivewireContourTool.d.ts.map +1 -1
  44. package/dist/types/tools/annotation/PlanarFreehandROITool.d.ts.map +1 -1
  45. package/dist/types/tools/annotation/SplineROITool.d.ts.map +1 -1
  46. package/dist/types/tools/annotation/planarFreehandROITool/drawLoop.d.ts.map +1 -1
  47. package/dist/types/tools/base/ContourBaseTool.d.ts +8 -0
  48. package/dist/types/tools/base/ContourBaseTool.d.ts.map +1 -1
  49. package/dist/types/utilities/contours/updateContourPolyline.d.ts +5 -0
  50. package/dist/types/utilities/contours/updateContourPolyline.d.ts.map +1 -1
  51. package/dist/types/utilities/math/line/distanceToPointSquaredInfo.d.ts.map +1 -1
  52. package/dist/types/utilities/math/polyline/decimate.d.ts +3 -0
  53. package/dist/types/utilities/math/polyline/decimate.d.ts.map +1 -0
  54. package/dist/types/utilities/math/polyline/index.d.ts +2 -1
  55. package/dist/types/utilities/math/polyline/index.d.ts.map +1 -1
  56. package/dist/umd/index.js +1 -1
  57. package/dist/umd/index.js.map +1 -1
  58. package/package.json +3 -3
  59. package/src/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.ts +1 -1
  60. package/src/tools/annotation/LivewireContourTool.ts +15 -2
  61. package/src/tools/annotation/PlanarFreehandROITool.ts +12 -0
  62. package/src/tools/annotation/SplineROITool.ts +13 -2
  63. package/src/tools/annotation/planarFreehandROITool/drawLoop.ts +2 -3
  64. package/src/tools/base/ContourBaseTool.ts +23 -0
  65. package/src/utilities/contours/updateContourPolyline.ts +23 -1
  66. package/src/utilities/math/line/distanceToPointSquaredInfo.ts +2 -1
  67. package/src/utilities/math/polyline/decimate.ts +105 -0
  68. package/src/utilities/math/polyline/index.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.54.2",
3
+ "version": "1.55.0",
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.54.2",
32
+ "@cornerstonejs/core": "^1.55.0",
33
33
  "@icr/polyseg-wasm": "0.4.0",
34
34
  "comlink": "^4.4.1",
35
35
  "lodash.clonedeep": "4.5.0",
@@ -55,5 +55,5 @@
55
55
  "type": "individual",
56
56
  "url": "https://ohif.org/donate"
57
57
  },
58
- "gitHead": "6e3d8ea3d2aa05751ca8dcba38716d65324cc5f4"
58
+ "gitHead": "78d3c2fd9e2cd07d1ec5b84201f9f909ff6ce7cf"
59
59
  }
@@ -242,7 +242,7 @@ function getContourHolesData(
242
242
  });
243
243
  }
244
244
 
245
- async function combinePolylines(
245
+ function combinePolylines(
246
246
  viewport: Types.IViewport,
247
247
  targetAnnotation: ContourSegmentationAnnotation,
248
248
  targetPolyline: Types.Point2[],
@@ -32,7 +32,6 @@ import { LivewireScissors } from '../../utilities/livewire/LivewireScissors';
32
32
  import { LivewirePath } from '../../utilities/livewire/LiveWirePath';
33
33
  import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
34
34
  import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
35
- import updateContourPolyline from '../../utilities/contours/updateContourPolyline';
36
35
 
37
36
  const CLICK_CLOSE_CURVE_SQR_DIST = 10 ** 2; // px
38
37
 
@@ -108,6 +107,20 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
108
107
  */
109
108
  showInterpolationPolyline: false,
110
109
  },
110
+
111
+ /**
112
+ * The polyline may get processed in order to reduce the number of points
113
+ * for better performance and storage.
114
+ */
115
+ decimate: {
116
+ enabled: false,
117
+ /** A maximum given distance 'epsilon' to decide if a point should or
118
+ * shouldn't be added the resulting polyline which will have a lower
119
+ * number of points for higher `epsilon` values.
120
+ */
121
+ epsilon: 0.1,
122
+ },
123
+
111
124
  actions: {
112
125
  undo: {
113
126
  method: 'undo',
@@ -922,7 +935,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
922
935
  imagePoints = [...imagePoints, imagePoints[0]];
923
936
  }
924
937
 
925
- updateContourPolyline(
938
+ this.updateContourPolyline(
926
939
  annotation,
927
940
  {
928
941
  points: imagePoints,
@@ -231,6 +231,18 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
231
231
  // interpolation is complete.
232
232
  onInterpolationComplete: null,
233
233
  },
234
+ /**
235
+ * The polyline may get processed in order to reduce the number of points
236
+ * for better performance and storage.
237
+ */
238
+ decimate: {
239
+ enabled: false,
240
+ /** A maximum given distance 'epsilon' to decide if a point should or
241
+ * shouldn't be added the resulting polyline which will have a lower
242
+ * number of points for higher `epsilon` values.
243
+ */
244
+ epsilon: 0.1,
245
+ },
234
246
  calculateStats: false,
235
247
  getTextLines: defaultGetTextLines,
236
248
  statsCalculator: BasicStatsCalculator,
@@ -56,7 +56,6 @@ import { LinearSpline } from './splines/LinearSpline';
56
56
  import { CatmullRomSpline } from './splines/CatmullRomSpline';
57
57
  import { BSpline } from './splines/BSpline';
58
58
  import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
59
- import updateContourPolyline from '../../utilities/contours/updateContourPolyline';
60
59
 
61
60
  const SPLINE_MIN_POINTS = 3;
62
61
  const SPLINE_CLICK_CLOSE_CURVE_DIST = 10;
@@ -121,6 +120,18 @@ class SplineROITool extends ContourSegmentationBaseTool {
121
120
  * modifier must be pressed when the first point of a new contour is added.
122
121
  */
123
122
  contourHoleAdditionModifierKey: KeyboardBindings.Shift,
123
+ /**
124
+ * The polyline may get processed in order to reduce the number of points
125
+ * for better performance and storage.
126
+ */
127
+ decimate: {
128
+ enabled: false,
129
+ /** A maximum given distance 'epsilon' to decide if a point should or
130
+ * shouldn't be added the resulting polyline which will have a lower
131
+ * number of points for higher `epsilon` values.
132
+ */
133
+ epsilon: 0.1,
134
+ },
124
135
  spline: {
125
136
  configuration: {
126
137
  [SplineTypesEnum.Cardinal]: {
@@ -693,7 +704,7 @@ class SplineROITool extends ContourSegmentationBaseTool {
693
704
  const spline = this._updateSplineInstance(element, annotation);
694
705
  const splinePolylineCanvas = spline.getPolylinePoints();
695
706
 
696
- updateContourPolyline(
707
+ this.updateContourPolyline(
697
708
  annotation,
698
709
  {
699
710
  points: splinePolylineCanvas,
@@ -18,7 +18,6 @@ import { PlanarFreehandROIAnnotation } from '../../../types/ToolSpecificAnnotati
18
18
  import findOpenUShapedContourVectorToPeak from './findOpenUShapedContourVectorToPeak';
19
19
  import { polyline } from '../../../utilities/math';
20
20
  import { removeAnnotation } from '../../../stateManagement/annotation/annotationState';
21
- import { updateContourPolyline } from '../../../utilities/contours/';
22
21
  import reverseIfAntiClockwise from '../../../utilities/contours/reverseIfAntiClockwise';
23
22
 
24
23
  const {
@@ -238,7 +237,7 @@ function completeDrawClosedContour(
238
237
  // contours. A future optimization if we use this for segmentation is to re-do
239
238
  // this rendering with the GPU rather than SVG.
240
239
 
241
- updateContourPolyline(
240
+ this.updateContourPolyline(
242
241
  annotation,
243
242
  {
244
243
  points: updatedPoints,
@@ -315,7 +314,7 @@ function completeDrawOpenContour(
315
314
  // contours. A future optimisation if we use this for segmentation is to re-do
316
315
  // this rendering with the GPU rather than SVG.
317
316
 
318
- updateContourPolyline(
317
+ this.updateContourPolyline(
319
318
  annotation,
320
319
  {
321
320
  points: updatedPoints,
@@ -17,7 +17,9 @@ import type {
17
17
  import { drawPath as drawPathSvg } from '../../drawingSvg';
18
18
  import { StyleSpecifier } from '../../types/AnnotationStyle';
19
19
  import AnnotationTool from './AnnotationTool';
20
+ import { updateContourPolyline } from '../../utilities/contours/';
20
21
  import { getContourHolesDataCanvas } from '../../utilities/contours';
22
+ import { ContourWindingDirection } from '../../types/ContourAnnotation';
21
23
 
22
24
  /**
23
25
  * A contour base class responsible for rendering contour instances such as
@@ -210,6 +212,27 @@ abstract class ContourBaseTool extends AnnotationTool {
210
212
  );
211
213
  }
212
214
 
215
+ protected updateContourPolyline(
216
+ annotation: ContourAnnotation,
217
+ polylineData: {
218
+ points: Types.Point2[];
219
+ closed?: boolean;
220
+ targetWindingDirection?: ContourWindingDirection;
221
+ },
222
+ transforms: {
223
+ canvasToWorld: (point: Types.Point2) => Types.Point3;
224
+ }
225
+ ) {
226
+ const decimateConfig = this.configuration?.decimate || {};
227
+
228
+ updateContourPolyline(annotation, polylineData, transforms, {
229
+ decimate: {
230
+ enabled: !!decimateConfig.enabled,
231
+ epsilon: decimateConfig.epsilon,
232
+ },
233
+ });
234
+ }
235
+
213
236
  /**
214
237
  * Get polyline points in world space.
215
238
  * Just to give a chance for child classes to override it.
@@ -14,6 +14,12 @@ import {
14
14
  * @param viewport - Viewport
15
15
  * @param polylineData - Polyline data (points, winding direction and closed)
16
16
  * @param transforms - Methods to convert points to/from canvas and world spaces
17
+ * @param options - Options
18
+ * - decimate: allow to set some parameters to decimate the polyline reducing
19
+ * the amount of points stored which also affects how fast it will draw the
20
+ * annotation in a viewport, compute the winding direction, append/remove
21
+ * contours and create holes. A higher `epsilon` value results in a polyline
22
+ * with less points.
17
23
  */
18
24
  export default function updateContourPolyline(
19
25
  annotation: ContourAnnotation,
@@ -24,11 +30,27 @@ export default function updateContourPolyline(
24
30
  },
25
31
  transforms: {
26
32
  canvasToWorld: (point: Types.Point2) => Types.Point3;
33
+ },
34
+ options?: {
35
+ decimate?: {
36
+ enabled?: boolean;
37
+ epsilon?: number;
38
+ };
27
39
  }
28
40
  ) {
29
41
  const { canvasToWorld } = transforms;
30
42
  const { data } = annotation;
31
- const { points: polyline, targetWindingDirection } = polylineData;
43
+ const { targetWindingDirection } = polylineData;
44
+ let { points: polyline } = polylineData;
45
+
46
+ // Decimate the polyline to reduce tha amount of points
47
+ if (options?.decimate?.enabled) {
48
+ polyline = math.polyline.decimate(
49
+ polylineData.points,
50
+ options?.decimate?.epsilon
51
+ );
52
+ }
53
+
32
54
  let { closed } = polylineData;
33
55
  const numPoints = polyline.length;
34
56
  const polylineWorldPoints = new Array(numPoints);
@@ -24,7 +24,8 @@ export default function distanceToPointSquaredInfo(
24
24
  let closestPoint: Types.Point2;
25
25
  const distanceSquared = math.point.distanceToPointSquared(lineStart, lineEnd);
26
26
 
27
- // Check if lineStart is the same as lineEnd which means
27
+ // Check if lineStart equal to the lineEnd which means the closest point
28
+ // is any of these two points
28
29
  if (lineStart[0] === lineEnd[0] && lineStart[1] === lineEnd[1]) {
29
30
  closestPoint = lineStart;
30
31
  }
@@ -0,0 +1,105 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import * as mathLine from '../line';
3
+
4
+ const DEFAULT_EPSILON = 0.1;
5
+
6
+ /**
7
+ * Ramer–Douglas–Peucker algorithm implementation to decimate a polyline
8
+ * to a similar polyline with fewer points
9
+ *
10
+ * https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
11
+ * https://rosettacode.org/wiki/Ramer-Douglas-Peucker_line_simplification
12
+ * https://karthaus.nl/rdp/
13
+ *
14
+ * @param polyline - Polyline to decimate
15
+ * @param epsilon - A maximum given distance 'epsilon' to decide if a point
16
+ * should or shouldn't be added the decimated polyline version. In each
17
+ * iteration the polyline is split into two polylines and the distance of each
18
+ * point from those new polylines are checked against the line that connects
19
+ * the first and last points.
20
+ * @returns Decimated polyline
21
+ */
22
+ export default function decimate(
23
+ polyline: Types.Point2[],
24
+ epsilon = DEFAULT_EPSILON
25
+ ) {
26
+ const numPoints = polyline.length;
27
+
28
+ // The polyline must have at least a start and end points
29
+ if (numPoints < 3) {
30
+ return polyline;
31
+ }
32
+
33
+ const epsilonSquared = epsilon * epsilon;
34
+ const partitionQueue = [[0, numPoints - 1]];
35
+
36
+ // Used a boolean array to set each point that will be in the decimated polyline
37
+ // because pre-allocated arrays are 3-4x faster than thousands of push() calls
38
+ // to add all points to a new array.
39
+ const polylinePointFlags = new Array(numPoints).fill(false);
40
+
41
+ // Start and end points are always added to the decimated polyline
42
+ let numDecimatedPoints = 2;
43
+
44
+ // Add start and end points to the decimated polyline
45
+ polylinePointFlags[0] = true;
46
+ polylinePointFlags[numPoints - 1] = true;
47
+
48
+ // Iterative approach using a queue instead of recursion to reduce the number
49
+ // of function calls (performance)
50
+ while (partitionQueue.length) {
51
+ const [startIndex, endIndex] = partitionQueue.pop();
52
+
53
+ // Return if there is no point between the start and end points
54
+ if (endIndex - startIndex === 1) {
55
+ continue;
56
+ }
57
+
58
+ const startPoint = polyline[startIndex];
59
+ const endPoint = polyline[endIndex];
60
+ let maxDistSquared = -Infinity;
61
+ let maxDistIndex = -1;
62
+
63
+ // Search for the furthest point
64
+ for (let i = startIndex + 1; i < endIndex; i++) {
65
+ const currentPoint = polyline[i];
66
+ const distSquared = mathLine.distanceToPointSquared(
67
+ startPoint,
68
+ endPoint,
69
+ currentPoint
70
+ );
71
+
72
+ if (distSquared > maxDistSquared) {
73
+ maxDistSquared = distSquared;
74
+ maxDistIndex = i;
75
+ }
76
+ }
77
+
78
+ // Do not add any of the points because the fursthest one is very close to
79
+ // the line based on the epsilon value
80
+ if (maxDistSquared < epsilonSquared) {
81
+ continue;
82
+ }
83
+
84
+ // Update the flag for the furthest point because it will be added to the
85
+ // decimated polyline
86
+ polylinePointFlags[maxDistIndex] = true;
87
+ numDecimatedPoints++;
88
+
89
+ // Partition the points into two parts using maxDistIndex as the pivot point
90
+ // and process both sides
91
+ partitionQueue.push([maxDistIndex, endIndex]);
92
+ partitionQueue.push([startIndex, maxDistIndex]);
93
+ }
94
+
95
+ // A pre-allocated array is 3-4x faster then multiple push() calls
96
+ const decimatedPolyline: Types.Point2[] = new Array(numDecimatedPoints);
97
+
98
+ for (let srcIndex = 0, dstIndex = 0; srcIndex < numPoints; srcIndex++) {
99
+ if (polylinePointFlags[srcIndex]) {
100
+ decimatedPolyline[dstIndex++] = polyline[srcIndex];
101
+ }
102
+ }
103
+
104
+ return decimatedPolyline;
105
+ }
@@ -9,6 +9,7 @@ import getNormal3 from './getNormal3';
9
9
  import getNormal2 from './getNormal2';
10
10
  import { mergePolylines, subtractPolylines } from './combinePolyline';
11
11
  import intersectPolyline from './intersectPolyline';
12
+ import decimate from './decimate';
12
13
  import getFirstLineSegmentIntersectionIndexes from './getFirstLineSegmentIntersectionIndexes';
13
14
  import getLineSegmentIntersectionsIndexes from './getLineSegmentIntersectionsIndexes';
14
15
  import getLineSegmentIntersectionsCoordinates from './getLineSegmentIntersectionsCoordinates';
@@ -30,6 +31,7 @@ export {
30
31
  getNormal3,
31
32
  getNormal2,
32
33
  intersectPolyline,
34
+ decimate,
33
35
  getFirstLineSegmentIntersectionIndexes,
34
36
  getLineSegmentIntersectionsIndexes,
35
37
  getLineSegmentIntersectionsCoordinates,