@cornerstonejs/tools 1.84.0 → 1.84.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.84.0",
3
+ "version": "1.84.2",
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.84.0",
32
+ "@cornerstonejs/core": "^1.84.2",
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": "642bbba3dbb8ded72994e30759d029bed7adfd04"
62
+ "gitHead": "79667170db3c7d65acfcea1359c06b3f74763b40"
63
63
  }
@@ -1,88 +1,131 @@
1
1
  import { Types } from '@cornerstonejs/core';
2
+ import { mat4, vec3 } from 'gl-matrix';
2
3
  import { PlanarFreehandROITool } from '../../tools';
3
4
  import { ToolGroupManager } from '../../store';
4
5
  import { PlanarFreehandROIAnnotation } from '../../types/ToolSpecificAnnotationTypes';
5
6
  import interpolateSegmentPoints from './interpolation/interpolateSegmentPoints';
6
7
 
8
+ export type SmoothOptions = {
9
+ knotsRatioPercentage: number;
10
+ loop: number;
11
+ };
12
+
7
13
  function shouldPreventInterpolation(
8
- enabledElement: Types.IEnabledElement,
9
14
  annotation: PlanarFreehandROIAnnotation,
10
- knotsRatioPercentage: number
15
+ options?: SmoothOptions
11
16
  ): boolean {
12
- if (!annotation?.data?.polyline || knotsRatioPercentage <= 0) {
17
+ const knotsRatioPercentage = options?.knotsRatioPercentage || 30;
18
+ if (
19
+ !annotation?.data?.contour?.polyline?.length ||
20
+ knotsRatioPercentage <= 0
21
+ ) {
13
22
  return true;
14
23
  }
15
24
 
16
- if (!enabledElement.viewport) {
17
- return true;
18
- }
25
+ return false;
26
+ }
19
27
 
20
- const { renderingEngineId, viewportId, FrameOfReferenceUID } = enabledElement;
21
- const toolGroup = ToolGroupManager.getToolGroupForViewport(
22
- viewportId,
23
- renderingEngineId
24
- );
28
+ function rotateMatrix(normal, focal) {
29
+ const mat = mat4.create();
30
+ const eye = vec3.add(vec3.create(), focal, normal);
31
+ const up =
32
+ Math.abs(normal[0]) > 0.1
33
+ ? vec3.fromValues(-normal[1], normal[0], 0)
34
+ : vec3.fromValues(0, -normal[2], normal[1]);
35
+ // Use the focal point as the "eye" position so that the focal point get rotated to 0 for the k coordinate.
36
+ mat4.lookAt(mat, focal, eye, up);
37
+ return mat;
38
+ }
25
39
 
26
- if (annotation.metadata.FrameOfReferenceUID !== FrameOfReferenceUID) {
27
- return true;
40
+ /**
41
+ * Rotate the array to prevent interpolation at endpoints causing non-smooth endpoints
42
+ * Rotates the list in place.
43
+ */
44
+ function rotate(list, count = Math.floor(Math.random() * (list.length - 1))) {
45
+ if (count === 0) {
46
+ return 0;
28
47
  }
29
-
30
- if (!toolGroup) {
31
- return true;
48
+ const srcList = [...list];
49
+ const { length } = list;
50
+ for (let i = 0; i < length; i++) {
51
+ list[i] = srcList[(i + count + length) % length];
32
52
  }
33
-
34
- const toolInstance = toolGroup.getToolInstance(annotation.metadata.toolName);
35
-
36
- // strategy to prevent non PlanarFreehandTool
37
- if (!(toolInstance instanceof PlanarFreehandROITool)) {
38
- return true;
39
- }
40
-
41
- return (
42
- toolInstance.isDrawing ||
43
- toolInstance.isEditingOpen ||
44
- toolInstance.isEditingClosed
45
- );
53
+ return count;
46
54
  }
55
+
47
56
  /**
48
57
  * Interpolates a given annotation from a given enabledElement.
49
58
  * It mutates annotation param.
50
- * The param knotsRatioPercentage defines the percentage of points to be considered as knots on the interpolation process.
51
- * Interpolation will be skipped in case: annotation is not present in enabledElement (or there is no toolGroup associated with it), related tool is being modified.
59
+ * The param options.knotsRatioPercentage defines the percentage of points to be considered as knots on the interpolation process.
60
+ * The param options.loop can be set to run smoothing repeatedly. This results in
61
+ * additional smoothing.
62
+ * This works by translating the annotation into the view plane normal orientation, with a zero k component, and then
63
+ * performing the smoothing in-plane, and converting back to the original orientation.
64
+ * Closed polylines are smoothed at a random starting spot in order to prevent
65
+ * the start/end points from not being smoothed.
66
+ *
67
+ * Note that each smoothing iteration will reduce the size of the annotation, particularly
68
+ * for closed annotations. This occurs because a smaller/rounder annotation is smoother
69
+ * in some sense than the original.
52
70
  */
53
71
  export default function smoothAnnotation(
54
- enabledElement: Types.IEnabledElement,
55
72
  annotation: PlanarFreehandROIAnnotation,
56
- knotsRatioPercentage: number
73
+ options?: SmoothOptions
57
74
  ): boolean {
58
75
  // prevent running while there is any tool annotation being modified
59
- if (
60
- shouldPreventInterpolation(enabledElement, annotation, knotsRatioPercentage)
61
- ) {
76
+ if (shouldPreventInterpolation(annotation, options)) {
62
77
  return false;
63
78
  }
64
79
 
65
- const { viewport } = enabledElement;
80
+ const { viewPlaneNormal } = annotation.metadata;
81
+ const { closed, polyline } = annotation.data.contour;
82
+
66
83
  // use only 2 dimensions on interpolation (what visually matters),
67
84
  // otherwise a 3d interpolation might have a totally different output as it consider one more dimension.
68
- const canvasPoints = annotation.data.contour.polyline.map(
69
- viewport.worldToCanvas
85
+ const rotateMat = rotateMatrix(
86
+ viewPlaneNormal,
87
+ annotation.data.contour.polyline[0]
88
+ );
89
+ const canvasPoints = <Types.Point2[]>annotation.data.contour.polyline.map(
90
+ (p) => {
91
+ const planeP = vec3.transformMat4(vec3.create(), p, rotateMat);
92
+ return [planeP[0], planeP[1]];
93
+ }
70
94
  );
71
- const interpolatedCanvasPoints = <Types.Point2[]>(
95
+ let rotation = closed ? rotate(canvasPoints) : 0;
96
+ let interpolatedCanvasPoints = <Types.Point2[]>(
72
97
  interpolateSegmentPoints(
73
98
  canvasPoints,
74
99
  0,
75
100
  canvasPoints.length,
76
- knotsRatioPercentage
101
+ options?.knotsRatioPercentage || 30
77
102
  )
78
103
  );
79
104
 
80
105
  if (interpolatedCanvasPoints === canvasPoints) {
81
106
  return false;
82
107
  }
108
+ // Reverse the rotation so that handles still line up.
109
+ rotate(interpolatedCanvasPoints, -rotation);
83
110
 
84
- annotation.data.contour.polyline = interpolatedCanvasPoints.map(
85
- viewport.canvasToWorld
111
+ for (let i = 1; i < options?.loop; i++) {
112
+ rotation = closed ? rotate(interpolatedCanvasPoints) : 0;
113
+ interpolatedCanvasPoints = <Types.Point2[]>(
114
+ interpolateSegmentPoints(
115
+ interpolatedCanvasPoints,
116
+ 0,
117
+ canvasPoints.length,
118
+ options?.knotsRatioPercentage || 30
119
+ )
120
+ );
121
+ rotate(interpolatedCanvasPoints, -rotation);
122
+ }
123
+
124
+ const unRotate = mat4.invert(mat4.create(), rotateMat);
125
+ annotation.data.contour.polyline = <Types.Point3[]>(
126
+ interpolatedCanvasPoints.map((p) =>
127
+ vec3.transformMat4([0, 0, 0], [...p, 0], unRotate)
128
+ )
86
129
  );
87
130
 
88
131
  return true;