@cornerstonejs/tools 1.51.5 → 1.52.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 (128) hide show
  1. package/dist/cjs/drawingSvg/drawPolyline.js +1 -1
  2. package/dist/cjs/drawingSvg/drawPolyline.js.map +1 -1
  3. package/dist/cjs/enums/ChangeTypes.d.ts +2 -1
  4. package/dist/cjs/enums/ChangeTypes.js +1 -0
  5. package/dist/cjs/enums/ChangeTypes.js.map +1 -1
  6. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js +1 -1
  7. package/dist/cjs/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  8. package/dist/cjs/stateManagement/annotation/helpers/state.d.ts +2 -1
  9. package/dist/cjs/stateManagement/annotation/helpers/state.js +2 -1
  10. package/dist/cjs/stateManagement/annotation/helpers/state.js.map +1 -1
  11. package/dist/cjs/tools/annotation/LivewireContourSegmentationTool.d.ts +4 -0
  12. package/dist/cjs/tools/annotation/LivewireContourSegmentationTool.js +82 -0
  13. package/dist/cjs/tools/annotation/LivewireContourSegmentationTool.js.map +1 -1
  14. package/dist/cjs/tools/annotation/LivewireContourTool.d.ts +10 -9
  15. package/dist/cjs/tools/annotation/LivewireContourTool.js +56 -43
  16. package/dist/cjs/tools/annotation/LivewireContourTool.js.map +1 -1
  17. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +2 -1
  18. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  19. package/dist/cjs/tools/annotation/SplineROITool.js +3 -3
  20. package/dist/cjs/tools/annotation/SplineROITool.js.map +1 -1
  21. package/dist/cjs/types/ContourSegmentationAnnotation.d.ts +8 -0
  22. package/dist/cjs/types/InterpolationTypes.d.ts +2 -0
  23. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +2 -5
  24. package/dist/cjs/utilities/contours/interpolation/acceptAutogeneratedInterpolations.js.map +1 -1
  25. package/dist/cjs/utilities/contours/interpolation/createPolylineToolData.js +2 -1
  26. package/dist/cjs/utilities/contours/interpolation/createPolylineToolData.js.map +1 -1
  27. package/dist/cjs/utilities/contours/interpolation/findAnnotationForInterpolation.js +2 -1
  28. package/dist/cjs/utilities/contours/interpolation/findAnnotationForInterpolation.js.map +1 -1
  29. package/dist/cjs/utilities/contours/interpolation/getInterpolationData.js +3 -1
  30. package/dist/cjs/utilities/contours/interpolation/getInterpolationData.js.map +1 -1
  31. package/dist/cjs/utilities/contours/interpolation/interpolate.d.ts +8 -0
  32. package/dist/cjs/utilities/contours/interpolation/interpolate.js +56 -42
  33. package/dist/cjs/utilities/contours/interpolation/interpolate.js.map +1 -1
  34. package/dist/cjs/utilities/contours/interpolation/selectHandles.d.ts +4 -0
  35. package/dist/cjs/utilities/contours/interpolation/selectHandles.js +170 -0
  36. package/dist/cjs/utilities/contours/interpolation/selectHandles.js.map +1 -0
  37. package/dist/cjs/utilities/livewire/LivewireScissors.d.ts +2 -1
  38. package/dist/cjs/utilities/livewire/LivewireScissors.js +46 -24
  39. package/dist/cjs/utilities/livewire/LivewireScissors.js.map +1 -1
  40. package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js +13 -3
  41. package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
  42. package/dist/esm/drawingSvg/drawPolyline.js +1 -1
  43. package/dist/esm/drawingSvg/drawPolyline.js.map +1 -1
  44. package/dist/esm/enums/ChangeTypes.js +1 -0
  45. package/dist/esm/enums/ChangeTypes.js.map +1 -1
  46. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js +1 -1
  47. package/dist/esm/eventDispatchers/keyboardEventHandlers/keyDown.js.map +1 -1
  48. package/dist/esm/stateManagement/annotation/helpers/state.js +3 -2
  49. package/dist/esm/stateManagement/annotation/helpers/state.js.map +1 -1
  50. package/dist/esm/tools/annotation/LivewireContourSegmentationTool.js +81 -0
  51. package/dist/esm/tools/annotation/LivewireContourSegmentationTool.js.map +1 -1
  52. package/dist/esm/tools/annotation/LivewireContourTool.js +57 -44
  53. package/dist/esm/tools/annotation/LivewireContourTool.js.map +1 -1
  54. package/dist/esm/tools/annotation/PlanarFreehandROITool.js +2 -1
  55. package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  56. package/dist/esm/tools/annotation/SplineROITool.js +3 -3
  57. package/dist/esm/tools/annotation/SplineROITool.js.map +1 -1
  58. package/dist/esm/utilities/contours/interpolation/acceptAutogeneratedInterpolations.js.map +1 -1
  59. package/dist/esm/utilities/contours/interpolation/createPolylineToolData.js +2 -1
  60. package/dist/esm/utilities/contours/interpolation/createPolylineToolData.js.map +1 -1
  61. package/dist/esm/utilities/contours/interpolation/findAnnotationForInterpolation.js +2 -1
  62. package/dist/esm/utilities/contours/interpolation/findAnnotationForInterpolation.js.map +1 -1
  63. package/dist/esm/utilities/contours/interpolation/getInterpolationData.js +3 -1
  64. package/dist/esm/utilities/contours/interpolation/getInterpolationData.js.map +1 -1
  65. package/dist/esm/utilities/contours/interpolation/interpolate.js +57 -43
  66. package/dist/esm/utilities/contours/interpolation/interpolate.js.map +1 -1
  67. package/dist/esm/utilities/contours/interpolation/selectHandles.js +164 -0
  68. package/dist/esm/utilities/contours/interpolation/selectHandles.js.map +1 -0
  69. package/dist/esm/utilities/livewire/LivewireScissors.js +46 -24
  70. package/dist/esm/utilities/livewire/LivewireScissors.js.map +1 -1
  71. package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js +13 -3
  72. package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
  73. package/dist/types/enums/ChangeTypes.d.ts +2 -1
  74. package/dist/types/enums/ChangeTypes.d.ts.map +1 -1
  75. package/dist/types/stateManagement/annotation/helpers/state.d.ts +2 -1
  76. package/dist/types/stateManagement/annotation/helpers/state.d.ts.map +1 -1
  77. package/dist/types/tools/annotation/LivewireContourSegmentationTool.d.ts +4 -0
  78. package/dist/types/tools/annotation/LivewireContourSegmentationTool.d.ts.map +1 -1
  79. package/dist/types/tools/annotation/LivewireContourTool.d.ts +10 -9
  80. package/dist/types/tools/annotation/LivewireContourTool.d.ts.map +1 -1
  81. package/dist/types/tools/annotation/PlanarFreehandROITool.d.ts.map +1 -1
  82. package/dist/types/types/ContourSegmentationAnnotation.d.ts +8 -0
  83. package/dist/types/types/ContourSegmentationAnnotation.d.ts.map +1 -1
  84. package/dist/types/types/InterpolationTypes.d.ts +2 -0
  85. package/dist/types/types/InterpolationTypes.d.ts.map +1 -1
  86. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +2 -5
  87. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
  88. package/dist/types/utilities/contours/interpolation/acceptAutogeneratedInterpolations.d.ts.map +1 -1
  89. package/dist/types/utilities/contours/interpolation/createPolylineToolData.d.ts.map +1 -1
  90. package/dist/types/utilities/contours/interpolation/findAnnotationForInterpolation.d.ts.map +1 -1
  91. package/dist/types/utilities/contours/interpolation/getInterpolationData.d.ts.map +1 -1
  92. package/dist/types/utilities/contours/interpolation/interpolate.d.ts +8 -0
  93. package/dist/types/utilities/contours/interpolation/interpolate.d.ts.map +1 -1
  94. package/dist/types/utilities/contours/interpolation/selectHandles.d.ts +5 -0
  95. package/dist/types/utilities/contours/interpolation/selectHandles.d.ts.map +1 -0
  96. package/dist/types/utilities/livewire/LivewireScissors.d.ts +2 -1
  97. package/dist/types/utilities/livewire/LivewireScissors.d.ts.map +1 -1
  98. package/dist/types/utilities/segmentation/InterpolationManager/InterpolationManager.d.ts.map +1 -1
  99. package/dist/umd/index.js +1 -1
  100. package/dist/umd/index.js.map +1 -1
  101. package/package.json +3 -3
  102. package/src/drawingSvg/drawPolyline.ts +1 -1
  103. package/src/enums/ChangeTypes.ts +4 -0
  104. package/src/eventDispatchers/keyboardEventHandlers/keyDown.ts +1 -1
  105. package/src/stateManagement/annotation/helpers/state.ts +4 -2
  106. package/src/tools/annotation/LivewireContourSegmentationTool.ts +151 -0
  107. package/src/tools/annotation/LivewireContourTool.ts +113 -82
  108. package/src/tools/annotation/PlanarFreehandROITool.ts +8 -3
  109. package/src/tools/annotation/SplineROITool.ts +3 -3
  110. package/src/types/ContourSegmentationAnnotation.ts +38 -0
  111. package/src/types/InterpolationTypes.ts +6 -0
  112. package/src/types/ToolSpecificAnnotationTypes.ts +7 -5
  113. package/src/utilities/contours/interpolation/acceptAutogeneratedInterpolations.ts +3 -1
  114. package/src/utilities/contours/interpolation/createPolylineToolData.ts +7 -1
  115. package/src/utilities/contours/interpolation/findAnnotationForInterpolation.ts +2 -1
  116. package/src/utilities/contours/interpolation/getInterpolationData.ts +3 -1
  117. package/src/utilities/contours/interpolation/interpolate.ts +94 -75
  118. package/src/utilities/contours/interpolation/selectHandles.ts +240 -0
  119. package/src/utilities/livewire/LivewireScissors.ts +65 -53
  120. package/src/utilities/segmentation/InterpolationManager/InterpolationManager.ts +39 -4
  121. package/dist/cjs/utilities/contours/PointsArray.d.ts +0 -29
  122. package/dist/cjs/utilities/contours/PointsArray.js +0 -104
  123. package/dist/cjs/utilities/contours/PointsArray.js.map +0 -1
  124. package/dist/esm/utilities/contours/PointsArray.js +0 -98
  125. package/dist/esm/utilities/contours/PointsArray.js.map +0 -1
  126. package/dist/types/utilities/contours/PointsArray.d.ts +0 -30
  127. package/dist/types/utilities/contours/PointsArray.d.ts.map +0 -1
  128. package/src/utilities/contours/PointsArray.ts +0 -165
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.51.5",
3
+ "version": "1.52.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.51.5",
32
+ "@cornerstonejs/core": "^1.52.0",
33
33
  "comlink": "^4.4.1",
34
34
  "lodash.clonedeep": "4.5.0",
35
35
  "lodash.get": "^4.4.2"
@@ -53,5 +53,5 @@
53
53
  "type": "individual",
54
54
  "url": "https://ohif.org/donate"
55
55
  },
56
- "gitHead": "8ed54955b6c0c04818954dabb9a1d22d1bf4360e"
56
+ "gitHead": "f09289b1bbae632126eec5ba208dd89da1923a4d"
57
57
  }
@@ -53,7 +53,7 @@ export default function drawPolyline(
53
53
  let pointsAttribute = '';
54
54
 
55
55
  for (const point of points) {
56
- pointsAttribute += `${point[0]}, ${point[1]} `;
56
+ pointsAttribute += `${point[0].toFixed(1)}, ${point[1].toFixed(1)} `;
57
57
  }
58
58
 
59
59
  if (options.connectLastToFirst) {
@@ -27,6 +27,10 @@ enum ChangeTypes {
27
27
  * Completed occurs only for the annotation completed event, just to identify it
28
28
  */
29
29
  Completed = 'Completed',
30
+ /**
31
+ * Occurs when an interpolation result is updated with more tool specific data.
32
+ */
33
+ InterpolationUpdated = 'InterpolationUpdated',
30
34
  }
31
35
 
32
36
  export default ChangeTypes;
@@ -40,7 +40,7 @@ export default function keyDown(evt: KeyDownEventType): void {
40
40
  // so that the method can depend on the specific configuration in use.
41
41
  const method =
42
42
  typeof value.method === 'function' ? value.method : key[value.method];
43
- method.call(key, element, value);
43
+ method.call(key, element, value, evt);
44
44
  }
45
45
  }
46
46
  }
@@ -4,7 +4,7 @@ import {
4
4
  eventTarget,
5
5
  getEnabledElementByIds,
6
6
  } from '@cornerstonejs/core';
7
- import { Events } from '../../../enums';
7
+ import { Events, ChangeTypes } from '../../../enums';
8
8
  import { Annotation } from '../../../types/AnnotationTypes';
9
9
  import { getToolGroupsWithToolName } from '../../../store/ToolGroupManager';
10
10
  import {
@@ -85,7 +85,8 @@ function triggerAnnotationAddedForFOR(annotation: Annotation) {
85
85
  */
86
86
  function triggerAnnotationModified(
87
87
  annotation: Annotation,
88
- element: HTMLDivElement
88
+ element: HTMLDivElement,
89
+ changeType = ChangeTypes.HandlesUpdated
89
90
  ): void {
90
91
  const enabledElement = getEnabledElement(element);
91
92
  const { viewportId, renderingEngineId } = enabledElement;
@@ -94,6 +95,7 @@ function triggerAnnotationModified(
94
95
  annotation,
95
96
  viewportId,
96
97
  renderingEngineId,
98
+ changeType,
97
99
  };
98
100
 
99
101
  triggerEvent(eventTarget, eventType, eventDetail);
@@ -1,8 +1,159 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import { utilities as csUtils } from '@cornerstonejs/core';
3
+
1
4
  import LivewireContourTool from './LivewireContourTool';
5
+ import { LivewirePath } from '../../utilities/livewire/LiveWirePath';
6
+ import { triggerAnnotationModified } from '../../stateManagement/annotation/helpers/state';
7
+ import { ChangeTypes } from '../../enums';
8
+ import type { ContourSegmentationAnnotation } from '../../types';
9
+ import { drawPolyline as drawPolylineSvg } from '../../drawingSvg';
2
10
 
3
11
  class LivewireContourSegmentationTool extends LivewireContourTool {
4
12
  static toolName;
5
13
 
14
+ /**
15
+ * Updates the interpolated annotations with the currently displayed image data,
16
+ * performing hte livewire on the image data as generated.
17
+ * Note - this function is only called for interpolated livewire SEGMENTATION
18
+ * objects, and will return immediately otherwise.
19
+ *
20
+ * The work for the interpolation is performed in a microtask, enabling this
21
+ * method to return quickly for faster render speeds, but ensuring that the
22
+ * annotation data isn't updated before the changes are performed. The removes
23
+ * some irritating flickering on navigation.
24
+ */
25
+ public updateInterpolatedAnnotation(
26
+ annotation: ContourSegmentationAnnotation,
27
+ enabledElement: Types.IEnabledElement
28
+ ) {
29
+ // The interpolation sources is used as a flag here - a true livewire
30
+ // behaviour would be to perform a livewire between the two planes
31
+ // closest to this plane for each point, and use that handle. That is
32
+ // oblique, however, which is not currently supported.
33
+ if (
34
+ this.editData ||
35
+ !annotation.invalidated ||
36
+ !annotation.data.handles.interpolationSources
37
+ ) {
38
+ return;
39
+ }
40
+ annotation.data.contour.originalPolyline = annotation.data.contour.polyline;
41
+
42
+ // See docs above for why this is a microtask
43
+ queueMicrotask(() => {
44
+ if (!annotation.data.handles.interpolationSources) {
45
+ return;
46
+ }
47
+ const { points } = annotation.data.handles;
48
+
49
+ const { element } = enabledElement.viewport;
50
+ this.setupBaseEditData(points[0], element, annotation);
51
+ const { length: count } = points;
52
+ const { scissors } = this;
53
+ const { nearestEdge, repeatInterpolation } =
54
+ this.configuration.interpolation;
55
+ annotation.data.handles.originalPoints = points;
56
+ const { worldToSlice, sliceToWorld } = this.editData;
57
+ const handleSmoothing = [];
58
+
59
+ // New path generation - go through the handles and regenerate the polyline
60
+ if (nearestEdge) {
61
+ let lastPoint = worldToSlice(points[points.length - 1]);
62
+ // Nearest edge handling
63
+ points.forEach((point, hIndex) => {
64
+ const testPoint = worldToSlice(point);
65
+ lastPoint = testPoint;
66
+ handleSmoothing.push(testPoint);
67
+
68
+ // Fill the costs buffer and then find the minimum cost
69
+ // This is a little too aggressive about pulling the line in
70
+ scissors.startSearch(lastPoint);
71
+ scissors.findPathToPoint(testPoint);
72
+ // Fill the costs for a point a bit further along by searching for a
73
+ // point further along.
74
+ scissors.findPathToPoint(
75
+ worldToSlice(points[(hIndex + 3) % points.length])
76
+ );
77
+ const minPoint = scissors.findMinNearby(testPoint, nearestEdge);
78
+ if (!csUtils.isEqual(testPoint, minPoint)) {
79
+ handleSmoothing[hIndex] = minPoint;
80
+ lastPoint = minPoint;
81
+ points[hIndex] = sliceToWorld(minPoint);
82
+ }
83
+ });
84
+ }
85
+
86
+ // Regenerate the updated data based on the updated handles
87
+ const acceptedPath = new LivewirePath();
88
+ for (let i = 0; i < count; i++) {
89
+ scissors.startSearch(worldToSlice(points[i]));
90
+ const path = scissors.findPathToPoint(
91
+ worldToSlice(points[(i + 1) % count])
92
+ );
93
+ acceptedPath.addPoints(path);
94
+ }
95
+
96
+ // Now, update the rendering
97
+ this.updateAnnotation(element, acceptedPath);
98
+ this.scissors = null;
99
+ this.scissorsRight = null;
100
+ this.editData = null;
101
+ annotation.data.handles.interpolationSources = null;
102
+
103
+ if (repeatInterpolation) {
104
+ triggerAnnotationModified(
105
+ annotation,
106
+ enabledElement.viewport.element,
107
+ ChangeTypes.InterpolationUpdated
108
+ );
109
+ }
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Adds the update to the interpolated annotaiton on render an instance,
115
+ * but otherwise just calls the parent render annotation instance.
116
+ */
117
+ protected renderAnnotationInstance(renderContext): boolean {
118
+ const { enabledElement, svgDrawingHelper } = renderContext;
119
+ const annotation =
120
+ renderContext.annotation as ContourSegmentationAnnotation;
121
+ const { annotationUID } = annotation;
122
+ const { viewport } = enabledElement;
123
+ const { worldToCanvas } = viewport;
124
+ const { showInterpolationPolyline } =
125
+ this.configuration.interpolation || {};
126
+
127
+ this.updateInterpolatedAnnotation?.(annotation, enabledElement);
128
+ const { originalPolyline } = annotation.data.contour;
129
+
130
+ const rendered = super.renderAnnotationInstance(renderContext);
131
+
132
+ if (
133
+ showInterpolationPolyline &&
134
+ originalPolyline &&
135
+ annotation.autoGenerated
136
+ ) {
137
+ const polylineCanvasPoints = originalPolyline.map(
138
+ worldToCanvas
139
+ ) as Types.Point2[];
140
+ polylineCanvasPoints.push(polylineCanvasPoints[0]);
141
+ drawPolylineSvg(
142
+ svgDrawingHelper,
143
+ annotationUID,
144
+ 'interpolationContour-0',
145
+ polylineCanvasPoints,
146
+ {
147
+ color: '#70ffff',
148
+ lineWidth: 1,
149
+ fillOpacity: 0,
150
+ }
151
+ );
152
+ }
153
+
154
+ return rendered;
155
+ }
156
+
6
157
  protected isContourSegmentationTool(): boolean {
7
158
  // Re-enable contour segmentation behavior disabled by LivewireContourTool
8
159
  return true;
@@ -1,20 +1,17 @@
1
1
  import { vec3 } from 'gl-matrix';
2
-
3
2
  import {
4
3
  getEnabledElement,
5
- triggerEvent,
6
- eventTarget,
7
4
  utilities as csUtils,
8
5
  VolumeViewport,
9
6
  } from '@cornerstonejs/core';
10
7
  import type { Types } from '@cornerstonejs/core';
8
+
11
9
  import { removeAnnotation } from '../../stateManagement/annotation/annotationState';
12
10
  import { drawHandles as drawHandlesSvg } from '../../drawingSvg';
13
11
  import { state } from '../../store';
14
12
  import { Events, ChangeTypes } from '../../enums';
15
13
  import { resetElementCursor } from '../../cursors/elementCursor';
16
14
  import type {
17
- Annotation,
18
15
  EventTypes,
19
16
  ToolHandle,
20
17
  PublicToolProps,
@@ -25,9 +22,9 @@ import { math, triggerAnnotationRenderForViewportIds } from '../../utilities';
25
22
  import findHandlePolylineIndex from '../../utilities/contours/findHandlePolylineIndex';
26
23
  import { LivewireContourAnnotation } from '../../types/ToolSpecificAnnotationTypes';
27
24
  import {
28
- AnnotationCompletedEventDetail,
29
- AnnotationModifiedEventDetail,
30
- } from '../../types/EventTypes';
25
+ triggerAnnotationModified,
26
+ triggerAnnotationCompleted,
27
+ } from '../../stateManagement/annotation/helpers/state';
31
28
  import reverseIfAntiClockwise from '../../utilities/contours/reverseIfAntiClockwise';
32
29
 
33
30
  import { LivewireScissors } from '../../utilities/livewire/LivewireScissors';
@@ -35,15 +32,13 @@ import { LivewirePath } from '../../utilities/livewire/LiveWirePath';
35
32
  import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
36
33
  import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
37
34
 
38
- const { isEqual } = csUtils;
39
-
40
35
  const CLICK_CLOSE_CURVE_SQR_DIST = 10 ** 2; // px
41
36
 
42
37
  class LivewireContourTool extends ContourSegmentationBaseTool {
43
38
  public static toolName: string;
44
- private scissors: LivewireScissors;
39
+ protected scissors: LivewireScissors;
45
40
  /** The scissors from the right handle, used for editing */
46
- private scissorsRight: LivewireScissors;
41
+ protected scissorsRight: LivewireScissors;
47
42
 
48
43
  touchDragCallback: any;
49
44
  mouseDragCallback: any;
@@ -72,6 +67,48 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
72
67
  supportedInteractionTypes: ['Mouse', 'Touch'],
73
68
  configuration: {
74
69
  preventHandleOutsideImage: false,
70
+ /**
71
+ * Configuring this to a value larger than 0 will snap handles to nearby
72
+ * livewire points, within the given rectangle surrounding the clicked point.
73
+ * If set to 0, then the exact clicked point will be used instead, which may
74
+ * not be an edge and can result in jagged outlines.
75
+ * The unit is image pixels (index).
76
+ */
77
+ snapHandleNearby: 2,
78
+
79
+ /**
80
+ * Interpolation is only available for segmentation versions of these
81
+ * tools. To use it on the segmentation tools, set enabled to true,
82
+ * and create two livewire contours in the same segment index, separated
83
+ * by at least one slice.
84
+ */
85
+ interpolation: {
86
+ enabled: false,
87
+
88
+ /**
89
+ * Set the nearestEdge to snap interpolated handles to an edge within
90
+ * the given number of pixels. Setting to 0 disables snap to pixel
91
+ * for interpolation and the interpolated point will be used directly.
92
+ * Setting to too large a value may result in many points outside the contour
93
+ * being chosen.
94
+ */
95
+ nearestEdge: 2,
96
+ /**
97
+ * Set to true to show the interpolated polyline, which can be useful
98
+ * when understanding the nearest edge and
99
+ */
100
+ showInterpolationPolyline: false,
101
+ },
102
+ actions: {
103
+ undo: {
104
+ method: 'undo',
105
+ bindings: [
106
+ {
107
+ key: 'Escape',
108
+ },
109
+ ],
110
+ },
111
+ },
75
112
  },
76
113
  }
77
114
  ) {
@@ -214,7 +251,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
214
251
  const { currentPoints, element } = eventDetail;
215
252
  const { world: worldPos } = currentPoints;
216
253
  const { renderingEngine } = getEnabledElement(element);
217
- const annotation = this.createAnnotation(evt) as LivewireContourAnnotation;
254
+ const annotation = this.createAnnotation(evt);
218
255
 
219
256
  this.setupBaseEditData(worldPos, element, annotation);
220
257
  this.addAnnotation(annotation, element);
@@ -335,7 +372,10 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
335
372
  evt.preventDefault();
336
373
  };
337
374
 
338
- _endCallback = (evt: EventTypes.InteractionEventType): void => {
375
+ _endCallback = (
376
+ evt: EventTypes.InteractionEventType,
377
+ clearAnnotation = false
378
+ ): void => {
339
379
  const eventDetail = evt.detail;
340
380
  const { element } = eventDetail;
341
381
 
@@ -353,10 +393,17 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
353
393
  const { renderingEngine } = enabledElement;
354
394
 
355
395
  if (
356
- this.isHandleOutsideImage &&
357
- this.configuration.preventHandleOutsideImage
396
+ (this.isHandleOutsideImage &&
397
+ this.configuration.preventHandleOutsideImage) ||
398
+ clearAnnotation
358
399
  ) {
359
400
  removeAnnotation(annotation.annotationUID);
401
+ this.clearEditData();
402
+ triggerAnnotationRenderForViewportIds(
403
+ renderingEngine,
404
+ viewportIdsToRender
405
+ );
406
+ return;
360
407
  }
361
408
 
362
409
  // Reverse the points if needed, ensuring both the handles and the
@@ -377,47 +424,15 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
377
424
  : ChangeTypes.HandlesUpdated;
378
425
 
379
426
  this.triggerChangeEvent(annotation, enabledElement, changeType);
427
+ this.clearEditData();
428
+ };
380
429
 
430
+ protected clearEditData() {
381
431
  this.editData = null;
382
432
  this.scissors = null;
383
433
  this.scissorsRight = null;
384
434
  this.isDrawing = false;
385
- };
386
-
387
- /**
388
- * Triggers an annotation completed event.
389
- */
390
- triggerAnnotationCompleted = (
391
- annotation: LivewireContourAnnotation
392
- ): void => {
393
- const eventType = Events.ANNOTATION_COMPLETED;
394
- const eventDetail: AnnotationCompletedEventDetail = {
395
- annotation,
396
- changeType: ChangeTypes.Completed,
397
- };
398
-
399
- triggerEvent(eventTarget, eventType, eventDetail);
400
- };
401
-
402
- /**
403
- * Triggers an annotation modified event.
404
- */
405
- triggerAnnotationModified = (
406
- annotation: LivewireContourAnnotation,
407
- enabledElement: Types.IEnabledElement,
408
- changeType = ChangeTypes.StatsUpdated
409
- ): void => {
410
- const { viewportId, renderingEngineId } = enabledElement;
411
- const eventType = Events.ANNOTATION_MODIFIED;
412
- const eventDetail: AnnotationModifiedEventDetail = {
413
- annotation,
414
- viewportId,
415
- renderingEngineId,
416
- changeType,
417
- };
418
-
419
- triggerEvent(eventTarget, eventType, eventDetail);
420
- };
435
+ }
421
436
 
422
437
  /**
423
438
  * Triggers an annotation complete or modified event based on changeType.
@@ -428,9 +443,13 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
428
443
  changeType = ChangeTypes.StatsUpdated
429
444
  ): void => {
430
445
  if (changeType === ChangeTypes.Completed) {
431
- this.triggerAnnotationCompleted(annotation);
446
+ triggerAnnotationCompleted(annotation);
432
447
  } else {
433
- this.triggerAnnotationModified(annotation, enabledElement, changeType);
448
+ triggerAnnotationModified(
449
+ annotation,
450
+ enabledElement.viewport.element,
451
+ changeType
452
+ );
434
453
  }
435
454
  };
436
455
 
@@ -485,25 +504,24 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
485
504
  }
486
505
  }
487
506
 
488
- this.editData.closed = this.editData.closed || closePath;
489
- this.editData.confirmedPath = this.editData.currentPath;
490
-
491
- const smoothPathCount = this.scissors.smoothPathCount(
492
- this.editData.confirmedPath.pointArray,
493
- this.editData.currentPath.getLastControlPoint()
494
- );
495
- if (smoothPathCount) {
496
- this.editData.currentPath.removeLastPoints(smoothPathCount);
497
- annotation.data.contour.polyline.splice(
498
- annotation.data.contour.polyline.length - smoothPathCount,
499
- smoothPathCount
507
+ const { snapHandleNearby } = this.configuration;
508
+ // Snap the handles as they get created, but not during edit
509
+ if (snapHandleNearby && !this.editData.closed) {
510
+ const currentPath = new LivewirePath();
511
+ const snapPoint = this.scissors.findMinNearby(
512
+ worldToSlice(worldPosOriginal),
513
+ 1
500
514
  );
501
- worldPos =
502
- annotation.data.contour.polyline[
503
- annotation.data.contour.polyline.length - 1
504
- ];
515
+ const pathPoints = this.scissors.findPathToPoint(snapPoint);
516
+ currentPath.addPoints(pathPoints);
517
+ currentPath.prependPath(this.editData.confirmedPath);
518
+ worldPos = sliceToWorld(snapPoint);
519
+ this.editData.currentPath = currentPath;
505
520
  }
506
521
 
522
+ this.editData.closed = this.editData.closed || closePath;
523
+ this.editData.confirmedPath = this.editData.currentPath;
524
+
507
525
  // Add the current cursor position as a new control point after clicking
508
526
  const lastPoint = this.editData.currentPath.getLastPoint();
509
527
 
@@ -518,7 +536,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
518
536
 
519
537
  if (this.editData.closed) {
520
538
  // Update the annotation because `editData` will be set to null
521
- this._updateAnnotation(element, this.editData.confirmedPath);
539
+ this.updateAnnotation(element, this.editData.confirmedPath);
522
540
  this._endCallback(evt);
523
541
  }
524
542
 
@@ -624,7 +642,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
624
642
 
625
643
  const { activeHandleIndex } = data.handles;
626
644
  if (activeHandleIndex === null || activeHandleIndex === undefined) {
627
- data.handle.activeHandleIndex = handleIndex;
645
+ data.handles.activeHandleIndex = handleIndex;
628
646
  } else if (activeHandleIndex !== handleIndex) {
629
647
  throw new Error(
630
648
  `Trying to edit a different handle than the one currently being edited ${handleIndex}!==${data.handles.activeHandleIndex}`
@@ -773,22 +791,22 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
773
791
  const { element } = viewport;
774
792
 
775
793
  // Update the annotation that is in editData (being edited)
776
- this._updateAnnotation(element, this.editData?.currentPath);
794
+ this.updateAnnotation(element, this.editData?.currentPath);
777
795
 
778
796
  return super.renderAnnotation(enabledElement, svgDrawingHelper);
779
797
  }
780
798
 
781
799
  protected isContourSegmentationTool(): boolean {
782
- // Disable contour segmenatation behavior because it shall be activated only
800
+ // Disable contour segmentation behavior because it shall be activated only
783
801
  // for LivewireContourSegmentationTool
784
802
  return false;
785
803
  }
786
804
 
787
- protected createAnnotation(evt: EventTypes.InteractionEventType): Annotation {
805
+ protected createAnnotation(evt: EventTypes.InteractionEventType) {
788
806
  const contourSegmentationAnnotation = super.createAnnotation(evt);
789
807
  const { world: worldPos } = evt.detail.currentPoints;
790
808
 
791
- return <LivewireContourAnnotation>csUtils.deepMerge(
809
+ const annotation = <LivewireContourAnnotation>csUtils.deepMerge(
792
810
  contourSegmentationAnnotation,
793
811
  {
794
812
  data: {
@@ -798,6 +816,21 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
798
816
  },
799
817
  }
800
818
  );
819
+ return annotation;
820
+ }
821
+
822
+ /**
823
+ * Clears any in progress edits, mostly used to get rid of accidentally started
824
+ * contours that happen on clicking not quite the right handle point.
825
+ * Eventually this is to be replaced with a proper undo, once that framework
826
+ * is available.
827
+ */
828
+ public undo(element, config, evt) {
829
+ if (!this.editData) {
830
+ // TODO - proper undo
831
+ return;
832
+ }
833
+ this._endCallback(evt, true);
801
834
  }
802
835
 
803
836
  /**
@@ -808,14 +841,15 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
808
841
  protected renderAnnotationInstance(renderContext: {
809
842
  enabledElement: Types.IEnabledElement;
810
843
  targetId: string;
811
- annotation: Annotation;
844
+ annotation: LivewireContourAnnotation;
812
845
  annotationStyle: Record<string, any>;
813
846
  svgDrawingHelper: SVGDrawingHelper;
814
847
  }): boolean {
815
- const { enabledElement, svgDrawingHelper, annotationStyle } = renderContext;
848
+ const { annotation, enabledElement, svgDrawingHelper, annotationStyle } =
849
+ renderContext;
850
+
816
851
  const { viewport } = enabledElement;
817
852
  const { worldToCanvas } = viewport;
818
- const annotation = renderContext.annotation as LivewireContourAnnotation;
819
853
  const { annotationUID, data, highlighted } = annotation;
820
854
  const { handles } = data;
821
855
  const newAnnotation = this.editData?.newAnnotation;
@@ -851,10 +885,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
851
885
  return true;
852
886
  }
853
887
 
854
- private _updateAnnotation(
855
- element: HTMLDivElement,
856
- livewirePath: LivewirePath
857
- ) {
888
+ protected updateAnnotation(_, livewirePath: LivewirePath) {
858
889
  if (!this.editData || !livewirePath) {
859
890
  return;
860
891
  }
@@ -46,6 +46,7 @@ import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScale
46
46
  import { getModalityUnit } from '../../utilities/getModalityUnit';
47
47
  import { BasicStatsCalculator } from '../../utilities/math/basic';
48
48
  import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
49
+ import { ChangeTypes } from '../../enums';
49
50
 
50
51
  const { pointCanProjectOnLine } = polyline;
51
52
  const { EPSILON } = CONSTANTS;
@@ -79,7 +80,7 @@ const PARALLEL_THRESHOLD = 1 - EPSILON;
79
80
  *
80
81
  * The result of smoothing will be removal of some of the outliers
81
82
  * Changing tool configuration (see below) you can fine-tune the smoothing process by changing knotsRatioPercentageOnAdd and knotsRatioPercentageOnEdit value, which smaller values produces a more agressive smoothing.
82
- * A smaller value of knotsRatioPercentageOnAdd/knotsRatioPercentageOnEdit produces a more agressive smoothing.
83
+ * A smaller value of knotsRatioPercentageOnAdd/knotsRatioPercentageOnEdit produces a more aggressive smoothing.
83
84
  *
84
85
  * ```js
85
86
  * cornerstoneTools.addTool(PlanarFreehandROITool)
@@ -98,7 +99,7 @@ const PARALLEL_THRESHOLD = 1 - EPSILON;
98
99
  * ],
99
100
  * })
100
101
  *
101
- * // set smoothing agressiveness while adding new annotation (ps: this does not change if smoothing is ON or OFF)
102
+ * // set smoothing aggressiveness while adding new annotation (ps: this does not change if smoothing is ON or OFF)
102
103
  * toolGroup.setToolConfiguration(PlanarFreehandROITool.toolName, {
103
104
  * smoothing: { knotsRatioPercentageOnAdd: 30 },
104
105
  * });
@@ -823,7 +824,11 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
823
824
  };
824
825
  }
825
826
 
826
- triggerAnnotationModified(annotation, element);
827
+ triggerAnnotationModified(
828
+ annotation,
829
+ enabledElement.element,
830
+ ChangeTypes.StatsUpdated
831
+ );
827
832
 
828
833
  annotation.invalidated = false;
829
834
 
@@ -814,9 +814,9 @@ class SplineROITool extends ContourSegmentationBaseTool {
814
814
 
815
815
  // Add an action to create a new spline data on creating an interpolated
816
816
  // instance.
817
- let postInterpolateAction;
817
+ let onInterpolationComplete;
818
818
  if (this.configuration.interpolation?.enabled) {
819
- postInterpolateAction = (annotation) => {
819
+ onInterpolationComplete = (annotation) => {
820
820
  annotation.data.spline ||= createSpline();
821
821
  this.createInterpolatedSplineControl(annotation);
822
822
  };
@@ -830,7 +830,7 @@ class SplineROITool extends ContourSegmentationBaseTool {
830
830
  spline: createSpline(),
831
831
  cachedStats: {},
832
832
  },
833
- postInterpolateAction,
833
+ onInterpolationComplete,
834
834
  });
835
835
  }
836
836