@cornerstonejs/tools 1.78.3 → 1.80.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 (118) hide show
  1. package/dist/cjs/cursors/SVGCursorDescriptor.js +7 -0
  2. package/dist/cjs/cursors/SVGCursorDescriptor.js.map +1 -1
  3. package/dist/cjs/drawingSvg/drawHeight.d.ts +3 -0
  4. package/dist/cjs/drawingSvg/drawHeight.js +49 -0
  5. package/dist/cjs/drawingSvg/drawHeight.js.map +1 -0
  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/index.d.ts +2 -2
  10. package/dist/cjs/index.js +3 -2
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/tools/annotation/HeightTool.d.ts +40 -0
  13. package/dist/cjs/tools/annotation/HeightTool.js +463 -0
  14. package/dist/cjs/tools/annotation/HeightTool.js.map +1 -0
  15. package/dist/cjs/tools/index.d.ts +2 -1
  16. package/dist/cjs/tools/index.js +4 -2
  17. package/dist/cjs/tools/index.js.map +1 -1
  18. package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
  19. package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js +189 -62
  20. package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
  21. package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
  22. package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js +167 -52
  23. package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
  24. package/dist/cjs/types/CINETypes.d.ts +1 -0
  25. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +26 -4
  26. package/dist/cjs/utilities/cine/playClip.js +52 -5
  27. package/dist/cjs/utilities/cine/playClip.js.map +1 -1
  28. package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.d.ts +3 -0
  29. package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js +31 -0
  30. package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
  31. package/dist/cjs/utilities/planar/index.d.ts +3 -1
  32. package/dist/cjs/utilities/planar/index.js +4 -1
  33. package/dist/cjs/utilities/planar/index.js.map +1 -1
  34. package/dist/cjs/utilities/stackPrefetch/stackContextPrefetch.js +13 -2
  35. package/dist/cjs/utilities/stackPrefetch/stackContextPrefetch.js.map +1 -1
  36. package/dist/cjs/utilities/stackPrefetch/stackPrefetchUtils.js +0 -1
  37. package/dist/cjs/utilities/stackPrefetch/stackPrefetchUtils.js.map +1 -1
  38. package/dist/cjs/utilities/viewport/isViewportPreScaled.js +1 -4
  39. package/dist/cjs/utilities/viewport/isViewportPreScaled.js.map +1 -1
  40. package/dist/cjs/utilities/viewport/jumpToSlice.js +4 -6
  41. package/dist/cjs/utilities/viewport/jumpToSlice.js.map +1 -1
  42. package/dist/esm/cursors/SVGCursorDescriptor.js +7 -0
  43. package/dist/esm/cursors/SVGCursorDescriptor.js.map +1 -1
  44. package/dist/esm/drawingSvg/drawHeight.js +43 -0
  45. package/dist/esm/drawingSvg/drawHeight.js.map +1 -0
  46. package/dist/esm/drawingSvg/index.js +2 -1
  47. package/dist/esm/drawingSvg/index.js.map +1 -1
  48. package/dist/esm/index.js +2 -2
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/tools/annotation/HeightTool.js +439 -0
  51. package/dist/esm/tools/annotation/HeightTool.js.map +1 -0
  52. package/dist/esm/tools/index.js +2 -1
  53. package/dist/esm/tools/index.js.map +1 -1
  54. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +192 -66
  55. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
  56. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +168 -54
  57. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
  58. package/dist/esm/utilities/cine/playClip.js +52 -5
  59. package/dist/esm/utilities/cine/playClip.js.map +1 -1
  60. package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js +27 -0
  61. package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
  62. package/dist/esm/utilities/planar/index.js +3 -1
  63. package/dist/esm/utilities/planar/index.js.map +1 -1
  64. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js +11 -2
  65. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js.map +1 -1
  66. package/dist/esm/utilities/stackPrefetch/stackPrefetchUtils.js +1 -2
  67. package/dist/esm/utilities/stackPrefetch/stackPrefetchUtils.js.map +1 -1
  68. package/dist/esm/utilities/viewport/isViewportPreScaled.js +2 -5
  69. package/dist/esm/utilities/viewport/isViewportPreScaled.js.map +1 -1
  70. package/dist/esm/utilities/viewport/jumpToSlice.js +5 -7
  71. package/dist/esm/utilities/viewport/jumpToSlice.js.map +1 -1
  72. package/dist/types/cursors/SVGCursorDescriptor.d.ts.map +1 -1
  73. package/dist/types/drawingSvg/drawHeight.d.ts +4 -0
  74. package/dist/types/drawingSvg/drawHeight.d.ts.map +1 -0
  75. package/dist/types/drawingSvg/index.d.ts +2 -1
  76. package/dist/types/drawingSvg/index.d.ts.map +1 -1
  77. package/dist/types/index.d.ts +2 -2
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/tools/annotation/HeightTool.d.ts +41 -0
  80. package/dist/types/tools/annotation/HeightTool.d.ts.map +1 -0
  81. package/dist/types/tools/index.d.ts +2 -1
  82. package/dist/types/tools/index.d.ts.map +1 -1
  83. package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
  84. package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts.map +1 -1
  85. package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
  86. package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts.map +1 -1
  87. package/dist/types/types/CINETypes.d.ts +1 -0
  88. package/dist/types/types/CINETypes.d.ts.map +1 -1
  89. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +26 -4
  90. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
  91. package/dist/types/utilities/cine/playClip.d.ts.map +1 -1
  92. package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts +4 -0
  93. package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts.map +1 -0
  94. package/dist/types/utilities/planar/index.d.ts +3 -1
  95. package/dist/types/utilities/planar/index.d.ts.map +1 -1
  96. package/dist/types/utilities/stackPrefetch/stackContextPrefetch.d.ts.map +1 -1
  97. package/dist/types/utilities/stackPrefetch/stackPrefetchUtils.d.ts.map +1 -1
  98. package/dist/types/utilities/viewport/isViewportPreScaled.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/cursors/SVGCursorDescriptor.ts +7 -0
  103. package/src/drawingSvg/drawHeight.ts +90 -0
  104. package/src/drawingSvg/index.ts +2 -0
  105. package/src/index.ts +2 -0
  106. package/src/tools/annotation/HeightTool.ts +882 -0
  107. package/src/tools/index.ts +2 -0
  108. package/src/tools/segmentation/CircleROIStartEndThresholdTool.ts +310 -102
  109. package/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +287 -77
  110. package/src/types/CINETypes.ts +3 -0
  111. package/src/types/ToolSpecificAnnotationTypes.ts +26 -4
  112. package/src/utilities/cine/playClip.ts +67 -8
  113. package/src/utilities/planar/filterAnnotationsWithinPlane.ts +76 -0
  114. package/src/utilities/planar/index.ts +3 -0
  115. package/src/utilities/stackPrefetch/stackContextPrefetch.ts +12 -2
  116. package/src/utilities/stackPrefetch/stackPrefetchUtils.ts +7 -5
  117. package/src/utilities/viewport/isViewportPreScaled.ts +2 -5
  118. package/src/utilities/viewport/jumpToSlice.ts +4 -4
@@ -0,0 +1,882 @@
1
+ import { Events } from '../../enums';
2
+ import { getEnabledElement, utilities as csUtils } from '@cornerstonejs/core';
3
+ import type { Types } from '@cornerstonejs/core';
4
+
5
+ import { getCalibratedLengthUnitsAndScale } from '../../utilities/getCalibratedUnits';
6
+ import { roundNumber } from '../../utilities';
7
+ import { AnnotationTool } from '../base';
8
+ import throttle from '../../utilities/throttle';
9
+ import {
10
+ addAnnotation,
11
+ getAnnotations,
12
+ removeAnnotation,
13
+ } from '../../stateManagement/annotation/annotationState';
14
+ import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
15
+ import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
16
+ import {
17
+ triggerAnnotationCompleted,
18
+ triggerAnnotationModified,
19
+ } from '../../stateManagement/annotation/helpers/state';
20
+ import * as lineSegment from '../../utilities/math/line';
21
+
22
+ import {
23
+ drawHandles as drawHandlesSvg,
24
+ drawHeight as drawHeightSvg,
25
+ drawLinkedTextBox as drawLinkedTextBoxSvg,
26
+ } from '../../drawingSvg';
27
+ import { state } from '../../store';
28
+ import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
29
+ import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
30
+ import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
31
+
32
+ import {
33
+ resetElementCursor,
34
+ hideElementCursor,
35
+ } from '../../cursors/elementCursor';
36
+
37
+ import {
38
+ EventTypes,
39
+ ToolHandle,
40
+ TextBoxHandle,
41
+ PublicToolProps,
42
+ ToolProps,
43
+ SVGDrawingHelper,
44
+ } from '../../types';
45
+ import { LengthAnnotation } from '../../types/ToolSpecificAnnotationTypes';
46
+ import { StyleSpecifier } from '../../types/AnnotationStyle';
47
+
48
+ const { transformWorldToIndex } = csUtils;
49
+
50
+ /**
51
+ * HeightTool let you draw annotations that measures the height of two drawing
52
+ * points on a slice. You can use the HeightTool in all imaging planes even in oblique
53
+ * reconstructed planes. Note: annotation tools in cornerstone3DTools exists in the exact location
54
+ * in the physical 3d space, as a result, by default, all annotations that are
55
+ * drawing in the same frameOfReference will get shared between viewports that
56
+ * are in the same frameOfReference.
57
+ *
58
+ * The resulting annotation's data (statistics) and metadata (the
59
+ * state of the viewport while drawing was happening) will get added to the
60
+ * ToolState manager and can be accessed from the ToolState by calling getAnnotations
61
+ * or similar methods.
62
+ *
63
+ * ```js
64
+ * cornerstoneTools.addTool(HeightTool)
65
+ *
66
+ * const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
67
+ *
68
+ * toolGroup.addTool(HeightTool.toolName)
69
+ *
70
+ * toolGroup.addViewport('viewportId', 'renderingEngineId')
71
+ *
72
+ * toolGroup.setToolActive(HeightTool.toolName, {
73
+ * bindings: [
74
+ * {
75
+ * mouseButton: MouseBindings.Primary, // Left Click
76
+ * },
77
+ * ],
78
+ * })
79
+ * ```
80
+ *
81
+ * Read more in the Docs section of the website.
82
+
83
+ */
84
+
85
+ class HeightTool extends AnnotationTool {
86
+ static toolName;
87
+
88
+ public touchDragCallback: any;
89
+ public mouseDragCallback: any;
90
+ _throttledCalculateCachedStats: any;
91
+ editData: {
92
+ annotation: any;
93
+ viewportIdsToRender: string[];
94
+ handleIndex?: number;
95
+ movingTextBox?: boolean;
96
+ newAnnotation?: boolean;
97
+ hasMoved?: boolean;
98
+ } | null;
99
+ isDrawing: boolean;
100
+ isHandleOutsideImage: boolean;
101
+
102
+ //Lines to generate height
103
+ endfirstLine: Types.Point2;
104
+ endsecondLine: Types.Point2;
105
+ //Middle lines:
106
+ midX: number;
107
+
108
+ constructor(
109
+ toolProps: PublicToolProps = {},
110
+ defaultToolProps: ToolProps = {
111
+ supportedInteractionTypes: ['Mouse', 'Touch'],
112
+ configuration: {
113
+ preventHandleOutsideImage: false,
114
+ getTextLines: defaultGetTextLines,
115
+ },
116
+ }
117
+ ) {
118
+ super(toolProps, defaultToolProps);
119
+
120
+ this._throttledCalculateCachedStats = throttle(
121
+ this._calculateCachedStats,
122
+ 100,
123
+ { trailing: true }
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Based on the current position of the mouse and the current imageId to create
129
+ * a Length Annotation and stores it in the annotationManager
130
+ *
131
+ * @param evt - EventTypes.NormalizedMouseEventType
132
+ * @returns The annotation object.
133
+ *
134
+ */
135
+ addNewAnnotation = (
136
+ evt: EventTypes.InteractionEventType
137
+ ): LengthAnnotation => {
138
+ const eventDetail = evt.detail;
139
+ const { currentPoints, element } = eventDetail;
140
+ const worldPos = currentPoints.world;
141
+ const enabledElement = getEnabledElement(element);
142
+ const { viewport, renderingEngine } = enabledElement;
143
+
144
+ hideElementCursor(element);
145
+ this.isDrawing = true;
146
+
147
+ const {
148
+ viewPlaneNormal,
149
+ viewUp,
150
+ position: cameraPosition,
151
+ } = viewport.getCamera();
152
+ const referencedImageId = this.getReferencedImageId(
153
+ viewport,
154
+ worldPos,
155
+ viewPlaneNormal,
156
+ viewUp
157
+ );
158
+
159
+ const annotation = {
160
+ highlighted: true,
161
+ invalidated: true,
162
+ metadata: {
163
+ ...viewport.getViewReference({ points: [worldPos] }),
164
+ toolName: this.getToolName(),
165
+ referencedImageId,
166
+ viewUp,
167
+ cameraPosition,
168
+ },
169
+ data: {
170
+ handles: {
171
+ points: [<Types.Point3>[...worldPos], <Types.Point3>[...worldPos]],
172
+ activeHandleIndex: null,
173
+ textBox: {
174
+ hasMoved: false,
175
+ worldPosition: <Types.Point3>[0, 0, 0],
176
+ worldBoundingBox: {
177
+ topLeft: <Types.Point3>[0, 0, 0],
178
+ topRight: <Types.Point3>[0, 0, 0],
179
+ bottomLeft: <Types.Point3>[0, 0, 0],
180
+ bottomRight: <Types.Point3>[0, 0, 0],
181
+ },
182
+ },
183
+ },
184
+ label: '',
185
+ cachedStats: {},
186
+ },
187
+ };
188
+
189
+ addAnnotation(annotation, element);
190
+
191
+ const viewportIdsToRender = getViewportIdsWithToolToRender(
192
+ element,
193
+ this.getToolName()
194
+ );
195
+
196
+ this.editData = {
197
+ annotation,
198
+ viewportIdsToRender,
199
+ handleIndex: 1,
200
+ movingTextBox: false,
201
+ newAnnotation: true,
202
+ hasMoved: false,
203
+ };
204
+ this._activateDraw(element);
205
+
206
+ evt.preventDefault();
207
+
208
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
209
+
210
+ return annotation;
211
+ };
212
+
213
+ /**
214
+ * It returns if the canvas point is near the provided height annotation in the provided
215
+ * element or not. A proximity is passed to the function to determine the
216
+ * proximity of the point to the annotation in number of pixels.
217
+ *
218
+ * @param element - HTML Element
219
+ * @param annotation - Annotation
220
+ * @param canvasCoords - Canvas coordinates
221
+ * @param proximity - Proximity to tool to consider
222
+ * @returns Boolean, whether the canvas point is near tool
223
+ */
224
+ isPointNearTool = (
225
+ element: HTMLDivElement,
226
+ annotation: LengthAnnotation,
227
+ canvasCoords: Types.Point2,
228
+ proximity: number
229
+ ): boolean => {
230
+ const enabledElement = getEnabledElement(element);
231
+ const { viewport } = enabledElement;
232
+ const { data } = annotation;
233
+ const [point1, point2] = data.handles.points;
234
+ const canvasPoint1 = viewport.worldToCanvas(point1);
235
+ const canvasPoint2 = viewport.worldToCanvas(point2);
236
+
237
+ const line = {
238
+ start: {
239
+ x: canvasPoint1[0],
240
+ y: canvasPoint1[1],
241
+ },
242
+ end: {
243
+ x: canvasPoint2[0],
244
+ y: canvasPoint2[1],
245
+ },
246
+ };
247
+
248
+ const distanceToPoint = lineSegment.distanceToPoint(
249
+ [line.start.x, line.start.y],
250
+ [line.end.x, line.end.y],
251
+ [canvasCoords[0], canvasCoords[1]]
252
+ );
253
+
254
+ if (distanceToPoint <= proximity) {
255
+ return true;
256
+ }
257
+
258
+ return false;
259
+ };
260
+
261
+ toolSelectedCallback = (
262
+ evt: EventTypes.InteractionEventType,
263
+ annotation: LengthAnnotation
264
+ ): void => {
265
+ const eventDetail = evt.detail;
266
+ const { element } = eventDetail;
267
+
268
+ annotation.highlighted = true;
269
+
270
+ const viewportIdsToRender = getViewportIdsWithToolToRender(
271
+ element,
272
+ this.getToolName()
273
+ );
274
+
275
+ this.editData = {
276
+ annotation,
277
+ viewportIdsToRender,
278
+ movingTextBox: false,
279
+ };
280
+
281
+ this._activateModify(element);
282
+
283
+ hideElementCursor(element);
284
+
285
+ const enabledElement = getEnabledElement(element);
286
+ const { renderingEngine } = enabledElement;
287
+
288
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
289
+
290
+ evt.preventDefault();
291
+ };
292
+
293
+ handleSelectedCallback(
294
+ evt: EventTypes.InteractionEventType,
295
+ annotation: LengthAnnotation,
296
+ handle: ToolHandle
297
+ ): void {
298
+ const eventDetail = evt.detail;
299
+ const { element } = eventDetail;
300
+ const { data } = annotation;
301
+
302
+ annotation.highlighted = true;
303
+
304
+ let movingTextBox = false;
305
+ let handleIndex;
306
+
307
+ if ((handle as TextBoxHandle).worldPosition) {
308
+ movingTextBox = true;
309
+ } else {
310
+ handleIndex = data.handles.points.findIndex((p) => p === handle);
311
+ }
312
+
313
+ // Find viewports to render on drag.
314
+ const viewportIdsToRender = getViewportIdsWithToolToRender(
315
+ element,
316
+ this.getToolName()
317
+ );
318
+
319
+ this.editData = {
320
+ annotation,
321
+ viewportIdsToRender,
322
+ handleIndex,
323
+ movingTextBox,
324
+ };
325
+ this._activateModify(element);
326
+
327
+ hideElementCursor(element);
328
+
329
+ const enabledElement = getEnabledElement(element);
330
+ const { renderingEngine } = enabledElement;
331
+
332
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
333
+
334
+ evt.preventDefault();
335
+ }
336
+
337
+ _endCallback = (evt: EventTypes.InteractionEventType): void => {
338
+ const eventDetail = evt.detail;
339
+ const { element } = eventDetail;
340
+
341
+ const { annotation, viewportIdsToRender, newAnnotation, hasMoved } =
342
+ this.editData;
343
+ const { data } = annotation;
344
+
345
+ if (newAnnotation && !hasMoved) {
346
+ // when user starts the drawing by click, and moving the mouse, instead
347
+ // of click and drag
348
+ return;
349
+ }
350
+
351
+ data.handles.activeHandleIndex = null;
352
+
353
+ this._deactivateModify(element);
354
+ this._deactivateDraw(element);
355
+ resetElementCursor(element);
356
+
357
+ const enabledElement = getEnabledElement(element);
358
+ const { renderingEngine } = enabledElement;
359
+
360
+ if (
361
+ this.isHandleOutsideImage &&
362
+ this.configuration.preventHandleOutsideImage
363
+ ) {
364
+ removeAnnotation(annotation.annotationUID);
365
+ }
366
+
367
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
368
+
369
+ if (newAnnotation) {
370
+ triggerAnnotationCompleted(annotation);
371
+ }
372
+
373
+ this.editData = null;
374
+ this.isDrawing = false;
375
+ };
376
+
377
+ _dragCallback = (evt: EventTypes.InteractionEventType): void => {
378
+ this.isDrawing = true;
379
+ const eventDetail = evt.detail;
380
+ const { element } = eventDetail;
381
+
382
+ const { annotation, viewportIdsToRender, handleIndex, movingTextBox } =
383
+ this.editData;
384
+ const { data } = annotation;
385
+
386
+ if (movingTextBox) {
387
+ // Drag mode - moving text box
388
+ const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
389
+ const worldPosDelta = deltaPoints.world;
390
+
391
+ const { textBox } = data.handles;
392
+ const { worldPosition } = textBox;
393
+
394
+ worldPosition[0] += worldPosDelta[0];
395
+ worldPosition[1] += worldPosDelta[1];
396
+ worldPosition[2] += worldPosDelta[2];
397
+
398
+ textBox.hasMoved = true;
399
+ } else if (handleIndex === undefined) {
400
+ // Drag mode - moving handle
401
+ const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
402
+ const worldPosDelta = deltaPoints.world;
403
+
404
+ const points = data.handles.points;
405
+
406
+ points.forEach((point) => {
407
+ point[0] += worldPosDelta[0];
408
+ point[1] += worldPosDelta[1];
409
+ point[2] += worldPosDelta[2];
410
+ });
411
+ annotation.invalidated = true;
412
+ } else {
413
+ // Move mode - after double click, and mouse move to draw
414
+ const { currentPoints } = eventDetail;
415
+ const worldPos = currentPoints.world;
416
+
417
+ data.handles.points[handleIndex] = [...worldPos];
418
+ annotation.invalidated = true;
419
+ }
420
+
421
+ this.editData.hasMoved = true;
422
+
423
+ const enabledElement = getEnabledElement(element);
424
+ const { renderingEngine } = enabledElement;
425
+
426
+ triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
427
+ };
428
+
429
+ cancel = (element: HTMLDivElement) => {
430
+ // If it is mid-draw or mid-modify
431
+ if (this.isDrawing) {
432
+ this.isDrawing = false;
433
+ this._deactivateDraw(element);
434
+ this._deactivateModify(element);
435
+ resetElementCursor(element);
436
+
437
+ const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
438
+ const { data } = annotation;
439
+
440
+ annotation.highlighted = false;
441
+ data.handles.activeHandleIndex = null;
442
+
443
+ const enabledElement = getEnabledElement(element);
444
+ const { renderingEngine } = enabledElement;
445
+
446
+ triggerAnnotationRenderForViewportIds(
447
+ renderingEngine,
448
+ viewportIdsToRender
449
+ );
450
+
451
+ if (newAnnotation) {
452
+ triggerAnnotationCompleted(annotation);
453
+ }
454
+
455
+ this.editData = null;
456
+ return annotation.annotationUID;
457
+ }
458
+ };
459
+
460
+ _activateModify = (element: HTMLDivElement) => {
461
+ state.isInteractingWithTool = true;
462
+
463
+ element.addEventListener(
464
+ Events.MOUSE_UP,
465
+ this._endCallback as EventListener
466
+ );
467
+ element.addEventListener(
468
+ Events.MOUSE_DRAG,
469
+ this._dragCallback as EventListener
470
+ );
471
+ element.addEventListener(
472
+ Events.MOUSE_CLICK,
473
+ this._endCallback as EventListener
474
+ );
475
+
476
+ element.addEventListener(
477
+ Events.TOUCH_END,
478
+ this._endCallback as EventListener
479
+ );
480
+ element.addEventListener(
481
+ Events.TOUCH_DRAG,
482
+ this._dragCallback as EventListener
483
+ );
484
+ element.addEventListener(
485
+ Events.TOUCH_TAP,
486
+ this._endCallback as EventListener
487
+ );
488
+ };
489
+
490
+ _deactivateModify = (element: HTMLDivElement) => {
491
+ state.isInteractingWithTool = false;
492
+
493
+ element.removeEventListener(
494
+ Events.MOUSE_UP,
495
+ this._endCallback as EventListener
496
+ );
497
+ element.removeEventListener(
498
+ Events.MOUSE_DRAG,
499
+ this._dragCallback as EventListener
500
+ );
501
+ element.removeEventListener(
502
+ Events.MOUSE_CLICK,
503
+ this._endCallback as EventListener
504
+ );
505
+
506
+ element.removeEventListener(
507
+ Events.TOUCH_END,
508
+ this._endCallback as EventListener
509
+ );
510
+ element.removeEventListener(
511
+ Events.TOUCH_DRAG,
512
+ this._dragCallback as EventListener
513
+ );
514
+ element.removeEventListener(
515
+ Events.TOUCH_TAP,
516
+ this._endCallback as EventListener
517
+ );
518
+ };
519
+
520
+ _activateDraw = (element: HTMLDivElement) => {
521
+ state.isInteractingWithTool = true;
522
+
523
+ element.addEventListener(
524
+ Events.MOUSE_UP,
525
+ this._endCallback as EventListener
526
+ );
527
+ element.addEventListener(
528
+ Events.MOUSE_DRAG,
529
+ this._dragCallback as EventListener
530
+ );
531
+ element.addEventListener(
532
+ Events.MOUSE_MOVE,
533
+ this._dragCallback as EventListener
534
+ );
535
+ element.addEventListener(
536
+ Events.MOUSE_CLICK,
537
+ this._endCallback as EventListener
538
+ );
539
+
540
+ element.addEventListener(
541
+ Events.TOUCH_END,
542
+ this._endCallback as EventListener
543
+ );
544
+ element.addEventListener(
545
+ Events.TOUCH_DRAG,
546
+ this._dragCallback as EventListener
547
+ );
548
+ element.addEventListener(
549
+ Events.TOUCH_TAP,
550
+ this._endCallback as EventListener
551
+ );
552
+ };
553
+
554
+ _deactivateDraw = (element: HTMLDivElement) => {
555
+ state.isInteractingWithTool = false;
556
+
557
+ element.removeEventListener(
558
+ Events.MOUSE_UP,
559
+ this._endCallback as EventListener
560
+ );
561
+ element.removeEventListener(
562
+ Events.MOUSE_DRAG,
563
+ this._dragCallback as EventListener
564
+ );
565
+ element.removeEventListener(
566
+ Events.MOUSE_MOVE,
567
+ this._dragCallback as EventListener
568
+ );
569
+ element.removeEventListener(
570
+ Events.MOUSE_CLICK,
571
+ this._endCallback as EventListener
572
+ );
573
+
574
+ element.removeEventListener(
575
+ Events.TOUCH_END,
576
+ this._endCallback as EventListener
577
+ );
578
+ element.removeEventListener(
579
+ Events.TOUCH_DRAG,
580
+ this._dragCallback as EventListener
581
+ );
582
+ element.removeEventListener(
583
+ Events.TOUCH_TAP,
584
+ this._endCallback as EventListener
585
+ );
586
+ };
587
+
588
+ /**
589
+ * it is used to draw the height annotation in each
590
+ * request animation frame. It calculates the updated cached statistics if
591
+ * data is invalidated and cache it.
592
+ *
593
+ * @param enabledElement - The Cornerstone's enabledElement.
594
+ * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
595
+ */
596
+ renderAnnotation = (
597
+ enabledElement: Types.IEnabledElement,
598
+ svgDrawingHelper: SVGDrawingHelper
599
+ ): boolean => {
600
+ let renderStatus = false;
601
+ const { viewport } = enabledElement;
602
+ const { element } = viewport;
603
+
604
+ let annotations = getAnnotations(this.getToolName(), element);
605
+
606
+ // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
607
+ if (!annotations?.length) {
608
+ return renderStatus;
609
+ }
610
+
611
+ annotations = this.filterInteractableAnnotationsForElement(
612
+ element,
613
+ annotations
614
+ );
615
+
616
+ if (!annotations?.length) {
617
+ return renderStatus;
618
+ }
619
+
620
+ const targetId = this.getTargetId(viewport);
621
+ const renderingEngine = viewport.getRenderingEngine();
622
+
623
+ const styleSpecifier: StyleSpecifier = {
624
+ toolGroupId: this.toolGroupId,
625
+ toolName: this.getToolName(),
626
+ viewportId: enabledElement.viewport.id,
627
+ };
628
+
629
+ // Draw SVG
630
+ for (let i = 0; i < annotations.length; i++) {
631
+ const annotation = annotations[i] as LengthAnnotation;
632
+ const { annotationUID, data } = annotation;
633
+ const { points, activeHandleIndex } = data.handles;
634
+
635
+ styleSpecifier.annotationUID = annotationUID;
636
+
637
+ const { color, lineWidth, lineDash, shadow } = this.getAnnotationStyle({
638
+ annotation,
639
+ styleSpecifier,
640
+ });
641
+
642
+ const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
643
+
644
+ let activeHandleCanvasCoords;
645
+
646
+ // If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
647
+ // force to recalculate the stats from the points
648
+ if (
649
+ !data.cachedStats[targetId] ||
650
+ data.cachedStats[targetId].unit == null
651
+ ) {
652
+ data.cachedStats[targetId] = {
653
+ length: null,
654
+ unit: null,
655
+ };
656
+
657
+ this._calculateCachedStats(annotation, renderingEngine, enabledElement);
658
+ } else if (annotation.invalidated) {
659
+ this._throttledCalculateCachedStats(
660
+ annotation,
661
+ renderingEngine,
662
+ enabledElement
663
+ );
664
+ }
665
+
666
+ if (!isAnnotationVisible(annotationUID)) {
667
+ continue;
668
+ }
669
+
670
+ if (
671
+ !isAnnotationLocked(annotation) &&
672
+ !this.editData &&
673
+ activeHandleIndex !== null
674
+ ) {
675
+ // Not locked or creating and hovering over handle, so render handle.
676
+ activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
677
+ }
678
+
679
+ if (activeHandleCanvasCoords) {
680
+ const handleGroupUID = '0';
681
+
682
+ drawHandlesSvg(
683
+ svgDrawingHelper,
684
+ annotationUID,
685
+ handleGroupUID,
686
+ canvasCoordinates,
687
+ {
688
+ color,
689
+ lineDash,
690
+ lineWidth,
691
+ }
692
+ );
693
+ }
694
+
695
+ const heightUID = '0';
696
+
697
+ //Draw Height:
698
+ drawHeightSvg(
699
+ svgDrawingHelper,
700
+ annotationUID,
701
+ heightUID,
702
+ canvasCoordinates[0],
703
+ canvasCoordinates[1],
704
+ {
705
+ color,
706
+ width: lineWidth,
707
+ lineDash: lineDash,
708
+ }
709
+ );
710
+
711
+ renderStatus = true;
712
+
713
+ // If rendering engine has been destroyed while rendering
714
+ if (!viewport.getRenderingEngine()) {
715
+ console.warn('Rendering Engine has been destroyed');
716
+ return renderStatus;
717
+ }
718
+
719
+ const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
720
+ if (!options.visibility) {
721
+ data.handles.textBox = {
722
+ hasMoved: false,
723
+ worldPosition: <Types.Point3>[0, 0, 0],
724
+ worldBoundingBox: {
725
+ topLeft: <Types.Point3>[0, 0, 0],
726
+ topRight: <Types.Point3>[0, 0, 0],
727
+ bottomLeft: <Types.Point3>[0, 0, 0],
728
+ bottomRight: <Types.Point3>[0, 0, 0],
729
+ },
730
+ };
731
+ continue;
732
+ }
733
+
734
+ const textLines = this.configuration.getTextLines(data, targetId);
735
+
736
+ // Need to update to sync with annotation while unlinked/not moved
737
+ if (!data.handles.textBox.hasMoved) {
738
+ const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
739
+
740
+ data.handles.textBox.worldPosition =
741
+ viewport.canvasToWorld(canvasTextBoxCoords);
742
+ }
743
+
744
+ const textBoxPosition = viewport.worldToCanvas(
745
+ data.handles.textBox.worldPosition
746
+ );
747
+
748
+ const textBoxUID = '1';
749
+ const boundingBox = drawLinkedTextBoxSvg(
750
+ svgDrawingHelper,
751
+ annotationUID,
752
+ textBoxUID,
753
+ textLines,
754
+ textBoxPosition,
755
+ canvasCoordinates,
756
+ {},
757
+ options
758
+ );
759
+
760
+ const { x: left, y: top, width, height } = boundingBox;
761
+
762
+ data.handles.textBox.worldBoundingBox = {
763
+ topLeft: viewport.canvasToWorld([left, top]),
764
+ topRight: viewport.canvasToWorld([left + width, top]),
765
+ bottomLeft: viewport.canvasToWorld([left, top + height]),
766
+ bottomRight: viewport.canvasToWorld([left + width, top + height]),
767
+ };
768
+ }
769
+
770
+ return renderStatus;
771
+ };
772
+
773
+ _calculateHeight(pos1, pos2) {
774
+ const dx = pos2[0] - pos1[0];
775
+ const dy = pos2[1] - pos1[1];
776
+ const dz = pos2[2] - pos1[2];
777
+ //SAGITAL X alway 0
778
+ //CORONAL Y alway 0
779
+ //AXIAL Z alway 0
780
+
781
+ //SAGITAL:
782
+ if (dx == 0) {
783
+ //SAGITAL when it reaches 0 takes the measurement of Y, to correct it we return 0.
784
+ if (dy != 0) {
785
+ //SAGITAL use Z:
786
+ return Math.abs(dz);
787
+ } else {
788
+ return 0;
789
+ }
790
+ }
791
+ //SAGITAL AND CORONAL use Z:
792
+ //CORONAL:
793
+ else if (dy == 0) {
794
+ //CORONAL use Z:
795
+ return Math.abs(dz);
796
+ }
797
+ //AXIAL
798
+ else if (dz == 0) {
799
+ //AXIAL use Y:
800
+ return Math.abs(dy);
801
+ }
802
+ }
803
+
804
+ _calculateCachedStats(annotation, renderingEngine, enabledElement) {
805
+ const data = annotation.data;
806
+ const { element } = enabledElement.viewport;
807
+
808
+ const worldPos1 = data.handles.points[0];
809
+ const worldPos2 = data.handles.points[1];
810
+ const { cachedStats } = data;
811
+ const targetIds = Object.keys(cachedStats);
812
+
813
+ // TODO clean up, this doesn't need a length per volume, it has no stats derived from volumes.
814
+
815
+ for (let i = 0; i < targetIds.length; i++) {
816
+ const targetId = targetIds[i];
817
+
818
+ const image = this.getTargetIdImage(targetId, renderingEngine);
819
+
820
+ // If image does not exists for the targetId, skip. This can be due
821
+ // to various reasons such as if the target was a volumeViewport, and
822
+ // the volumeViewport has been decached in the meantime.
823
+ if (!image) {
824
+ continue;
825
+ }
826
+
827
+ const { imageData, dimensions } = image;
828
+
829
+ const index1 = transformWorldToIndex(imageData, worldPos1);
830
+ const index2 = transformWorldToIndex(imageData, worldPos2);
831
+ const handles = [index1, index2];
832
+ const { scale, units } = getCalibratedLengthUnitsAndScale(image, handles);
833
+
834
+ const height = this._calculateHeight(worldPos1, worldPos2) / scale;
835
+
836
+ this._isInsideVolume(index1, index2, dimensions)
837
+ ? (this.isHandleOutsideImage = false)
838
+ : (this.isHandleOutsideImage = true);
839
+
840
+ // TODO -> Do we instead want to clip to the bounds of the volume and only include that portion?
841
+ // Seems like a lot of work for an unrealistic case. At the moment bail out of stat calculation if either
842
+ // corner is off the canvas.
843
+
844
+ // todo: add insideVolume calculation, for removing tool if outside
845
+ cachedStats[targetId] = {
846
+ height,
847
+ unit: units,
848
+ };
849
+ }
850
+
851
+ annotation.invalidated = false;
852
+
853
+ // Dispatching annotation modified
854
+ triggerAnnotationModified(annotation, element);
855
+
856
+ return cachedStats;
857
+ }
858
+
859
+ _isInsideVolume(index1, index2, dimensions) {
860
+ return (
861
+ csUtils.indexWithinDimensions(index1, dimensions) &&
862
+ csUtils.indexWithinDimensions(index2, dimensions)
863
+ );
864
+ }
865
+ }
866
+
867
+ function defaultGetTextLines(data, targetId): string[] {
868
+ const cachedVolumeStats = data.cachedStats[targetId];
869
+ const { height, unit } = cachedVolumeStats;
870
+
871
+ // Can be null on load
872
+ if (height === undefined || height === null || isNaN(height)) {
873
+ return;
874
+ }
875
+
876
+ const textLines = [`${roundNumber(height)} ${unit}`];
877
+
878
+ return textLines;
879
+ }
880
+
881
+ HeightTool.toolName = 'Height';
882
+ export default HeightTool;