@cornerstonejs/tools 1.24.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.
- package/dist/cjs/drawingSvg/drawRedactionRect.d.ts +1 -0
- package/dist/cjs/drawingSvg/drawRedactionRect.js +44 -0
- package/dist/cjs/drawingSvg/drawRedactionRect.js.map +1 -0
- package/dist/cjs/drawingSvg/index.d.ts +2 -1
- package/dist/cjs/drawingSvg/index.js +3 -1
- package/dist/cjs/drawingSvg/index.js.map +1 -1
- package/dist/cjs/eventListeners/touch/touchStartListener.js +0 -8
- package/dist/cjs/eventListeners/touch/touchStartListener.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tools/ZoomTool.js +2 -5
- package/dist/cjs/tools/ZoomTool.js.map +1 -1
- package/dist/cjs/tools/annotation/VideoRedactionTool.d.ts +46 -0
- package/dist/cjs/tools/annotation/VideoRedactionTool.js +492 -0
- package/dist/cjs/tools/annotation/VideoRedactionTool.js.map +1 -0
- package/dist/cjs/tools/base/AnnotationDisplayTool.d.ts +1 -1
- package/dist/cjs/tools/base/AnnotationDisplayTool.js +2 -1
- package/dist/cjs/tools/base/AnnotationDisplayTool.js.map +1 -1
- package/dist/cjs/tools/base/AnnotationTool.js.map +1 -1
- package/dist/cjs/tools/base/BaseTool.js +3 -0
- package/dist/cjs/tools/base/BaseTool.js.map +1 -1
- package/dist/cjs/types/AnnotationTypes.d.ts +1 -1
- package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +20 -0
- package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js +6 -0
- package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
- package/dist/cjs/utilities/scroll.d.ts +1 -1
- package/dist/cjs/utilities/scroll.js +3 -0
- package/dist/cjs/utilities/scroll.js.map +1 -1
- package/dist/cjs/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.d.ts +1 -1
- package/dist/cjs/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.js.map +1 -1
- package/dist/cjs/utilities/viewportFilters/filterViewportsWithToolEnabled.d.ts +1 -1
- package/dist/cjs/utilities/viewportFilters/filterViewportsWithToolEnabled.js.map +1 -1
- package/dist/cjs/utilities/voi/colorbar/Colorbar.js +10 -8
- package/dist/cjs/utilities/voi/colorbar/Colorbar.js.map +1 -1
- package/dist/cjs/utilities/voi/colorbar/ColorbarCanvas.js +3 -0
- package/dist/cjs/utilities/voi/colorbar/ColorbarCanvas.js.map +1 -1
- package/dist/esm/drawingSvg/drawRedactionRect.d.ts +1 -0
- package/dist/esm/drawingSvg/drawRedactionRect.js +38 -0
- package/dist/esm/drawingSvg/drawRedactionRect.js.map +1 -0
- package/dist/esm/drawingSvg/index.d.ts +2 -1
- package/dist/esm/drawingSvg/index.js +2 -1
- package/dist/esm/drawingSvg/index.js.map +1 -1
- package/dist/esm/eventListeners/touch/touchStartListener.js +0 -8
- package/dist/esm/eventListeners/touch/touchStartListener.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tools/ZoomTool.js +2 -5
- package/dist/esm/tools/ZoomTool.js.map +1 -1
- package/dist/esm/tools/annotation/VideoRedactionTool.d.ts +46 -0
- package/dist/esm/tools/annotation/VideoRedactionTool.js +464 -0
- package/dist/esm/tools/annotation/VideoRedactionTool.js.map +1 -0
- package/dist/esm/tools/base/AnnotationDisplayTool.d.ts +1 -1
- package/dist/esm/tools/base/AnnotationDisplayTool.js +3 -2
- package/dist/esm/tools/base/AnnotationDisplayTool.js.map +1 -1
- package/dist/esm/tools/base/AnnotationTool.js.map +1 -1
- package/dist/esm/tools/base/BaseTool.js +4 -1
- package/dist/esm/tools/base/BaseTool.js.map +1 -1
- package/dist/esm/types/AnnotationTypes.d.ts +1 -1
- package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +20 -0
- package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js +7 -1
- package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
- package/dist/esm/utilities/scroll.d.ts +1 -1
- package/dist/esm/utilities/scroll.js +4 -1
- package/dist/esm/utilities/scroll.js.map +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.d.ts +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.js.map +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithToolEnabled.d.ts +1 -1
- package/dist/esm/utilities/viewportFilters/filterViewportsWithToolEnabled.js.map +1 -1
- package/dist/esm/utilities/voi/colorbar/Colorbar.js +10 -8
- package/dist/esm/utilities/voi/colorbar/Colorbar.js.map +1 -1
- package/dist/esm/utilities/voi/colorbar/ColorbarCanvas.js +3 -0
- package/dist/esm/utilities/voi/colorbar/ColorbarCanvas.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/drawingSvg/drawRedactionRect.ts +62 -0
- package/src/drawingSvg/index.ts +2 -0
- package/src/eventListeners/touch/touchStartListener.ts +3 -10
- package/src/index.ts +3 -0
- package/src/tools/ZoomTool.ts +2 -10
- package/src/tools/annotation/VideoRedactionTool.ts +788 -0
- package/src/tools/base/AnnotationDisplayTool.ts +6 -3
- package/src/tools/base/AnnotationTool.ts +1 -1
- package/src/tools/base/BaseTool.ts +3 -0
- package/src/types/AnnotationTypes.ts +1 -1
- package/src/types/ToolSpecificAnnotationTypes.ts +21 -0
- package/src/utilities/planar/filterAnnotationsForDisplay.ts +7 -0
- package/src/utilities/scroll.ts +4 -1
- package/src/utilities/viewportFilters/filterViewportsWithFrameOfReferenceUID.ts +1 -1
- package/src/utilities/viewportFilters/filterViewportsWithToolEnabled.ts +1 -1
- package/src/utilities/voi/colorbar/Colorbar.ts +10 -11
- package/src/utilities/voi/colorbar/ColorbarCanvas.ts +3 -0
|
@@ -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;
|