@cornerstonejs/tools 1.25.0 → 1.26.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 (84) hide show
  1. package/dist/cjs/drawingSvg/drawRedactionRect.d.ts +1 -0
  2. package/dist/cjs/drawingSvg/drawRedactionRect.js +44 -0
  3. package/dist/cjs/drawingSvg/drawRedactionRect.js.map +1 -0
  4. package/dist/cjs/drawingSvg/index.d.ts +2 -1
  5. package/dist/cjs/drawingSvg/index.js +3 -1
  6. package/dist/cjs/drawingSvg/index.js.map +1 -1
  7. package/dist/cjs/eventListeners/touch/touchStartListener.js +0 -8
  8. package/dist/cjs/eventListeners/touch/touchStartListener.js.map +1 -1
  9. package/dist/cjs/index.d.ts +2 -1
  10. package/dist/cjs/index.js +7 -2
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/tools/ZoomTool.js +2 -5
  13. package/dist/cjs/tools/ZoomTool.js.map +1 -1
  14. package/dist/cjs/tools/annotation/VideoRedactionTool.d.ts +46 -0
  15. package/dist/cjs/tools/annotation/VideoRedactionTool.js +492 -0
  16. package/dist/cjs/tools/annotation/VideoRedactionTool.js.map +1 -0
  17. package/dist/cjs/tools/base/AnnotationDisplayTool.d.ts +1 -1
  18. package/dist/cjs/tools/base/AnnotationDisplayTool.js +2 -1
  19. package/dist/cjs/tools/base/AnnotationDisplayTool.js.map +1 -1
  20. package/dist/cjs/tools/base/AnnotationTool.js.map +1 -1
  21. package/dist/cjs/tools/base/BaseTool.js +3 -0
  22. package/dist/cjs/tools/base/BaseTool.js.map +1 -1
  23. package/dist/cjs/types/AnnotationTypes.d.ts +1 -1
  24. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +20 -0
  25. package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js +6 -0
  26. package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
  27. package/dist/cjs/utilities/scroll.d.ts +1 -1
  28. package/dist/cjs/utilities/scroll.js +3 -0
  29. package/dist/cjs/utilities/scroll.js.map +1 -1
  30. package/dist/cjs/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.d.ts +1 -1
  31. package/dist/cjs/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.js.map +1 -1
  32. package/dist/cjs/utilities/viewportFilters/filterViewportsWithToolEnabled.d.ts +1 -1
  33. package/dist/cjs/utilities/viewportFilters/filterViewportsWithToolEnabled.js.map +1 -1
  34. package/dist/esm/drawingSvg/drawRedactionRect.d.ts +1 -0
  35. package/dist/esm/drawingSvg/drawRedactionRect.js +38 -0
  36. package/dist/esm/drawingSvg/drawRedactionRect.js.map +1 -0
  37. package/dist/esm/drawingSvg/index.d.ts +2 -1
  38. package/dist/esm/drawingSvg/index.js +2 -1
  39. package/dist/esm/drawingSvg/index.js.map +1 -1
  40. package/dist/esm/eventListeners/touch/touchStartListener.js +0 -8
  41. package/dist/esm/eventListeners/touch/touchStartListener.js.map +1 -1
  42. package/dist/esm/index.d.ts +2 -1
  43. package/dist/esm/index.js +2 -1
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/tools/ZoomTool.js +2 -5
  46. package/dist/esm/tools/ZoomTool.js.map +1 -1
  47. package/dist/esm/tools/annotation/VideoRedactionTool.d.ts +46 -0
  48. package/dist/esm/tools/annotation/VideoRedactionTool.js +464 -0
  49. package/dist/esm/tools/annotation/VideoRedactionTool.js.map +1 -0
  50. package/dist/esm/tools/base/AnnotationDisplayTool.d.ts +1 -1
  51. package/dist/esm/tools/base/AnnotationDisplayTool.js +3 -2
  52. package/dist/esm/tools/base/AnnotationDisplayTool.js.map +1 -1
  53. package/dist/esm/tools/base/AnnotationTool.js.map +1 -1
  54. package/dist/esm/tools/base/BaseTool.js +4 -1
  55. package/dist/esm/tools/base/BaseTool.js.map +1 -1
  56. package/dist/esm/types/AnnotationTypes.d.ts +1 -1
  57. package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +20 -0
  58. package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js +7 -1
  59. package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
  60. package/dist/esm/utilities/scroll.d.ts +1 -1
  61. package/dist/esm/utilities/scroll.js +4 -1
  62. package/dist/esm/utilities/scroll.js.map +1 -1
  63. package/dist/esm/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.d.ts +1 -1
  64. package/dist/esm/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.js.map +1 -1
  65. package/dist/esm/utilities/viewportFilters/filterViewportsWithToolEnabled.d.ts +1 -1
  66. package/dist/esm/utilities/viewportFilters/filterViewportsWithToolEnabled.js.map +1 -1
  67. package/dist/umd/index.js +1 -1
  68. package/dist/umd/index.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/drawingSvg/drawRedactionRect.ts +62 -0
  71. package/src/drawingSvg/index.ts +2 -0
  72. package/src/eventListeners/touch/touchStartListener.ts +3 -10
  73. package/src/index.ts +3 -0
  74. package/src/tools/ZoomTool.ts +2 -10
  75. package/src/tools/annotation/VideoRedactionTool.ts +788 -0
  76. package/src/tools/base/AnnotationDisplayTool.ts +6 -3
  77. package/src/tools/base/AnnotationTool.ts +1 -1
  78. package/src/tools/base/BaseTool.ts +3 -0
  79. package/src/types/AnnotationTypes.ts +1 -1
  80. package/src/types/ToolSpecificAnnotationTypes.ts +21 -0
  81. package/src/utilities/planar/filterAnnotationsForDisplay.ts +7 -0
  82. package/src/utilities/scroll.ts +4 -1
  83. package/src/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.ts +1 -1
  84. package/src/utilities/viewportFilters/filterViewportsWithToolEnabled.ts +1 -1
@@ -0,0 +1,788 @@
1
+ import { vec3, vec2 } from 'gl-matrix';
2
+
3
+ import {
4
+ getEnabledElement,
5
+ triggerEvent,
6
+ eventTarget,
7
+ utilities as csUtils,
8
+ cache,
9
+ } from '@cornerstonejs/core';
10
+ import type { Types } from '@cornerstonejs/core';
11
+ import { AnnotationTool } from '../base';
12
+
13
+ import throttle from '../../utilities/throttle';
14
+ import {
15
+ addAnnotation,
16
+ getAnnotations,
17
+ removeAnnotation,
18
+ } from '../../stateManagement';
19
+ import {
20
+ drawHandles as drawHandlesSvg,
21
+ drawRedactionRect as drawRedactionRectSvg,
22
+ } from '../../drawingSvg';
23
+ import { state } from '../../store';
24
+ import { Events } from '../../enums';
25
+ import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
26
+ import * as rectangle from '../../utilities/math/rectangle';
27
+ import {
28
+ resetElementCursor,
29
+ hideElementCursor,
30
+ } from '../../cursors/elementCursor';
31
+ import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
32
+
33
+ import { EventTypes, SVGDrawingHelper } from '../../types';
34
+ import { StyleSpecifier } from '../../types/AnnotationStyle';
35
+ import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints';
36
+ import { VideoRedactionAnnotation } from '../../types/ToolSpecificAnnotationTypes';
37
+
38
+ class VideoRedactionTool extends AnnotationTool {
39
+ _throttledCalculateCachedStats: any;
40
+ editData: {
41
+ annotation: any;
42
+ viewportUIDsToRender: string[];
43
+ handleIndex?: number;
44
+ newAnnotation?: boolean;
45
+ hasMoved?: boolean;
46
+ } | null;
47
+ _configuration: any;
48
+ isDrawing: boolean;
49
+ isHandleOutsideImage: boolean;
50
+
51
+ constructor(toolConfiguration = {}) {
52
+ super(toolConfiguration, {
53
+ supportedInteractionTypes: ['Mouse', 'Touch'],
54
+ configuration: { shadow: true, preventHandleOutsideImage: false },
55
+ });
56
+
57
+ this._throttledCalculateCachedStats = throttle(
58
+ this._calculateCachedStats,
59
+ 100,
60
+ { trailing: true }
61
+ );
62
+ }
63
+
64
+ addNewAnnotation = (
65
+ evt: EventTypes.InteractionEventType
66
+ ): VideoRedactionAnnotation => {
67
+ const eventData = evt.detail;
68
+ const { currentPoints, element } = eventData;
69
+ const worldPos = currentPoints.world;
70
+
71
+ const enabledElement = getEnabledElement(element);
72
+ const { viewport, renderingEngine } = enabledElement;
73
+
74
+ this.isDrawing = true;
75
+
76
+ const annotation = {
77
+ metadata: {
78
+ // We probably just want a different type of data here, hacking this
79
+ // together for now.
80
+ viewPlaneNormal: <Types.Point3>[0, 0, 1],
81
+ viewUp: <Types.Point3>[0, 1, 0],
82
+ FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
83
+ referencedImageId: viewport.getFrameOfReferenceUID(),
84
+ toolName: this.getToolName(),
85
+ },
86
+ data: {
87
+ invalidated: true,
88
+ handles: {
89
+ points: [
90
+ <Types.Point3>[...worldPos],
91
+ <Types.Point3>[...worldPos],
92
+ <Types.Point3>[...worldPos],
93
+ <Types.Point3>[...worldPos],
94
+ ],
95
+ activeHandleIndex: null,
96
+ },
97
+ cachedStats: {},
98
+ active: true,
99
+ },
100
+ };
101
+
102
+ addAnnotation(annotation, element);
103
+
104
+ const viewportUIDsToRender = getViewportIdsWithToolToRender(
105
+ element,
106
+ this.getToolName(),
107
+ false
108
+ );
109
+
110
+ this.editData = {
111
+ annotation,
112
+ viewportUIDsToRender,
113
+ handleIndex: 3,
114
+ newAnnotation: true,
115
+ hasMoved: false,
116
+ };
117
+ this._activateDraw(element);
118
+
119
+ hideElementCursor(element);
120
+
121
+ evt.preventDefault();
122
+
123
+ triggerAnnotationRenderForViewportIds(
124
+ renderingEngine,
125
+ viewportUIDsToRender
126
+ );
127
+
128
+ return annotation;
129
+ };
130
+
131
+ getHandleNearImagePoint = (element, annotation, canvasCoords, proximity) => {
132
+ const enabledElement = getEnabledElement(element);
133
+ const { viewport } = enabledElement;
134
+
135
+ const { data } = annotation;
136
+ const { points } = data.handles;
137
+
138
+ for (let i = 0; i < points.length; i++) {
139
+ const point = points[i];
140
+ const toolDataCanvasCoordinate = viewport.worldToCanvas(point);
141
+
142
+ const near =
143
+ vec2.distance(canvasCoords, <vec2>toolDataCanvasCoordinate) < proximity;
144
+
145
+ if (near === true) {
146
+ data.handles.activeHandleIndex = i;
147
+ return point;
148
+ }
149
+ }
150
+
151
+ data.handles.activeHandleIndex = null;
152
+ };
153
+
154
+ isPointNearTool = (element, annotation, canvasCoords, proximity) => {
155
+ const enabledElement = getEnabledElement(element);
156
+ const { viewport } = enabledElement;
157
+
158
+ const { data } = annotation;
159
+ const { points } = data.handles;
160
+
161
+ const canvasPoint1 = viewport.worldToCanvas(points[0]);
162
+ const canvasPoint2 = viewport.worldToCanvas(points[3]);
163
+
164
+ const rect = this._getRectangleImageCoordinates([
165
+ canvasPoint1,
166
+ canvasPoint2,
167
+ ]);
168
+
169
+ const point = [canvasCoords[0], canvasCoords[1]] as Types.Point2;
170
+ const { left, top, width, height } = rect;
171
+
172
+ const distanceToPoint = rectangle.distanceToPoint(
173
+ [left, top, width, height],
174
+ point
175
+ );
176
+
177
+ if (distanceToPoint <= proximity) {
178
+ return true;
179
+ }
180
+ };
181
+
182
+ toolSelectedCallback = (evt, annotation, interactionType = 'mouse') => {
183
+ const eventData = evt.detail;
184
+ const { element } = eventData;
185
+
186
+ const { data } = annotation;
187
+
188
+ data.active = true;
189
+
190
+ const viewportUIDsToRender = getViewportIdsWithToolToRender(
191
+ element,
192
+ this.getToolName(),
193
+ false
194
+ );
195
+
196
+ this.editData = {
197
+ annotation,
198
+ viewportUIDsToRender,
199
+ };
200
+
201
+ this._activateModify(element);
202
+
203
+ hideElementCursor(element);
204
+
205
+ const enabledElement = getEnabledElement(element);
206
+ const { renderingEngine } = enabledElement;
207
+
208
+ triggerAnnotationRenderForViewportIds(
209
+ renderingEngine,
210
+ viewportUIDsToRender
211
+ );
212
+
213
+ evt.preventDefault();
214
+ };
215
+
216
+ handleSelectedCallback = (
217
+ evt,
218
+ annotation,
219
+ handle,
220
+ interactionType = 'mouse'
221
+ ) => {
222
+ const eventData = evt.detail;
223
+ const { element } = eventData;
224
+ const { data } = annotation;
225
+
226
+ data.active = true;
227
+
228
+ let movingTextBox = false;
229
+ let handleIndex;
230
+
231
+ if (handle.worldPosition) {
232
+ movingTextBox = true;
233
+ } else {
234
+ handleIndex = data.handles.points.findIndex((p) => p === handle);
235
+ }
236
+
237
+ // Find viewports to render on drag.
238
+ const viewportUIDsToRender = getViewportIdsWithToolToRender(
239
+ element,
240
+ this.getToolName(),
241
+ false
242
+ );
243
+
244
+ this.editData = {
245
+ annotation,
246
+ viewportUIDsToRender,
247
+ handleIndex,
248
+ };
249
+ this._activateModify(element);
250
+
251
+ hideElementCursor(element);
252
+
253
+ const enabledElement = getEnabledElement(element);
254
+ const { renderingEngine } = enabledElement;
255
+
256
+ triggerAnnotationRenderForViewportIds(
257
+ renderingEngine,
258
+ viewportUIDsToRender
259
+ );
260
+
261
+ evt.preventDefault();
262
+ };
263
+
264
+ _mouseUpCallback = (evt) => {
265
+ const eventData = evt.detail;
266
+ const { element } = eventData;
267
+
268
+ const { annotation, viewportUIDsToRender, newAnnotation, hasMoved } =
269
+ this.editData;
270
+ const { data } = annotation;
271
+
272
+ if (newAnnotation && !hasMoved) {
273
+ return;
274
+ }
275
+
276
+ data.active = false;
277
+ data.handles.activeHandleIndex = null;
278
+
279
+ this._deactivateModify(element);
280
+ this._deactivateDraw(element);
281
+
282
+ resetElementCursor(element);
283
+
284
+ const enabledElement = getEnabledElement(element);
285
+ const { renderingEngine } = enabledElement;
286
+
287
+ this.editData = null;
288
+ this.isDrawing = false;
289
+
290
+ if (
291
+ this.isHandleOutsideImage &&
292
+ this.configuration.preventHandleOutsideImage
293
+ ) {
294
+ removeAnnotation(annotation.annotationUID);
295
+ }
296
+
297
+ triggerAnnotationRenderForViewportIds(
298
+ renderingEngine,
299
+ viewportUIDsToRender
300
+ );
301
+ };
302
+
303
+ _mouseDragCallback = (evt) => {
304
+ this.isDrawing = true;
305
+
306
+ const eventData = evt.detail;
307
+ const { element } = eventData;
308
+
309
+ const { annotation, viewportUIDsToRender, handleIndex } = this.editData;
310
+ const { data } = annotation;
311
+
312
+ if (handleIndex === undefined) {
313
+ // Moving tool, so move all points by the world points delta
314
+ const { deltaPoints } = eventData;
315
+ const worldPosDelta = deltaPoints.world;
316
+
317
+ const { points } = data.handles;
318
+
319
+ points.forEach((point) => {
320
+ point[0] += worldPosDelta[0];
321
+ point[1] += worldPosDelta[1];
322
+ point[2] += worldPosDelta[2];
323
+ });
324
+ data.invalidated = true;
325
+ } else {
326
+ // Moving handle.
327
+ const { currentPoints } = eventData;
328
+ const enabledElement = getEnabledElement(element);
329
+ const { worldToCanvas, canvasToWorld } = enabledElement.viewport;
330
+ const worldPos = currentPoints.world;
331
+
332
+ const { points } = data.handles;
333
+
334
+ // Move this handle.
335
+ points[handleIndex] = [...worldPos];
336
+
337
+ let bottomLeftCanvas;
338
+ let bottomRightCanvas;
339
+ let topLeftCanvas;
340
+ let topRightCanvas;
341
+
342
+ let bottomLeftWorld;
343
+ let bottomRightWorld;
344
+ let topLeftWorld;
345
+ let topRightWorld;
346
+
347
+ switch (handleIndex) {
348
+ case 0:
349
+ case 3:
350
+ // Moving bottomLeft or topRight
351
+
352
+ bottomLeftCanvas = worldToCanvas(points[0]);
353
+ topRightCanvas = worldToCanvas(points[3]);
354
+
355
+ bottomRightCanvas = [topRightCanvas[0], bottomLeftCanvas[1]];
356
+ topLeftCanvas = [bottomLeftCanvas[0], topRightCanvas[1]];
357
+
358
+ bottomRightWorld = canvasToWorld(bottomRightCanvas);
359
+ topLeftWorld = canvasToWorld(topLeftCanvas);
360
+
361
+ points[1] = bottomRightWorld;
362
+ points[2] = topLeftWorld;
363
+
364
+ break;
365
+ case 1:
366
+ case 2:
367
+ // Moving bottomRight or topLeft
368
+ bottomRightCanvas = worldToCanvas(points[1]);
369
+ topLeftCanvas = worldToCanvas(points[2]);
370
+
371
+ bottomLeftCanvas = <Types.Point2>[
372
+ topLeftCanvas[0],
373
+ bottomRightCanvas[1],
374
+ ];
375
+ topRightCanvas = <Types.Point2>[
376
+ bottomRightCanvas[0],
377
+ topLeftCanvas[1],
378
+ ];
379
+
380
+ bottomLeftWorld = canvasToWorld(bottomLeftCanvas);
381
+ topRightWorld = canvasToWorld(topRightCanvas);
382
+
383
+ points[0] = bottomLeftWorld;
384
+ points[3] = topRightWorld;
385
+
386
+ break;
387
+ }
388
+ data.invalidated = true;
389
+ }
390
+
391
+ this.editData.hasMoved = true;
392
+
393
+ const enabledElement = getEnabledElement(element);
394
+ const { renderingEngine } = enabledElement;
395
+
396
+ triggerAnnotationRenderForViewportIds(
397
+ renderingEngine,
398
+ viewportUIDsToRender
399
+ );
400
+ };
401
+
402
+ cancel(element) {
403
+ // If it is mid-draw or mid-modify
404
+ if (!this.isDrawing) {
405
+ return;
406
+ }
407
+ this.isDrawing = false;
408
+ this._deactivateDraw(element);
409
+ this._deactivateModify(element);
410
+ resetElementCursor(element);
411
+
412
+ const { annotation, viewportUIDsToRender } = this.editData;
413
+
414
+ const { data } = annotation;
415
+
416
+ data.active = false;
417
+ data.handles.activeHandleIndex = null;
418
+
419
+ const enabledElement = getEnabledElement(element);
420
+ const { renderingEngine } = enabledElement;
421
+
422
+ triggerAnnotationRenderForViewportIds(
423
+ renderingEngine,
424
+ viewportUIDsToRender
425
+ );
426
+
427
+ this.editData = null;
428
+ return annotation.metadata.annotationUID;
429
+ }
430
+ /**
431
+ * Add event handlers for the modify event loop, and prevent default event prapogation.
432
+ */
433
+ _activateDraw = (element) => {
434
+ state.isInteractingWithTool = true;
435
+
436
+ element.addEventListener(Events.MOUSE_UP, this._mouseUpCallback);
437
+ element.addEventListener(Events.MOUSE_DRAG, this._mouseDragCallback);
438
+ element.addEventListener(Events.MOUSE_MOVE, this._mouseDragCallback);
439
+ element.addEventListener(Events.MOUSE_CLICK, this._mouseUpCallback);
440
+
441
+ element.addEventListener(Events.TOUCH_END, this._mouseUpCallback);
442
+ element.addEventListener(Events.TOUCH_DRAG, this._mouseDragCallback);
443
+ };
444
+
445
+ /**
446
+ * Add event handlers for the modify event loop, and prevent default event prapogation.
447
+ */
448
+ _deactivateDraw = (element) => {
449
+ state.isInteractingWithTool = false;
450
+
451
+ element.removeEventListener(Events.MOUSE_UP, this._mouseUpCallback);
452
+ element.removeEventListener(Events.MOUSE_DRAG, this._mouseDragCallback);
453
+ element.removeEventListener(Events.MOUSE_MOVE, this._mouseDragCallback);
454
+ element.removeEventListener(Events.MOUSE_CLICK, this._mouseUpCallback);
455
+
456
+ element.removeEventListener(Events.TOUCH_END, this._mouseUpCallback);
457
+ element.removeEventListener(Events.TOUCH_DRAG, this._mouseDragCallback);
458
+ };
459
+
460
+ /**
461
+ * Add event handlers for the modify event loop, and prevent default event prapogation.
462
+ */
463
+ _activateModify = (element) => {
464
+ state.isInteractingWithTool = true;
465
+
466
+ element.addEventListener(Events.MOUSE_UP, this._mouseUpCallback);
467
+ element.addEventListener(Events.MOUSE_DRAG, this._mouseDragCallback);
468
+ element.addEventListener(Events.MOUSE_CLICK, this._mouseUpCallback);
469
+
470
+ element.addEventListener(Events.TOUCH_END, this._mouseUpCallback);
471
+ element.addEventListener(Events.TOUCH_DRAG, this._mouseDragCallback);
472
+ };
473
+
474
+ /**
475
+ * Remove event handlers for the modify event loop, and enable default event propagation.
476
+ */
477
+ _deactivateModify = (element) => {
478
+ state.isInteractingWithTool = false;
479
+
480
+ element.removeEventListener(Events.MOUSE_UP, this._mouseUpCallback);
481
+ element.removeEventListener(Events.MOUSE_DRAG, this._mouseDragCallback);
482
+ element.removeEventListener(Events.MOUSE_CLICK, this._mouseUpCallback);
483
+
484
+ element.removeEventListener(Events.TOUCH_END, this._mouseUpCallback);
485
+ element.removeEventListener(Events.TOUCH_DRAG, this._mouseDragCallback);
486
+ };
487
+
488
+ renderAnnotation = (
489
+ enabledElement: Types.IEnabledElement,
490
+ svgDrawingHelper: SVGDrawingHelper
491
+ ): boolean => {
492
+ const renderStatus = false;
493
+ const { viewport } = enabledElement;
494
+ const { element } = viewport;
495
+
496
+ let annotations = getAnnotations(this.getToolName(), element);
497
+
498
+ if (!annotations?.length) {
499
+ return renderStatus;
500
+ }
501
+
502
+ annotations = this.filterInteractableAnnotationsForElement(
503
+ element,
504
+ annotations
505
+ );
506
+
507
+ if (!annotations?.length) {
508
+ return renderStatus;
509
+ }
510
+
511
+ const targetId = this.getTargetId(viewport);
512
+ const renderingEngine = viewport.getRenderingEngine();
513
+
514
+ const styleSpecifier: StyleSpecifier = {
515
+ toolGroupId: this.toolGroupId,
516
+ toolName: this.getToolName(),
517
+ viewportId: enabledElement.viewport.id,
518
+ };
519
+
520
+ for (let i = 0; i < annotations.length; i++) {
521
+ const annotation = annotations[i];
522
+ const { annotationUID } = annotation;
523
+ const toolMetadata = annotation.metadata;
524
+
525
+ const data = annotation.data;
526
+ const { points, activeHandleIndex } = data.handles;
527
+ const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
528
+
529
+ const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
530
+ const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
531
+ const color = this.getStyle('color', styleSpecifier, annotation);
532
+ // If rendering engine has been destroyed while rendering
533
+ if (!viewport.getRenderingEngine()) {
534
+ console.warn('Rendering Engine has been destroyed');
535
+ return;
536
+ }
537
+
538
+ let activeHandleCanvasCoords;
539
+
540
+ if (
541
+ // !isToolDataLocked(toolData) &&
542
+ !this.editData &&
543
+ activeHandleIndex !== null
544
+ ) {
545
+ // Not locked or creating and hovering over handle, so render handle.
546
+ activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
547
+ }
548
+
549
+ if (activeHandleCanvasCoords) {
550
+ const handleGroupUID = '0';
551
+
552
+ drawHandlesSvg(
553
+ svgDrawingHelper,
554
+ annotationUID,
555
+ handleGroupUID,
556
+ activeHandleCanvasCoords,
557
+ {
558
+ color,
559
+ }
560
+ );
561
+ }
562
+
563
+ const rectangleUID = '0';
564
+ drawRedactionRectSvg(
565
+ svgDrawingHelper,
566
+ annotationUID,
567
+ rectangleUID,
568
+ canvasCoordinates[0],
569
+ canvasCoordinates[3],
570
+ {
571
+ color: 'black',
572
+ lineDash,
573
+ lineWidth,
574
+ }
575
+ );
576
+ }
577
+ };
578
+
579
+ _getRectangleImageCoordinates = (
580
+ points: Array<Types.Point2>
581
+ ): {
582
+ left: number;
583
+ top: number;
584
+ width: number;
585
+ height: number;
586
+ } => {
587
+ const [point0, point1] = points;
588
+
589
+ return {
590
+ left: Math.min(point0[0], point1[0]),
591
+ top: Math.min(point0[1], point1[1]),
592
+ width: Math.abs(point0[0] - point1[0]),
593
+ height: Math.abs(point0[1] - point1[1]),
594
+ };
595
+ };
596
+
597
+ _getImageVolumeFromTargetUID(targetUID, renderingEngine) {
598
+ let imageVolume, viewport;
599
+ if (targetUID.startsWith('stackTarget')) {
600
+ const coloneIndex = targetUID.indexOf(':');
601
+ const viewportUID = targetUID.substring(coloneIndex + 1);
602
+ const viewport = renderingEngine.getViewport(viewportUID);
603
+ imageVolume = viewport.getImageData();
604
+ } else {
605
+ imageVolume = cache.getVolume(targetUID);
606
+ }
607
+
608
+ return { imageVolume, viewport };
609
+ }
610
+
611
+ /**
612
+ * _calculateCachedStats - For each volume in the frame of reference that a
613
+ * tool instance in particular viewport defines as its target volume, find the
614
+ * volume coordinates (i,j,k) being probed by the two corners. One of i,j or k
615
+ * will be constant across the two points. In the other two directions iterate
616
+ * over the voxels and calculate the first and second-order statistics.
617
+ *
618
+ * @param {object} data - The toolData tool-specific data.
619
+ * @param {Array<number>} viewPlaneNormal The normal vector of the camera.
620
+ * @param {Array<number>} viewUp The viewUp vector of the camera.
621
+ */
622
+ _calculateCachedStats = (
623
+ annotation,
624
+ viewPlaneNormal,
625
+ viewUp,
626
+ renderingEngine,
627
+ enabledElement
628
+ ) => {
629
+ const { data } = annotation;
630
+ const { viewportUID, renderingEngineUID, sceneUID } = enabledElement;
631
+
632
+ const worldPos1 = data.handles.points[0];
633
+ const worldPos2 = data.handles.points[3];
634
+ const { cachedStats } = data;
635
+
636
+ const targetUIDs = Object.keys(cachedStats);
637
+
638
+ for (let i = 0; i < targetUIDs.length; i++) {
639
+ const targetUID = targetUIDs[i];
640
+
641
+ const { imageVolume } = this._getImageVolumeFromTargetUID(
642
+ targetUID,
643
+ renderingEngine
644
+ );
645
+
646
+ const {
647
+ dimensions,
648
+ scalarData,
649
+ vtkImageData: imageData,
650
+ metadata,
651
+ } = imageVolume;
652
+ const worldPos1Index = vec3.fromValues(0, 0, 0);
653
+ const worldPos2Index = vec3.fromValues(0, 0, 0);
654
+
655
+ imageData.worldToIndexVec3(worldPos1, worldPos1Index);
656
+
657
+ worldPos1Index[0] = Math.floor(worldPos1Index[0]);
658
+ worldPos1Index[1] = Math.floor(worldPos1Index[1]);
659
+ worldPos1Index[2] = Math.floor(worldPos1Index[2]);
660
+
661
+ imageData.worldToIndexVec3(worldPos2, worldPos2Index);
662
+
663
+ worldPos2Index[0] = Math.floor(worldPos2Index[0]);
664
+ worldPos2Index[1] = Math.floor(worldPos2Index[1]);
665
+ worldPos2Index[2] = Math.floor(worldPos2Index[2]);
666
+
667
+ // Check if one of the indexes are inside the volume, this then gives us
668
+ // Some area to do stats over.
669
+
670
+ if (this._isInsideVolume(worldPos1Index, worldPos2Index, dimensions)) {
671
+ this.isHandleOutsideImage = false;
672
+
673
+ // Calculate index bounds to iterate over
674
+
675
+ const iMin = Math.min(worldPos1Index[0], worldPos2Index[0]);
676
+ const iMax = Math.max(worldPos1Index[0], worldPos2Index[0]);
677
+
678
+ const jMin = Math.min(worldPos1Index[1], worldPos2Index[1]);
679
+ const jMax = Math.max(worldPos1Index[1], worldPos2Index[1]);
680
+
681
+ const kMin = Math.min(worldPos1Index[2], worldPos2Index[2]);
682
+ const kMax = Math.max(worldPos1Index[2], worldPos2Index[2]);
683
+
684
+ const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(
685
+ viewPlaneNormal,
686
+ viewUp,
687
+ worldPos1,
688
+ worldPos2
689
+ );
690
+
691
+ const area = worldWidth * worldHeight;
692
+
693
+ let count = 0;
694
+ let mean = 0;
695
+ let stdDev = 0;
696
+
697
+ const yMultiple = dimensions[0];
698
+ const zMultiple = dimensions[0] * dimensions[1];
699
+
700
+ // This is a triple loop, but one of these 3 values will be constant
701
+ // In the planar view.
702
+ for (let k = kMin; k <= kMax; k++) {
703
+ for (let j = jMin; j <= jMax; j++) {
704
+ for (let i = iMin; i <= iMax; i++) {
705
+ const value = scalarData[k * zMultiple + j * yMultiple + i];
706
+
707
+ count++;
708
+ mean += value;
709
+ }
710
+ }
711
+ }
712
+
713
+ mean /= count;
714
+
715
+ for (let k = kMin; k <= kMax; k++) {
716
+ for (let j = jMin; j <= jMax; j++) {
717
+ for (let i = iMin; i <= iMax; i++) {
718
+ const value = scalarData[k * zMultiple + j * yMultiple + i];
719
+
720
+ const valueMinusMean = value - mean;
721
+
722
+ stdDev += valueMinusMean * valueMinusMean;
723
+ }
724
+ }
725
+ }
726
+
727
+ stdDev /= count;
728
+ stdDev = Math.sqrt(stdDev);
729
+
730
+ cachedStats[targetUID] = {
731
+ Modality: metadata.Modality,
732
+ area,
733
+ mean,
734
+ stdDev,
735
+ };
736
+ } else {
737
+ this.isHandleOutsideImage = true;
738
+ cachedStats[targetUID] = {
739
+ Modality: metadata.Modality,
740
+ };
741
+ }
742
+ }
743
+
744
+ data.invalidated = false;
745
+
746
+ // Dispatching measurement modified
747
+ const eventType = Events.ANNOTATION_MODIFIED;
748
+
749
+ const eventDetail = {
750
+ annotation,
751
+ viewportUID,
752
+ renderingEngineUID,
753
+ sceneUID: sceneUID,
754
+ };
755
+ triggerEvent(eventTarget, eventType, eventDetail);
756
+
757
+ return cachedStats;
758
+ };
759
+
760
+ _isInsideVolume = (index1, index2, dimensions) => {
761
+ return (
762
+ csUtils.indexWithinDimensions(index1, dimensions) &&
763
+ csUtils.indexWithinDimensions(index2, dimensions)
764
+ );
765
+ };
766
+
767
+ _getTargetStackUID(viewport) {
768
+ return `stackTarget:${viewport.uid}`;
769
+ }
770
+
771
+ _getTargetVolumeUID = (scene) => {
772
+ if (this.configuration.volumeUID) {
773
+ return this.configuration.volumeUID;
774
+ }
775
+
776
+ const volumeActors = scene.getVolumeActors();
777
+
778
+ if (!volumeActors && !volumeActors.length) {
779
+ // No stack to scroll through
780
+ return;
781
+ }
782
+
783
+ return volumeActors[0].uid;
784
+ };
785
+ }
786
+
787
+ VideoRedactionTool.toolName = 'VideoRedaction';
788
+ export default VideoRedactionTool;