@cornerstonejs/tools 4.18.3 → 4.18.4
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/esm/tools/VolumeCroppingControlTool.d.ts +10 -35
- package/dist/esm/tools/VolumeCroppingControlTool.js +179 -699
- package/dist/esm/tools/VolumeCroppingTool.d.ts +32 -32
- package/dist/esm/tools/VolumeCroppingTool.js +775 -525
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +7 -0
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +34 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +6 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +7 -0
- package/dist/esm/utilities/draw3D/index.d.ts +2 -0
- package/dist/esm/utilities/draw3D/index.js +2 -0
- package/dist/esm/utilities/index.d.ts +2 -1
- package/dist/esm/utilities/index.js +2 -1
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +6 -0
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +37 -0
- package/dist/esm/utilities/volumeCropping/constants.d.ts +31 -0
- package/dist/esm/utilities/volumeCropping/constants.js +31 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +6 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +9 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +50 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +1 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +13 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +19 -0
- package/dist/esm/utilities/volumeCropping/index.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/index.js +9 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +8 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.js +11 -0
- package/dist/esm/utilities/volumeCropping/types.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/types.js +0 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
import { vec2
|
|
1
|
+
import { vec2 } from 'gl-matrix';
|
|
2
2
|
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
|
|
3
3
|
import { AnnotationTool } from './base';
|
|
4
|
-
import { getRenderingEngine, getEnabledElementByIds, getEnabledElement,
|
|
5
|
-
import { getToolGroup
|
|
4
|
+
import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, CONSTANTS, triggerEvent, eventTarget, convertColorArrayToRgbString, } from '@cornerstonejs/core';
|
|
5
|
+
import { getToolGroup } from '../store/ToolGroupManager';
|
|
6
6
|
import { addAnnotation, getAnnotations, removeAnnotation, } from '../stateManagement/annotation/annotationState';
|
|
7
7
|
import { drawCircle as drawCircleSvg, drawLine as drawLineSvg, } from '../drawingSvg';
|
|
8
8
|
import { state } from '../store/state';
|
|
9
9
|
import { Events } from '../enums';
|
|
10
10
|
import { getViewportIdsWithToolToRender } from '../utilities/viewportFilters';
|
|
11
11
|
import { resetElementCursor, hideElementCursor, } from '../cursors/elementCursor';
|
|
12
|
-
import liangBarksyClip from '../utilities/math/vec2/liangBarksyClip';
|
|
13
12
|
import * as lineSegment from '../utilities/math/line';
|
|
14
13
|
import { isAnnotationLocked } from '../stateManagement/annotation/annotationLocking';
|
|
15
14
|
import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
|
|
16
|
-
|
|
17
|
-
function defaultReferenceLineColor() {
|
|
18
|
-
return 'rgb(0, 200, 0)';
|
|
19
|
-
}
|
|
20
|
-
function defaultReferenceLineControllable() {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
15
|
+
import { NUM_CLIPPING_PLANES, LINE_INTERSECTION_TOLERANCE, POINT_PROXIMITY_THRESHOLD_PIXELS, copyClippingPlanes, getColorKeyForPlaneIndex, getOrientationFromNormal, computePlanePlaneIntersection, findLineBoundsIntersection, } from '../utilities/volumeCropping';
|
|
23
16
|
const OPERATION = {
|
|
24
17
|
DRAG: 1,
|
|
25
18
|
ROTATE: 2,
|
|
@@ -52,12 +45,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
52
45
|
},
|
|
53
46
|
}) {
|
|
54
47
|
super(toolProps, defaultToolProps);
|
|
55
|
-
this.
|
|
56
|
-
this.sphereStates = [];
|
|
57
|
-
this.draggingSphereIndex = null;
|
|
58
|
-
this.toolCenter = [0, 0, 0];
|
|
59
|
-
this.toolCenterMin = [0, 0, 0];
|
|
60
|
-
this.toolCenterMax = [0, 0, 0];
|
|
48
|
+
this.clippingPlanes = [];
|
|
61
49
|
this.initializeViewport = ({ renderingEngineId, viewportId, }) => {
|
|
62
50
|
if (!renderingEngineId || !viewportId) {
|
|
63
51
|
console.warn('VolumeCroppingControlTool: Missing renderingEngineId or viewportId');
|
|
@@ -68,15 +56,21 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
68
56
|
return;
|
|
69
57
|
}
|
|
70
58
|
const { viewport } = enabledElement;
|
|
71
|
-
|
|
59
|
+
const volumeActors = viewport.getActors();
|
|
60
|
+
if (volumeActors && volumeActors.length > 0) {
|
|
61
|
+
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
62
|
+
if (imageData) {
|
|
63
|
+
this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
72
66
|
const { element } = viewport;
|
|
73
|
-
const { position, focalPoint
|
|
67
|
+
const { position, focalPoint } = viewport.getCamera();
|
|
74
68
|
let annotations = this._getAnnotations(enabledElement);
|
|
75
69
|
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
76
70
|
if (annotations?.length) {
|
|
77
71
|
removeAnnotation(annotations[0].annotationUID);
|
|
78
72
|
}
|
|
79
|
-
const orientation =
|
|
73
|
+
const orientation = getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
|
|
80
74
|
const annotation = {
|
|
81
75
|
highlighted: false,
|
|
82
76
|
metadata: {
|
|
@@ -86,11 +80,11 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
86
80
|
},
|
|
87
81
|
data: {
|
|
88
82
|
handles: {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
activeOperation: null,
|
|
84
|
+
clippingPlanes: this.clippingPlanes.length > 0
|
|
85
|
+
? copyClippingPlanes(this.clippingPlanes)
|
|
86
|
+
: [],
|
|
92
87
|
},
|
|
93
|
-
activeOperation: null,
|
|
94
88
|
activeViewportIds: [],
|
|
95
89
|
viewportId,
|
|
96
90
|
referenceLines: [],
|
|
@@ -98,10 +92,6 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
98
92
|
},
|
|
99
93
|
};
|
|
100
94
|
addAnnotation(annotation, element);
|
|
101
|
-
return {
|
|
102
|
-
normal: viewPlaneNormal,
|
|
103
|
-
point: viewport.canvasToWorld([100, 100]),
|
|
104
|
-
};
|
|
105
95
|
};
|
|
106
96
|
this._getViewportsInfo = () => {
|
|
107
97
|
const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
|
|
@@ -134,139 +124,22 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
134
124
|
}
|
|
135
125
|
viewport.render();
|
|
136
126
|
}
|
|
137
|
-
this.
|
|
138
|
-
};
|
|
139
|
-
this.computeToolCenter = () => {
|
|
140
|
-
const viewportsInfo = this._getViewportsInfo();
|
|
127
|
+
this._initializeViewports(viewportsInfo);
|
|
141
128
|
};
|
|
142
|
-
this.
|
|
143
|
-
if (!viewportsInfo || !viewportsInfo[0]) {
|
|
144
|
-
console.warn('
|
|
129
|
+
this._initializeViewports = (viewportsInfo) => {
|
|
130
|
+
if (!viewportsInfo?.length || !viewportsInfo[0]) {
|
|
131
|
+
console.warn('VolumeCroppingControlTool: No valid viewportsInfo for initialization.');
|
|
145
132
|
return;
|
|
146
133
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.map((vp) => {
|
|
150
|
-
if (vp.renderingEngineId) {
|
|
151
|
-
const renderingEngine = getRenderingEngine(vp.renderingEngineId);
|
|
152
|
-
const viewport = renderingEngine.getViewport(vp.viewportId);
|
|
153
|
-
if (viewport && viewport.getCamera) {
|
|
154
|
-
const orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
|
|
155
|
-
if (orientation) {
|
|
156
|
-
return orientation;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
})
|
|
162
|
-
.filter(Boolean);
|
|
163
|
-
const missingOrientation = orientationIds.find((id) => !presentOrientations.includes(id));
|
|
164
|
-
const presentNormals = [];
|
|
165
|
-
const presentCenters = [];
|
|
166
|
-
const presentViewportInfos = viewportsInfo.filter((vp) => {
|
|
167
|
-
let orientation = null;
|
|
168
|
-
if (vp.renderingEngineId) {
|
|
169
|
-
const renderingEngine = getRenderingEngine(vp.renderingEngineId);
|
|
170
|
-
const viewport = renderingEngine.getViewport(vp.viewportId);
|
|
171
|
-
if (viewport && viewport.getCamera) {
|
|
172
|
-
orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return orientation && orientationIds.includes(orientation);
|
|
134
|
+
viewportsInfo.forEach((vpInfo) => {
|
|
135
|
+
this.initializeViewport(vpInfo);
|
|
176
136
|
});
|
|
177
|
-
|
|
178
|
-
const { normal, point } = this.initializeViewport(vpInfo);
|
|
179
|
-
presentNormals.push(normal);
|
|
180
|
-
presentCenters.push(point);
|
|
181
|
-
});
|
|
182
|
-
if (presentViewportInfos.length === 2 && missingOrientation) {
|
|
183
|
-
const virtualNormal = [0, 0, 0];
|
|
184
|
-
vec3.cross(virtualNormal, presentNormals[0], presentNormals[1]);
|
|
185
|
-
vec3.normalize(virtualNormal, virtualNormal);
|
|
186
|
-
const virtualCenter = [
|
|
187
|
-
(presentCenters[0][0] + presentCenters[1][0]) / 2,
|
|
188
|
-
(presentCenters[0][1] + presentCenters[1][1]) / 2,
|
|
189
|
-
(presentCenters[0][2] + presentCenters[1][2]) / 2,
|
|
190
|
-
];
|
|
191
|
-
const orientation = null;
|
|
192
|
-
const virtualAnnotation = {
|
|
193
|
-
highlighted: false,
|
|
194
|
-
metadata: {
|
|
195
|
-
cameraPosition: [...virtualCenter],
|
|
196
|
-
cameraFocalPoint: [...virtualCenter],
|
|
197
|
-
toolName: this.getToolName(),
|
|
198
|
-
},
|
|
199
|
-
data: {
|
|
200
|
-
handles: {
|
|
201
|
-
activeOperation: null,
|
|
202
|
-
toolCenter: this.toolCenter,
|
|
203
|
-
toolCenterMin: this.toolCenterMin,
|
|
204
|
-
toolCenterMax: this.toolCenterMax,
|
|
205
|
-
},
|
|
206
|
-
activeViewportIds: [],
|
|
207
|
-
viewportId: missingOrientation,
|
|
208
|
-
referenceLines: [],
|
|
209
|
-
orientation,
|
|
210
|
-
},
|
|
211
|
-
isVirtual: true,
|
|
212
|
-
virtualNormal,
|
|
213
|
-
};
|
|
214
|
-
this._virtualAnnotations = [virtualAnnotation];
|
|
215
|
-
}
|
|
216
|
-
else if (presentViewportInfos.length === 1) {
|
|
217
|
-
let presentOrientation = null;
|
|
218
|
-
const vpInfo = presentViewportInfos[0];
|
|
219
|
-
if (vpInfo.renderingEngineId) {
|
|
220
|
-
const renderingEngine = getRenderingEngine(vpInfo.renderingEngineId);
|
|
221
|
-
const viewport = renderingEngine.getViewport(vpInfo.viewportId);
|
|
222
|
-
if (viewport && viewport.getCamera) {
|
|
223
|
-
presentOrientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const presentCenter = presentCenters[0];
|
|
227
|
-
const canonicalNormals = {
|
|
228
|
-
AXIAL: [0, 0, 1],
|
|
229
|
-
CORONAL: [0, 1, 0],
|
|
230
|
-
SAGITTAL: [1, 0, 0],
|
|
231
|
-
};
|
|
232
|
-
const missingIds = orientationIds.filter((id) => id !== presentOrientation);
|
|
233
|
-
const virtualAnnotations = missingIds.map((orientation) => {
|
|
234
|
-
const normal = canonicalNormals[orientation];
|
|
235
|
-
const virtualAnnotation = {
|
|
236
|
-
highlighted: false,
|
|
237
|
-
metadata: {
|
|
238
|
-
cameraPosition: [...presentCenter],
|
|
239
|
-
cameraFocalPoint: [...presentCenter],
|
|
240
|
-
toolName: this.getToolName(),
|
|
241
|
-
},
|
|
242
|
-
data: {
|
|
243
|
-
handles: {
|
|
244
|
-
activeOperation: null,
|
|
245
|
-
toolCenter: this.toolCenter,
|
|
246
|
-
toolCenterMin: this.toolCenterMin,
|
|
247
|
-
toolCenterMax: this.toolCenterMax,
|
|
248
|
-
},
|
|
249
|
-
activeViewportIds: [],
|
|
250
|
-
viewportId: orientation,
|
|
251
|
-
referenceLines: [],
|
|
252
|
-
orientation,
|
|
253
|
-
},
|
|
254
|
-
isVirtual: true,
|
|
255
|
-
virtualNormal: normal,
|
|
256
|
-
};
|
|
257
|
-
return virtualAnnotation;
|
|
258
|
-
});
|
|
259
|
-
this._virtualAnnotations = virtualAnnotations;
|
|
260
|
-
}
|
|
261
|
-
if (viewportsInfo && viewportsInfo.length) {
|
|
262
|
-
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
263
|
-
}
|
|
137
|
+
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
264
138
|
};
|
|
265
139
|
this.cancel = () => {
|
|
266
|
-
console.log('Not implemented yet');
|
|
267
140
|
};
|
|
268
141
|
this.isPointNearTool = (element, annotation, canvasCoords, proximity) => {
|
|
269
|
-
if (this._pointNearTool(element, annotation, canvasCoords,
|
|
142
|
+
if (this._pointNearTool(element, annotation, canvasCoords, POINT_PROXIMITY_THRESHOLD_PIXELS)) {
|
|
270
143
|
return true;
|
|
271
144
|
}
|
|
272
145
|
return false;
|
|
@@ -298,13 +171,8 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
298
171
|
if (!data.handles) {
|
|
299
172
|
continue;
|
|
300
173
|
}
|
|
301
|
-
const previousActiveOperation = data.handles.activeOperation;
|
|
302
|
-
const previousActiveViewportIds = data.activeViewportIds && data.activeViewportIds.length > 0
|
|
303
|
-
? [...data.activeViewportIds]
|
|
304
|
-
: [];
|
|
305
174
|
data.activeViewportIds = [];
|
|
306
|
-
|
|
307
|
-
near = this._pointNearTool(element, annotation, canvasCoords, 6);
|
|
175
|
+
const near = this._pointNearTool(element, annotation, canvasCoords, POINT_PROXIMITY_THRESHOLD_PIXELS);
|
|
308
176
|
const nearToolAndNotMarkedActive = near && !highlighted;
|
|
309
177
|
const notNearToolAndMarkedActive = !near && highlighted;
|
|
310
178
|
if (nearToolAndNotMarkedActive || notNearToolAndMarkedActive) {
|
|
@@ -321,12 +189,9 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
321
189
|
const enabledElement = getEnabledElement(element);
|
|
322
190
|
let orientation = null;
|
|
323
191
|
if (enabledElement.viewport && enabledElement.viewport.getCamera) {
|
|
324
|
-
orientation =
|
|
192
|
+
orientation = getOrientationFromNormal(enabledElement.viewport.getCamera().viewPlaneNormal);
|
|
325
193
|
}
|
|
326
194
|
const filtered = annotations.filter((annotation) => {
|
|
327
|
-
if (annotation.isVirtual) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
195
|
if (annotation.data.orientation &&
|
|
331
196
|
orientation &&
|
|
332
197
|
annotation.data.orientation === orientation) {
|
|
@@ -343,7 +208,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
343
208
|
const s2_x = q2[0] - q1[0];
|
|
344
209
|
const s2_y = q2[1] - q1[1];
|
|
345
210
|
const denom = -s2_x * s1_y + s1_x * s2_y;
|
|
346
|
-
if (Math.abs(denom) <
|
|
211
|
+
if (Math.abs(denom) < LINE_INTERSECTION_TOLERANCE) {
|
|
347
212
|
return null;
|
|
348
213
|
}
|
|
349
214
|
const s = (-s1_y * (p1[0] - q1[0]) + s1_x * (p1[1] - q1[1])) / denom;
|
|
@@ -360,10 +225,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
360
225
|
let renderStatus = false;
|
|
361
226
|
const { viewport, renderingEngine } = enabledElement;
|
|
362
227
|
const { element } = viewport;
|
|
363
|
-
|
|
364
|
-
if (this._virtualAnnotations && this._virtualAnnotations.length) {
|
|
365
|
-
annotations = annotations.concat(this._virtualAnnotations);
|
|
366
|
-
}
|
|
228
|
+
const annotations = this._getAnnotations(enabledElement);
|
|
367
229
|
const camera = viewport.getCamera();
|
|
368
230
|
const filteredToolAnnotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
369
231
|
const viewportAnnotation = filteredToolAnnotations[0];
|
|
@@ -374,135 +236,65 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
374
236
|
const { clientWidth, clientHeight } = viewport.canvas;
|
|
375
237
|
const canvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
|
|
376
238
|
const data = viewportAnnotation.data;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const data = annotation.data;
|
|
384
|
-
const isVirtual = 'isVirtual' in annotation &&
|
|
385
|
-
annotation.isVirtual === true;
|
|
386
|
-
data.handles.toolCenter = this.toolCenter;
|
|
387
|
-
let otherViewport, otherCamera, clientWidth, clientHeight, otherCanvasDiagonalLength, otherCanvasCenter, otherViewportCenterWorld;
|
|
388
|
-
if (isVirtual) {
|
|
389
|
-
const realViewports = viewportsInfo.filter((vp) => vp.viewportId !== data.viewportId);
|
|
390
|
-
if (realViewports.length === 2) {
|
|
391
|
-
const vp1 = renderingEngine.getViewport(realViewports[0].viewportId);
|
|
392
|
-
const vp2 = renderingEngine.getViewport(realViewports[1].viewportId);
|
|
393
|
-
const normal1 = vp1.getCamera().viewPlaneNormal;
|
|
394
|
-
const normal2 = vp2.getCamera().viewPlaneNormal;
|
|
395
|
-
const virtualNormal = vec3.create();
|
|
396
|
-
vec3.cross(virtualNormal, normal1, normal2);
|
|
397
|
-
vec3.normalize(virtualNormal, virtualNormal);
|
|
398
|
-
otherCamera = {
|
|
399
|
-
viewPlaneNormal: virtualNormal,
|
|
400
|
-
position: data.handles.toolCenter,
|
|
401
|
-
focalPoint: data.handles.toolCenter,
|
|
402
|
-
viewUp: [0, 1, 0],
|
|
403
|
-
};
|
|
404
|
-
clientWidth = viewport.canvas.clientWidth;
|
|
405
|
-
clientHeight = viewport.canvas.clientHeight;
|
|
406
|
-
otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
|
|
407
|
-
otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
|
|
408
|
-
otherViewportCenterWorld = data.handles.toolCenter;
|
|
409
|
-
otherViewport = {
|
|
410
|
-
id: data.viewportId,
|
|
411
|
-
canvas: viewport.canvas,
|
|
412
|
-
canvasToWorld: () => data.handles.toolCenter,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
const virtualNormal = annotation
|
|
417
|
-
.virtualNormal ?? [0, 0, 1];
|
|
418
|
-
otherCamera = {
|
|
419
|
-
viewPlaneNormal: virtualNormal,
|
|
420
|
-
position: data.handles.toolCenter,
|
|
421
|
-
focalPoint: data.handles.toolCenter,
|
|
422
|
-
viewUp: [0, 1, 0],
|
|
423
|
-
};
|
|
424
|
-
clientWidth = viewport.canvas.clientWidth;
|
|
425
|
-
clientHeight = viewport.canvas.clientHeight;
|
|
426
|
-
otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
|
|
427
|
-
otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
|
|
428
|
-
otherViewportCenterWorld = data.handles.toolCenter;
|
|
429
|
-
otherViewport = {
|
|
430
|
-
id: data.viewportId,
|
|
431
|
-
canvas: viewport.canvas,
|
|
432
|
-
canvasToWorld: () => data.handles.toolCenter,
|
|
433
|
-
};
|
|
434
|
-
}
|
|
239
|
+
let clippingPlanes = viewportAnnotation.data.handles.clippingPlanes;
|
|
240
|
+
if (!clippingPlanes || clippingPlanes.length < NUM_CLIPPING_PLANES) {
|
|
241
|
+
if (this.clippingPlanes &&
|
|
242
|
+
this.clippingPlanes.length >= NUM_CLIPPING_PLANES) {
|
|
243
|
+
clippingPlanes = this.clippingPlanes;
|
|
244
|
+
data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
|
|
435
245
|
}
|
|
436
246
|
else {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const { viewPlaneNormal, focalPoint } = camera;
|
|
251
|
+
const referenceLines = [];
|
|
252
|
+
const planeTypes = [
|
|
253
|
+
'min',
|
|
254
|
+
'max',
|
|
255
|
+
'min',
|
|
256
|
+
'max',
|
|
257
|
+
'min',
|
|
258
|
+
'max',
|
|
259
|
+
];
|
|
260
|
+
for (let planeIndex = 0; planeIndex < NUM_CLIPPING_PLANES; planeIndex++) {
|
|
261
|
+
const clippingPlane = clippingPlanes[planeIndex];
|
|
262
|
+
const intersection = computePlanePlaneIntersection(clippingPlane, viewPlaneNormal, focalPoint);
|
|
263
|
+
if (!intersection) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const lineBounds = findLineBoundsIntersection(intersection.point, intersection.direction, viewport);
|
|
267
|
+
if (!lineBounds) {
|
|
268
|
+
continue;
|
|
445
269
|
}
|
|
446
|
-
const otherViewportControllable = this._getReferenceLineControllable(otherViewport.id);
|
|
447
|
-
const direction = [0, 0, 0];
|
|
448
|
-
vtkMath.cross(camera.viewPlaneNormal, otherCamera.viewPlaneNormal, direction);
|
|
449
|
-
vtkMath.normalize(direction);
|
|
450
|
-
vtkMath.multiplyScalar(direction, otherCanvasDiagonalLength);
|
|
451
|
-
const pointWorld0 = [0, 0, 0];
|
|
452
|
-
vtkMath.add(otherViewportCenterWorld, direction, pointWorld0);
|
|
453
|
-
const pointWorld1 = [0, 0, 0];
|
|
454
|
-
vtkMath.subtract(otherViewportCenterWorld, direction, pointWorld1);
|
|
455
|
-
const pointCanvas0 = viewport.worldToCanvas(pointWorld0);
|
|
456
|
-
const otherViewportCenterCanvas = viewport.worldToCanvas([
|
|
457
|
-
otherViewportCenterWorld[0] ?? 0,
|
|
458
|
-
otherViewportCenterWorld[1] ?? 0,
|
|
459
|
-
otherViewportCenterWorld[2] ?? 0,
|
|
460
|
-
]);
|
|
461
|
-
const canvasUnitVectorFromCenter = vec2.create();
|
|
462
|
-
vec2.subtract(canvasUnitVectorFromCenter, pointCanvas0, otherViewportCenterCanvas);
|
|
463
|
-
vec2.normalize(canvasUnitVectorFromCenter, canvasUnitVectorFromCenter);
|
|
464
|
-
const canvasVectorFromCenterLong = vec2.create();
|
|
465
|
-
vec2.scale(canvasVectorFromCenterLong, canvasUnitVectorFromCenter, canvasDiagonalLength * 100);
|
|
466
|
-
const refLinesCenterMin = otherViewportControllable
|
|
467
|
-
? vec2.clone(volumeCroppingCenterCanvasMin)
|
|
468
|
-
: vec2.clone(otherViewportCenterCanvas);
|
|
469
|
-
const refLinePointMinOne = vec2.create();
|
|
470
|
-
const refLinePointMinTwo = vec2.create();
|
|
471
|
-
vec2.add(refLinePointMinOne, refLinesCenterMin, canvasVectorFromCenterLong);
|
|
472
|
-
vec2.subtract(refLinePointMinTwo, refLinesCenterMin, canvasVectorFromCenterLong);
|
|
473
|
-
liangBarksyClip(refLinePointMinOne, refLinePointMinTwo, canvasBox);
|
|
474
|
-
referenceLines.push([
|
|
475
|
-
otherViewport,
|
|
476
|
-
refLinePointMinOne,
|
|
477
|
-
refLinePointMinTwo,
|
|
478
|
-
'min',
|
|
479
|
-
]);
|
|
480
|
-
const refLinesCenterMax = otherViewportControllable
|
|
481
|
-
? vec2.clone(volumeCroppingCenterCanvasMax)
|
|
482
|
-
: vec2.clone(otherViewportCenterCanvas);
|
|
483
|
-
const refLinePointMaxOne = vec2.create();
|
|
484
|
-
const refLinePointMaxTwo = vec2.create();
|
|
485
|
-
vec2.add(refLinePointMaxOne, refLinesCenterMax, canvasVectorFromCenterLong);
|
|
486
|
-
vec2.subtract(refLinePointMaxTwo, refLinesCenterMax, canvasVectorFromCenterLong);
|
|
487
|
-
liangBarksyClip(refLinePointMaxOne, refLinePointMaxTwo, canvasBox);
|
|
488
270
|
referenceLines.push([
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
271
|
+
{
|
|
272
|
+
id: viewport.id,
|
|
273
|
+
canvas: viewport.canvas,
|
|
274
|
+
},
|
|
275
|
+
lineBounds.start,
|
|
276
|
+
lineBounds.end,
|
|
277
|
+
planeTypes[planeIndex],
|
|
278
|
+
planeIndex,
|
|
493
279
|
]);
|
|
494
|
-
}
|
|
280
|
+
}
|
|
495
281
|
data.referenceLines = referenceLines;
|
|
496
282
|
const viewportColor = this._getReferenceLineColor(viewport.id);
|
|
497
|
-
const
|
|
283
|
+
const defaultColor = viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
|
|
498
284
|
referenceLines.forEach((line, lineIndex) => {
|
|
285
|
+
const [otherViewport, startPoint, endPoint, type, planeIndex] = line;
|
|
286
|
+
if (planeIndex === undefined ||
|
|
287
|
+
planeIndex < 0 ||
|
|
288
|
+
planeIndex >= NUM_CLIPPING_PLANES) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
499
291
|
const intersections = [];
|
|
500
292
|
for (let j = 0; j < referenceLines.length; ++j) {
|
|
501
293
|
if (j === lineIndex) {
|
|
502
294
|
continue;
|
|
503
295
|
}
|
|
504
296
|
const otherLine = referenceLines[j];
|
|
505
|
-
const intersection = lineIntersection2D(
|
|
297
|
+
const intersection = lineIntersection2D(startPoint, endPoint, otherLine[1], otherLine[2]);
|
|
506
298
|
if (intersection) {
|
|
507
299
|
intersections.push({
|
|
508
300
|
with: otherLine[3],
|
|
@@ -510,32 +302,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
510
302
|
});
|
|
511
303
|
}
|
|
512
304
|
}
|
|
513
|
-
const
|
|
514
|
-
let orientation = null;
|
|
515
|
-
if (otherViewport && otherViewport.id) {
|
|
516
|
-
const annotationForViewport = annotations.find((a) => a.data.viewportId === otherViewport.id);
|
|
517
|
-
if (annotationForViewport && annotationForViewport.data.orientation) {
|
|
518
|
-
orientation = String(annotationForViewport.data.orientation).toUpperCase();
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
const idUpper = otherViewport.id.toUpperCase();
|
|
522
|
-
if (idUpper.includes('AXIAL')) {
|
|
523
|
-
orientation = 'AXIAL';
|
|
524
|
-
}
|
|
525
|
-
else if (idUpper.includes('CORONAL')) {
|
|
526
|
-
orientation = 'CORONAL';
|
|
527
|
-
}
|
|
528
|
-
else if (idUpper.includes('SAGITTAL')) {
|
|
529
|
-
orientation = 'SAGITTAL';
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
305
|
+
const colorKey = getColorKeyForPlaneIndex(planeIndex);
|
|
533
306
|
const lineColors = this.configuration.lineColors || {};
|
|
534
|
-
const colorArr =
|
|
535
|
-
lineColors.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
: colorArr;
|
|
307
|
+
const colorArr = colorKey
|
|
308
|
+
? lineColors[colorKey] || lineColors.UNKNOWN || [1.0, 0.0, 0.0]
|
|
309
|
+
: [1.0, 0.0, 0.0];
|
|
310
|
+
const color = convertColorArrayToRgbString(colorArr);
|
|
539
311
|
const viewportControllable = this._getReferenceLineControllable(otherViewport.id);
|
|
540
312
|
const selectedViewportId = data.activeViewportIds.find((id) => id === otherViewport.id);
|
|
541
313
|
let lineWidth = this.configuration.lineWidth ?? 1.5;
|
|
@@ -545,28 +317,29 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
545
317
|
if (lineActive) {
|
|
546
318
|
lineWidth = this.configuration.activeLineWidth ?? 2.5;
|
|
547
319
|
}
|
|
548
|
-
const lineUID =
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
320
|
+
const lineUID = `plane_${planeIndex}`;
|
|
321
|
+
if (intersections.length === 2) {
|
|
322
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID, intersections[0].point, intersections[1].point, {
|
|
323
|
+
color,
|
|
324
|
+
lineWidth,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID, startPoint, endPoint, {
|
|
329
|
+
color,
|
|
330
|
+
lineWidth,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (this.configuration.extendReferenceLines &&
|
|
334
|
+
intersections.length === 2) {
|
|
335
|
+
const sortedIntersections = intersections
|
|
336
|
+
.map((intersection) => ({
|
|
337
|
+
...intersection,
|
|
338
|
+
distance: vec2.distance(startPoint, intersection.point),
|
|
339
|
+
}))
|
|
340
|
+
.sort((a, b) => a.distance - b.distance);
|
|
341
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_before', startPoint, sortedIntersections[0].point, { color, lineWidth, lineDash: [4, 4] });
|
|
342
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_after', sortedIntersections[1].point, endPoint, { color, lineWidth, lineDash: [4, 4] });
|
|
570
343
|
}
|
|
571
344
|
});
|
|
572
345
|
renderStatus = true;
|
|
@@ -580,7 +353,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
580
353
|
];
|
|
581
354
|
const circleRadius = viewportIndicatorsConfig?.circleRadius || canvasDiagonalLength * 0.01;
|
|
582
355
|
const circleUID = '0';
|
|
583
|
-
drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color, fill:
|
|
356
|
+
drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color: defaultColor, fill: defaultColor });
|
|
584
357
|
}
|
|
585
358
|
return renderStatus;
|
|
586
359
|
};
|
|
@@ -602,40 +375,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
602
375
|
if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
|
|
603
376
|
return;
|
|
604
377
|
}
|
|
605
|
-
|
|
606
|
-
const newMin = [...this.toolCenterMin];
|
|
607
|
-
const newMax = [...this.toolCenterMax];
|
|
608
|
-
if (draggingSphereIndex >= 0 && draggingSphereIndex <= 5) {
|
|
609
|
-
const axis = Math.floor(draggingSphereIndex / 2);
|
|
610
|
-
const isMin = draggingSphereIndex % 2 === 0;
|
|
611
|
-
(isMin ? newMin : newMax)[axis] = toolCenter[axis];
|
|
612
|
-
this.setToolCenter(newMin, 'min');
|
|
613
|
-
this.setToolCenter(newMax, 'max');
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
if (draggingSphereIndex >= 6 && draggingSphereIndex <= 13) {
|
|
617
|
-
const idx = draggingSphereIndex;
|
|
618
|
-
if (idx < 10) {
|
|
619
|
-
newMin[0] = toolCenter[0];
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
newMax[0] = toolCenter[0];
|
|
623
|
-
}
|
|
624
|
-
if ([6, 7, 10, 11].includes(idx)) {
|
|
625
|
-
newMin[1] = toolCenter[1];
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
newMax[1] = toolCenter[1];
|
|
629
|
-
}
|
|
630
|
-
if (idx % 2 === 0) {
|
|
631
|
-
newMin[2] = toolCenter[2];
|
|
632
|
-
}
|
|
633
|
-
else {
|
|
634
|
-
newMax[2] = toolCenter[2];
|
|
635
|
-
}
|
|
636
|
-
this.setToolCenter(newMin, 'min');
|
|
637
|
-
this.setToolCenter(newMax, 'max');
|
|
638
|
-
}
|
|
378
|
+
return;
|
|
639
379
|
}
|
|
640
380
|
};
|
|
641
381
|
this._onNewVolume = () => {
|
|
@@ -643,72 +383,28 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
643
383
|
if (viewportsInfo && viewportsInfo.length > 0) {
|
|
644
384
|
const { viewportId, renderingEngineId } = viewportsInfo[0];
|
|
645
385
|
const renderingEngine = getRenderingEngine(renderingEngineId);
|
|
386
|
+
if (!renderingEngine) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
646
389
|
const viewport = renderingEngine.getViewport(viewportId);
|
|
390
|
+
if (!viewport) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
647
393
|
const volumeActors = viewport.getActors();
|
|
648
394
|
if (volumeActors.length > 0) {
|
|
649
395
|
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
650
396
|
if (imageData) {
|
|
651
397
|
this.seriesInstanceUID = imageData.seriesInstanceUID;
|
|
652
|
-
this._updateToolCentersFromViewport(viewport);
|
|
653
|
-
const annotations = getAnnotations(this.getToolName(), viewportId) || [];
|
|
654
|
-
annotations.forEach((annotation) => {
|
|
655
|
-
if (annotation.data && annotation.data.handles) {
|
|
656
|
-
annotation.data.handles.toolCenter = [...this.toolCenter];
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
398
|
}
|
|
660
399
|
}
|
|
661
400
|
}
|
|
662
|
-
this.
|
|
401
|
+
this._initializeViewports(viewportsInfo);
|
|
663
402
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
664
403
|
toolGroupId: this.toolGroupId,
|
|
665
404
|
viewportsInfo: viewportsInfo,
|
|
666
405
|
seriesInstanceUID: this.seriesInstanceUID,
|
|
667
406
|
});
|
|
668
407
|
};
|
|
669
|
-
this._getAnnotationsForViewportsWithDifferentCameras = (enabledElement, annotations) => {
|
|
670
|
-
const { viewportId, renderingEngine, viewport } = enabledElement;
|
|
671
|
-
const otherViewportAnnotations = annotations.filter((annotation) => annotation.data.viewportId !== viewportId);
|
|
672
|
-
if (!otherViewportAnnotations || !otherViewportAnnotations.length) {
|
|
673
|
-
return [];
|
|
674
|
-
}
|
|
675
|
-
const camera = viewport.getCamera();
|
|
676
|
-
const { viewPlaneNormal, position } = camera;
|
|
677
|
-
const viewportsWithDifferentCameras = otherViewportAnnotations.filter((annotation) => {
|
|
678
|
-
const { viewportId } = annotation.data;
|
|
679
|
-
const targetViewport = renderingEngine.getViewport(viewportId);
|
|
680
|
-
const cameraOfTarget = targetViewport.getCamera();
|
|
681
|
-
return !(csUtils.isEqual(cameraOfTarget.viewPlaneNormal, viewPlaneNormal, 1e-2) && csUtils.isEqual(cameraOfTarget.position, position, 1));
|
|
682
|
-
});
|
|
683
|
-
return viewportsWithDifferentCameras;
|
|
684
|
-
};
|
|
685
|
-
this._filterViewportWithSameOrientation = (enabledElement, referenceAnnotation, annotations) => {
|
|
686
|
-
const { renderingEngine } = enabledElement;
|
|
687
|
-
const { data } = referenceAnnotation;
|
|
688
|
-
const viewport = renderingEngine.getViewport(data.viewportId);
|
|
689
|
-
const linkedViewportAnnotations = annotations.filter((annotation) => {
|
|
690
|
-
const { data } = annotation;
|
|
691
|
-
const otherViewport = renderingEngine.getViewport(data.viewportId);
|
|
692
|
-
const otherViewportControllable = this._getReferenceLineControllable(otherViewport.id);
|
|
693
|
-
return otherViewportControllable === true;
|
|
694
|
-
});
|
|
695
|
-
if (!linkedViewportAnnotations || !linkedViewportAnnotations.length) {
|
|
696
|
-
return [];
|
|
697
|
-
}
|
|
698
|
-
const camera = viewport.getCamera();
|
|
699
|
-
const viewPlaneNormal = camera.viewPlaneNormal;
|
|
700
|
-
vtkMath.normalize(viewPlaneNormal);
|
|
701
|
-
const otherViewportsAnnotationsWithSameCameraDirection = linkedViewportAnnotations.filter((annotation) => {
|
|
702
|
-
const { viewportId } = annotation.data;
|
|
703
|
-
const otherViewport = renderingEngine.getViewport(viewportId);
|
|
704
|
-
const otherCamera = otherViewport.getCamera();
|
|
705
|
-
const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
|
|
706
|
-
vtkMath.normalize(otherViewPlaneNormal);
|
|
707
|
-
return (csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) &&
|
|
708
|
-
csUtils.isEqual(camera.viewUp, otherCamera.viewUp, 1e-2));
|
|
709
|
-
});
|
|
710
|
-
return otherViewportsAnnotationsWithSameCameraDirection;
|
|
711
|
-
};
|
|
712
408
|
this._activateModify = (element) => {
|
|
713
409
|
state.isInteractingWithTool = !this.configuration.mobile?.enabled;
|
|
714
410
|
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
@@ -760,48 +456,71 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
760
456
|
return;
|
|
761
457
|
}
|
|
762
458
|
const { handles } = viewportAnnotation.data;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
459
|
+
const clippingPlanes = handles.clippingPlanes;
|
|
460
|
+
if (handles.activeOperation === OPERATION.DRAG &&
|
|
461
|
+
clippingPlanes &&
|
|
462
|
+
clippingPlanes.length >= NUM_CLIPPING_PLANES) {
|
|
463
|
+
if (handles.activePlaneIndex !== undefined &&
|
|
464
|
+
handles.activePlaneIndex >= 0 &&
|
|
465
|
+
handles.activePlaneIndex < NUM_CLIPPING_PLANES) {
|
|
466
|
+
const planeIndex = handles.activePlaneIndex;
|
|
467
|
+
const plane = clippingPlanes[planeIndex];
|
|
468
|
+
const normal = plane.normal;
|
|
469
|
+
const dotProd = vtkMath.dot(delta, normal);
|
|
470
|
+
const moveDistance = dotProd;
|
|
471
|
+
plane.origin[0] += normal[0] * moveDistance;
|
|
472
|
+
plane.origin[1] += normal[1] * moveDistance;
|
|
473
|
+
plane.origin[2] += normal[2] * moveDistance;
|
|
768
474
|
}
|
|
769
|
-
else if (handles.activeType === '
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
475
|
+
else if (handles.activeType === 'min') {
|
|
476
|
+
clippingPlanes[0].origin[0] += delta[0];
|
|
477
|
+
clippingPlanes[2].origin[1] += delta[1];
|
|
478
|
+
clippingPlanes[4].origin[2] += delta[2];
|
|
773
479
|
}
|
|
774
|
-
else {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
480
|
+
else if (handles.activeType === 'max') {
|
|
481
|
+
clippingPlanes[1].origin[0] += delta[0];
|
|
482
|
+
clippingPlanes[3].origin[1] += delta[1];
|
|
483
|
+
clippingPlanes[5].origin[2] += delta[2];
|
|
778
484
|
}
|
|
485
|
+
this.clippingPlanes = copyClippingPlanes(clippingPlanes);
|
|
779
486
|
const viewportsInfo = this._getViewportsInfo();
|
|
487
|
+
viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
|
|
488
|
+
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
489
|
+
if (enabledElement) {
|
|
490
|
+
const annotations = this._getAnnotations(enabledElement);
|
|
491
|
+
annotations.forEach((annotation) => {
|
|
492
|
+
if (annotation.data?.handles) {
|
|
493
|
+
annotation.data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
});
|
|
780
498
|
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
781
499
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
782
500
|
toolGroupId: this.toolGroupId,
|
|
783
|
-
|
|
784
|
-
toolCenterMin: this.toolCenterMin,
|
|
785
|
-
toolCenterMax: this.toolCenterMax,
|
|
501
|
+
clippingPlanes: this.clippingPlanes,
|
|
786
502
|
handleType: handles.activeType,
|
|
787
|
-
viewportOrientation: [],
|
|
788
503
|
seriesInstanceUID: this.seriesInstanceUID,
|
|
789
504
|
});
|
|
790
505
|
}
|
|
791
506
|
};
|
|
792
507
|
this._getReferenceLineColor =
|
|
793
508
|
toolProps.configuration?.getReferenceLineColor ||
|
|
794
|
-
|
|
509
|
+
(() => 'rgb(0, 200, 0)');
|
|
795
510
|
this._getReferenceLineControllable =
|
|
796
|
-
toolProps.configuration?.getReferenceLineControllable ||
|
|
797
|
-
defaultReferenceLineControllable;
|
|
511
|
+
toolProps.configuration?.getReferenceLineControllable || (() => true);
|
|
798
512
|
const viewportsInfo = getToolGroup(this.toolGroupId)?.viewportsInfo;
|
|
799
513
|
eventTarget.addEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
|
|
800
514
|
if (viewportsInfo && viewportsInfo.length > 0) {
|
|
801
515
|
const { viewportId, renderingEngineId } = viewportsInfo[0];
|
|
802
|
-
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
803
516
|
const renderingEngine = getRenderingEngine(renderingEngineId);
|
|
517
|
+
if (!renderingEngine) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
804
520
|
const viewport = renderingEngine.getViewport(viewportId);
|
|
521
|
+
if (!viewport) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
805
524
|
const volumeActors = viewport.getActors();
|
|
806
525
|
if (!volumeActors || !volumeActors.length) {
|
|
807
526
|
console.warn(`VolumeCroppingControlTool: No volume actors found in viewport ${viewportId}.`);
|
|
@@ -809,67 +528,11 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
809
528
|
}
|
|
810
529
|
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
811
530
|
if (imageData) {
|
|
812
|
-
const dimensions = imageData.getDimensions();
|
|
813
|
-
const spacing = imageData.getSpacing();
|
|
814
|
-
const origin = imageData.getOrigin();
|
|
815
531
|
this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
|
|
816
|
-
const cropFactor = this.configuration.initialCropFactor ?? 0.2;
|
|
817
|
-
this.toolCenter = [
|
|
818
|
-
origin[0] + cropFactor * (dimensions[0] - 1) * spacing[0],
|
|
819
|
-
origin[1] + cropFactor * (dimensions[1] - 1) * spacing[1],
|
|
820
|
-
origin[2] + cropFactor * (dimensions[2] - 1) * spacing[2],
|
|
821
|
-
];
|
|
822
|
-
const maxCropFactor = 1 - cropFactor;
|
|
823
|
-
this.toolCenterMin = [
|
|
824
|
-
origin[0] + cropFactor * (dimensions[0] - 1) * spacing[0],
|
|
825
|
-
origin[1] + cropFactor * (dimensions[1] - 1) * spacing[1],
|
|
826
|
-
origin[2] + cropFactor * (dimensions[2] - 1) * spacing[2],
|
|
827
|
-
];
|
|
828
|
-
this.toolCenterMax = [
|
|
829
|
-
origin[0] + maxCropFactor * (dimensions[0] - 1) * spacing[0],
|
|
830
|
-
origin[1] + maxCropFactor * (dimensions[1] - 1) * spacing[1],
|
|
831
|
-
origin[2] + maxCropFactor * (dimensions[2] - 1) * spacing[2],
|
|
832
|
-
];
|
|
833
532
|
}
|
|
834
533
|
}
|
|
835
534
|
}
|
|
836
|
-
_updateToolCentersFromViewport(viewport) {
|
|
837
|
-
const volumeActors = viewport.getActors();
|
|
838
|
-
if (!volumeActors || !volumeActors.length) {
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
842
|
-
if (!imageData) {
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
|
|
846
|
-
const dimensions = imageData.getDimensions();
|
|
847
|
-
const spacing = imageData.getSpacing();
|
|
848
|
-
const origin = imageData.getOrigin();
|
|
849
|
-
const cropFactor = this.configuration.initialCropFactor ?? 0.2;
|
|
850
|
-
const cropStart = cropFactor / 2;
|
|
851
|
-
const cropEnd = 1 - cropFactor / 2;
|
|
852
|
-
this.toolCenter = [
|
|
853
|
-
origin[0] +
|
|
854
|
-
((cropStart + cropEnd) / 2) * (dimensions[0] - 1) * spacing[0],
|
|
855
|
-
origin[1] +
|
|
856
|
-
((cropStart + cropEnd) / 2) * (dimensions[1] - 1) * spacing[1],
|
|
857
|
-
origin[2] +
|
|
858
|
-
((cropStart + cropEnd) / 2) * (dimensions[2] - 1) * spacing[2],
|
|
859
|
-
];
|
|
860
|
-
this.toolCenterMin = [
|
|
861
|
-
origin[0] + cropStart * (dimensions[0] - 1) * spacing[0],
|
|
862
|
-
origin[1] + cropStart * (dimensions[1] - 1) * spacing[1],
|
|
863
|
-
origin[2] + cropStart * (dimensions[2] - 1) * spacing[2],
|
|
864
|
-
];
|
|
865
|
-
this.toolCenterMax = [
|
|
866
|
-
origin[0] + cropEnd * (dimensions[0] - 1) * spacing[0],
|
|
867
|
-
origin[1] + cropEnd * (dimensions[1] - 1) * spacing[1],
|
|
868
|
-
origin[2] + cropEnd * (dimensions[2] - 1) * spacing[2],
|
|
869
|
-
];
|
|
870
|
-
}
|
|
871
535
|
onSetToolInactive() {
|
|
872
|
-
console.debug(`VolumeCroppingControlTool: onSetToolInactive called for tool ${this.getToolName()}`);
|
|
873
536
|
}
|
|
874
537
|
onSetToolActive() {
|
|
875
538
|
const viewportsInfo = this._getViewportsInfo();
|
|
@@ -885,7 +548,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
885
548
|
if (!anyAnnotationExists) {
|
|
886
549
|
this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
|
|
887
550
|
this._subscribeToViewportNewVolumeSet(viewportsInfo);
|
|
888
|
-
this.
|
|
551
|
+
this._initializeViewports(viewportsInfo);
|
|
889
552
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
890
553
|
toolGroupId: this.toolGroupId,
|
|
891
554
|
viewportsInfo: viewportsInfo,
|
|
@@ -909,13 +572,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
909
572
|
}
|
|
910
573
|
}
|
|
911
574
|
onSetToolEnabled() {
|
|
912
|
-
|
|
913
|
-
const viewportsInfo = this._getViewportsInfo();
|
|
575
|
+
eventTarget.addEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
|
|
914
576
|
}
|
|
915
577
|
onSetToolDisabled() {
|
|
916
|
-
console.debug(`VolumeCroppingControlTool: onSetToolDisabled called for tool ${this.getToolName()}`);
|
|
917
578
|
const viewportsInfo = this._getViewportsInfo();
|
|
918
579
|
this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
|
|
580
|
+
eventTarget.removeEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
|
|
919
581
|
viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
|
|
920
582
|
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
921
583
|
if (!enabledElement) {
|
|
@@ -929,176 +591,24 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
929
591
|
}
|
|
930
592
|
});
|
|
931
593
|
}
|
|
932
|
-
_getOrientationFromNormal(normal) {
|
|
933
|
-
if (!normal) {
|
|
934
|
-
return null;
|
|
935
|
-
}
|
|
936
|
-
const canonical = {
|
|
937
|
-
AXIAL: [0, 0, 1],
|
|
938
|
-
CORONAL: [0, 1, 0],
|
|
939
|
-
SAGITTAL: [1, 0, 0],
|
|
940
|
-
};
|
|
941
|
-
const tol = 1e-2;
|
|
942
|
-
for (const [key, value] of Object.entries(canonical)) {
|
|
943
|
-
if (Math.abs(normal[0] - value[0]) < tol &&
|
|
944
|
-
Math.abs(normal[1] - value[1]) < tol &&
|
|
945
|
-
Math.abs(normal[2] - value[2]) < tol) {
|
|
946
|
-
return key;
|
|
947
|
-
}
|
|
948
|
-
if (Math.abs(normal[0] + value[0]) < tol &&
|
|
949
|
-
Math.abs(normal[1] + value[1]) < tol &&
|
|
950
|
-
Math.abs(normal[2] + value[2]) < tol) {
|
|
951
|
-
return key;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return null;
|
|
955
|
-
}
|
|
956
594
|
_syncWithVolumeCroppingTool(originalClippingPlanes) {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
(this.toolCenterMin[0] + this.toolCenterMax[0]) / 2,
|
|
971
|
-
(this.toolCenterMin[1] + this.toolCenterMax[1]) / 2,
|
|
972
|
-
(this.toolCenterMin[2] + this.toolCenterMax[2]) / 2,
|
|
973
|
-
];
|
|
974
|
-
const viewportsInfo = this._getViewportsInfo();
|
|
975
|
-
viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
|
|
976
|
-
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
977
|
-
if (enabledElement) {
|
|
978
|
-
const annotations = this._getAnnotations(enabledElement);
|
|
979
|
-
annotations.forEach((annotation) => {
|
|
980
|
-
if (annotation.data &&
|
|
981
|
-
annotation.data.handles &&
|
|
982
|
-
annotation.data.orientation) {
|
|
983
|
-
const orientation = annotation.data.orientation;
|
|
984
|
-
if (orientation === 'AXIAL') {
|
|
985
|
-
annotation.data.handles.toolCenterMin = [
|
|
986
|
-
planes[0].origin[0],
|
|
987
|
-
planes[2].origin[1],
|
|
988
|
-
annotation.data.handles.toolCenterMin[2],
|
|
989
|
-
];
|
|
990
|
-
annotation.data.handles.toolCenterMax = [
|
|
991
|
-
planes[1].origin[0],
|
|
992
|
-
planes[3].origin[1],
|
|
993
|
-
annotation.data.handles.toolCenterMax[2],
|
|
994
|
-
];
|
|
995
|
-
}
|
|
996
|
-
else if (orientation === 'CORONAL') {
|
|
997
|
-
annotation.data.handles.toolCenterMin = [
|
|
998
|
-
planes[0].origin[0],
|
|
999
|
-
annotation.data.handles.toolCenterMin[1],
|
|
1000
|
-
planes[4].origin[2],
|
|
1001
|
-
];
|
|
1002
|
-
annotation.data.handles.toolCenterMax = [
|
|
1003
|
-
planes[1].origin[0],
|
|
1004
|
-
annotation.data.handles.toolCenterMax[1],
|
|
1005
|
-
planes[5].origin[2],
|
|
1006
|
-
];
|
|
1007
|
-
}
|
|
1008
|
-
else if (orientation === 'SAGITTAL') {
|
|
1009
|
-
annotation.data.handles.toolCenterMin = [
|
|
1010
|
-
annotation.data.handles.toolCenterMin[0],
|
|
1011
|
-
planes[2].origin[1],
|
|
1012
|
-
planes[4].origin[2],
|
|
1013
|
-
];
|
|
1014
|
-
annotation.data.handles.toolCenterMax = [
|
|
1015
|
-
annotation.data.handles.toolCenterMax[0],
|
|
1016
|
-
planes[3].origin[1],
|
|
1017
|
-
planes[5].origin[2],
|
|
1018
|
-
];
|
|
1019
|
-
}
|
|
1020
|
-
annotation.data.handles.toolCenter = [
|
|
1021
|
-
(annotation.data.handles.toolCenterMin[0] +
|
|
1022
|
-
annotation.data.handles.toolCenterMax[0]) /
|
|
1023
|
-
2,
|
|
1024
|
-
(annotation.data.handles.toolCenterMin[1] +
|
|
1025
|
-
annotation.data.handles.toolCenterMax[1]) /
|
|
1026
|
-
2,
|
|
1027
|
-
(annotation.data.handles.toolCenterMin[2] +
|
|
1028
|
-
annotation.data.handles.toolCenterMax[2]) /
|
|
1029
|
-
2,
|
|
1030
|
-
];
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
});
|
|
1035
|
-
if (this._virtualAnnotations && this._virtualAnnotations.length > 0) {
|
|
1036
|
-
this._virtualAnnotations.forEach((annotation) => {
|
|
1037
|
-
if (annotation.data &&
|
|
1038
|
-
annotation.data.handles &&
|
|
1039
|
-
annotation.data.orientation) {
|
|
1040
|
-
const orientation = annotation.data.orientation.toUpperCase();
|
|
1041
|
-
if (orientation === 'AXIAL') {
|
|
1042
|
-
annotation.data.handles.toolCenterMin = [
|
|
1043
|
-
planes[0].origin[0],
|
|
1044
|
-
planes[2].origin[1],
|
|
1045
|
-
annotation.data.handles.toolCenterMin[2],
|
|
1046
|
-
];
|
|
1047
|
-
annotation.data.handles.toolCenterMax = [
|
|
1048
|
-
planes[1].origin[0],
|
|
1049
|
-
planes[3].origin[1],
|
|
1050
|
-
annotation.data.handles.toolCenterMax[2],
|
|
1051
|
-
];
|
|
1052
|
-
}
|
|
1053
|
-
else if (orientation === 'CORONAL') {
|
|
1054
|
-
annotation.data.handles.toolCenterMin = [
|
|
1055
|
-
planes[0].origin[0],
|
|
1056
|
-
annotation.data.handles.toolCenterMin[1],
|
|
1057
|
-
planes[4].origin[2],
|
|
1058
|
-
];
|
|
1059
|
-
annotation.data.handles.toolCenterMax = [
|
|
1060
|
-
planes[1].origin[0],
|
|
1061
|
-
annotation.data.handles.toolCenterMax[1],
|
|
1062
|
-
planes[5].origin[2],
|
|
1063
|
-
];
|
|
1064
|
-
}
|
|
1065
|
-
else if (orientation === 'SAGITTAL') {
|
|
1066
|
-
annotation.data.handles.toolCenterMin = [
|
|
1067
|
-
annotation.data.handles.toolCenterMin[0],
|
|
1068
|
-
planes[2].origin[1],
|
|
1069
|
-
planes[4].origin[2],
|
|
1070
|
-
];
|
|
1071
|
-
annotation.data.handles.toolCenterMax = [
|
|
1072
|
-
annotation.data.handles.toolCenterMax[0],
|
|
1073
|
-
planes[3].origin[1],
|
|
1074
|
-
planes[5].origin[2],
|
|
1075
|
-
];
|
|
1076
|
-
}
|
|
1077
|
-
annotation.data.handles.toolCenter = [
|
|
1078
|
-
(annotation.data.handles.toolCenterMin[0] +
|
|
1079
|
-
annotation.data.handles.toolCenterMax[0]) /
|
|
1080
|
-
2,
|
|
1081
|
-
(annotation.data.handles.toolCenterMin[1] +
|
|
1082
|
-
annotation.data.handles.toolCenterMax[1]) /
|
|
1083
|
-
2,
|
|
1084
|
-
(annotation.data.handles.toolCenterMin[2] +
|
|
1085
|
-
annotation.data.handles.toolCenterMax[2]) /
|
|
1086
|
-
2,
|
|
1087
|
-
];
|
|
595
|
+
if (!originalClippingPlanes ||
|
|
596
|
+
originalClippingPlanes.length < NUM_CLIPPING_PLANES) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
this.clippingPlanes = copyClippingPlanes(originalClippingPlanes);
|
|
600
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
601
|
+
viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
|
|
602
|
+
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
603
|
+
if (enabledElement) {
|
|
604
|
+
const annotations = this._getAnnotations(enabledElement);
|
|
605
|
+
annotations.forEach((annotation) => {
|
|
606
|
+
if (annotation.data?.handles) {
|
|
607
|
+
annotation.data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
|
|
1088
608
|
}
|
|
1089
609
|
});
|
|
1090
610
|
}
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
setToolCenter(toolCenter, handleType) {
|
|
1095
|
-
if (handleType === 'min') {
|
|
1096
|
-
this.toolCenterMin = [...toolCenter];
|
|
1097
|
-
}
|
|
1098
|
-
else if (handleType === 'max') {
|
|
1099
|
-
this.toolCenterMax = [...toolCenter];
|
|
1100
|
-
}
|
|
1101
|
-
const viewportsInfo = this._getViewportsInfo();
|
|
611
|
+
});
|
|
1102
612
|
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
1103
613
|
}
|
|
1104
614
|
addNewAnnotation(evt) {
|
|
@@ -1123,7 +633,6 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
1123
633
|
continue;
|
|
1124
634
|
}
|
|
1125
635
|
viewportIdArray.push(otherViewport.id);
|
|
1126
|
-
i++;
|
|
1127
636
|
}
|
|
1128
637
|
data.activeViewportIds = [...viewportIdArray];
|
|
1129
638
|
data.handles.activeOperation = OPERATION.DRAG;
|
|
@@ -1149,51 +658,22 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
1149
658
|
element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
|
|
1150
659
|
});
|
|
1151
660
|
}
|
|
1152
|
-
_applyDeltaShiftToSelectedViewportCameras(renderingEngine, viewportsAnnotationsToUpdate, delta) {
|
|
1153
|
-
viewportsAnnotationsToUpdate.forEach((annotation) => {
|
|
1154
|
-
this._applyDeltaShiftToViewportCamera(renderingEngine, annotation, delta);
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
_applyDeltaShiftToViewportCamera(renderingEngine, annotation, delta) {
|
|
1158
|
-
const { data } = annotation;
|
|
1159
|
-
const viewport = renderingEngine.getViewport(data.viewportId);
|
|
1160
|
-
const camera = viewport.getCamera();
|
|
1161
|
-
const normal = camera.viewPlaneNormal;
|
|
1162
|
-
const dotProd = vtkMath.dot(delta, normal);
|
|
1163
|
-
const projectedDelta = [...normal];
|
|
1164
|
-
vtkMath.multiplyScalar(projectedDelta, dotProd);
|
|
1165
|
-
if (Math.abs(projectedDelta[0]) > 1e-3 ||
|
|
1166
|
-
Math.abs(projectedDelta[1]) > 1e-3 ||
|
|
1167
|
-
Math.abs(projectedDelta[2]) > 1e-3) {
|
|
1168
|
-
const newFocalPoint = [0, 0, 0];
|
|
1169
|
-
const newPosition = [0, 0, 0];
|
|
1170
|
-
vtkMath.add(camera.focalPoint, projectedDelta, newFocalPoint);
|
|
1171
|
-
vtkMath.add(camera.position, projectedDelta, newPosition);
|
|
1172
|
-
viewport.setCamera({
|
|
1173
|
-
focalPoint: newFocalPoint,
|
|
1174
|
-
position: newPosition,
|
|
1175
|
-
});
|
|
1176
|
-
viewport.render();
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
661
|
_pointNearTool(element, annotation, canvasCoords, proximity) {
|
|
1180
662
|
const { data } = annotation;
|
|
1181
663
|
const referenceLines = data.referenceLines;
|
|
1182
664
|
const viewportIdArray = [];
|
|
1183
665
|
if (referenceLines) {
|
|
1184
666
|
for (let i = 0; i < referenceLines.length; ++i) {
|
|
1185
|
-
const otherViewport = referenceLines[i]
|
|
1186
|
-
const
|
|
1187
|
-
const end1 = referenceLines[i][2];
|
|
1188
|
-
const type = referenceLines[i][3];
|
|
1189
|
-
const distance1 = lineSegment.distanceToPoint(start1, end1, [
|
|
667
|
+
const [otherViewport, startPoint, endPoint, type, planeIndex] = referenceLines[i];
|
|
668
|
+
const distance = lineSegment.distanceToPoint(startPoint, endPoint, [
|
|
1190
669
|
canvasCoords[0],
|
|
1191
670
|
canvasCoords[1],
|
|
1192
671
|
]);
|
|
1193
|
-
if (
|
|
672
|
+
if (distance <= proximity) {
|
|
1194
673
|
viewportIdArray.push(otherViewport.id);
|
|
1195
|
-
data.handles.activeOperation =
|
|
674
|
+
data.handles.activeOperation = OPERATION.DRAG;
|
|
1196
675
|
data.handles.activeType = type;
|
|
676
|
+
data.handles.activePlaneIndex = planeIndex;
|
|
1197
677
|
}
|
|
1198
678
|
}
|
|
1199
679
|
}
|
|
@@ -1201,7 +681,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
1201
681
|
this.editData = {
|
|
1202
682
|
annotation,
|
|
1203
683
|
};
|
|
1204
|
-
return data.handles.activeOperation ===
|
|
684
|
+
return data.handles.activeOperation === OPERATION.DRAG ? true : false;
|
|
1205
685
|
}
|
|
1206
686
|
}
|
|
1207
687
|
VolumeCroppingControlTool.toolName = 'VolumeCroppingControl';
|