@cornerstonejs/tools 4.18.4 → 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/tools/VolumeCroppingControlTool.d.ts +35 -10
- package/dist/esm/tools/VolumeCroppingControlTool.js +699 -179
- package/dist/esm/tools/VolumeCroppingTool.d.ts +32 -32
- package/dist/esm/tools/VolumeCroppingTool.js +525 -775
- package/dist/esm/utilities/index.d.ts +1 -2
- package/dist/esm/utilities/index.js +1 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +0 -7
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +0 -34
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +0 -6
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +0 -7
- package/dist/esm/utilities/draw3D/index.d.ts +0 -2
- package/dist/esm/utilities/draw3D/index.js +0 -2
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +0 -6
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +0 -37
- package/dist/esm/utilities/volumeCropping/constants.d.ts +0 -31
- package/dist/esm/utilities/volumeCropping/constants.js +0 -31
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +0 -2
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +0 -6
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +0 -9
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +0 -9
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +0 -5
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +0 -50
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +0 -1
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +0 -13
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +0 -2
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +0 -19
- package/dist/esm/utilities/volumeCropping/index.d.ts +0 -9
- package/dist/esm/utilities/volumeCropping/index.js +0 -9
- package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +0 -8
- package/dist/esm/utilities/volumeCropping/parseCornerKey.js +0 -11
- package/dist/esm/utilities/volumeCropping/types.d.ts +0 -5
- package/dist/esm/utilities/volumeCropping/types.js +0 -0
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import { vec2 } from 'gl-matrix';
|
|
1
|
+
import { vec2, vec3 } 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, Enums, CONSTANTS, triggerEvent, eventTarget,
|
|
5
|
-
import { getToolGroup } from '../store/ToolGroupManager';
|
|
4
|
+
import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, triggerEvent, eventTarget, } from '@cornerstonejs/core';
|
|
5
|
+
import { getToolGroup, getToolGroupForViewport, } 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';
|
|
12
13
|
import * as lineSegment from '../utilities/math/line';
|
|
13
14
|
import { isAnnotationLocked } from '../stateManagement/annotation/annotationLocking';
|
|
14
15
|
import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
|
|
15
|
-
|
|
16
|
+
const { RENDERING_DEFAULTS } = CONSTANTS;
|
|
17
|
+
function defaultReferenceLineColor() {
|
|
18
|
+
return 'rgb(0, 200, 0)';
|
|
19
|
+
}
|
|
20
|
+
function defaultReferenceLineControllable() {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
16
23
|
const OPERATION = {
|
|
17
24
|
DRAG: 1,
|
|
18
25
|
ROTATE: 2,
|
|
@@ -45,7 +52,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
45
52
|
},
|
|
46
53
|
}) {
|
|
47
54
|
super(toolProps, defaultToolProps);
|
|
48
|
-
this.
|
|
55
|
+
this._virtualAnnotations = [];
|
|
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];
|
|
49
61
|
this.initializeViewport = ({ renderingEngineId, viewportId, }) => {
|
|
50
62
|
if (!renderingEngineId || !viewportId) {
|
|
51
63
|
console.warn('VolumeCroppingControlTool: Missing renderingEngineId or viewportId');
|
|
@@ -56,21 +68,15 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
56
68
|
return;
|
|
57
69
|
}
|
|
58
70
|
const { viewport } = enabledElement;
|
|
59
|
-
|
|
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
|
-
}
|
|
71
|
+
this._updateToolCentersFromViewport(viewport);
|
|
66
72
|
const { element } = viewport;
|
|
67
|
-
const { position, focalPoint } = viewport.getCamera();
|
|
73
|
+
const { position, focalPoint, viewPlaneNormal } = viewport.getCamera();
|
|
68
74
|
let annotations = this._getAnnotations(enabledElement);
|
|
69
75
|
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
70
76
|
if (annotations?.length) {
|
|
71
77
|
removeAnnotation(annotations[0].annotationUID);
|
|
72
78
|
}
|
|
73
|
-
const orientation =
|
|
79
|
+
const orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
|
|
74
80
|
const annotation = {
|
|
75
81
|
highlighted: false,
|
|
76
82
|
metadata: {
|
|
@@ -80,11 +86,11 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
80
86
|
},
|
|
81
87
|
data: {
|
|
82
88
|
handles: {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
: [],
|
|
89
|
+
toolCenter: this.toolCenter,
|
|
90
|
+
toolCenterMin: this.toolCenterMin,
|
|
91
|
+
toolCenterMax: this.toolCenterMax,
|
|
87
92
|
},
|
|
93
|
+
activeOperation: null,
|
|
88
94
|
activeViewportIds: [],
|
|
89
95
|
viewportId,
|
|
90
96
|
referenceLines: [],
|
|
@@ -92,6 +98,10 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
92
98
|
},
|
|
93
99
|
};
|
|
94
100
|
addAnnotation(annotation, element);
|
|
101
|
+
return {
|
|
102
|
+
normal: viewPlaneNormal,
|
|
103
|
+
point: viewport.canvasToWorld([100, 100]),
|
|
104
|
+
};
|
|
95
105
|
};
|
|
96
106
|
this._getViewportsInfo = () => {
|
|
97
107
|
const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
|
|
@@ -124,22 +134,139 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
124
134
|
}
|
|
125
135
|
viewport.render();
|
|
126
136
|
}
|
|
127
|
-
this.
|
|
137
|
+
this._computeToolCenter(viewportsInfo);
|
|
138
|
+
};
|
|
139
|
+
this.computeToolCenter = () => {
|
|
140
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
128
141
|
};
|
|
129
|
-
this.
|
|
130
|
-
if (!viewportsInfo
|
|
131
|
-
console.warn('
|
|
142
|
+
this._computeToolCenter = (viewportsInfo) => {
|
|
143
|
+
if (!viewportsInfo || !viewportsInfo[0]) {
|
|
144
|
+
console.warn(' _computeToolCenter : No valid viewportsInfo for computeToolCenter.');
|
|
132
145
|
return;
|
|
133
146
|
}
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
const orientationIds = ['AXIAL', 'CORONAL', 'SAGITTAL'];
|
|
148
|
+
const presentOrientations = viewportsInfo
|
|
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);
|
|
136
176
|
});
|
|
137
|
-
|
|
177
|
+
presentViewportInfos.forEach((vpInfo) => {
|
|
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
|
+
}
|
|
138
264
|
};
|
|
139
265
|
this.cancel = () => {
|
|
266
|
+
console.log('Not implemented yet');
|
|
140
267
|
};
|
|
141
268
|
this.isPointNearTool = (element, annotation, canvasCoords, proximity) => {
|
|
142
|
-
if (this._pointNearTool(element, annotation, canvasCoords,
|
|
269
|
+
if (this._pointNearTool(element, annotation, canvasCoords, 6)) {
|
|
143
270
|
return true;
|
|
144
271
|
}
|
|
145
272
|
return false;
|
|
@@ -171,8 +298,13 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
171
298
|
if (!data.handles) {
|
|
172
299
|
continue;
|
|
173
300
|
}
|
|
301
|
+
const previousActiveOperation = data.handles.activeOperation;
|
|
302
|
+
const previousActiveViewportIds = data.activeViewportIds && data.activeViewportIds.length > 0
|
|
303
|
+
? [...data.activeViewportIds]
|
|
304
|
+
: [];
|
|
174
305
|
data.activeViewportIds = [];
|
|
175
|
-
|
|
306
|
+
let near = false;
|
|
307
|
+
near = this._pointNearTool(element, annotation, canvasCoords, 6);
|
|
176
308
|
const nearToolAndNotMarkedActive = near && !highlighted;
|
|
177
309
|
const notNearToolAndMarkedActive = !near && highlighted;
|
|
178
310
|
if (nearToolAndNotMarkedActive || notNearToolAndMarkedActive) {
|
|
@@ -189,9 +321,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
189
321
|
const enabledElement = getEnabledElement(element);
|
|
190
322
|
let orientation = null;
|
|
191
323
|
if (enabledElement.viewport && enabledElement.viewport.getCamera) {
|
|
192
|
-
orientation =
|
|
324
|
+
orientation = this._getOrientationFromNormal(enabledElement.viewport.getCamera().viewPlaneNormal);
|
|
193
325
|
}
|
|
194
326
|
const filtered = annotations.filter((annotation) => {
|
|
327
|
+
if (annotation.isVirtual) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
195
330
|
if (annotation.data.orientation &&
|
|
196
331
|
orientation &&
|
|
197
332
|
annotation.data.orientation === orientation) {
|
|
@@ -208,7 +343,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
208
343
|
const s2_x = q2[0] - q1[0];
|
|
209
344
|
const s2_y = q2[1] - q1[1];
|
|
210
345
|
const denom = -s2_x * s1_y + s1_x * s2_y;
|
|
211
|
-
if (Math.abs(denom) <
|
|
346
|
+
if (Math.abs(denom) < 1e-8) {
|
|
212
347
|
return null;
|
|
213
348
|
}
|
|
214
349
|
const s = (-s1_y * (p1[0] - q1[0]) + s1_x * (p1[1] - q1[1])) / denom;
|
|
@@ -225,7 +360,10 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
225
360
|
let renderStatus = false;
|
|
226
361
|
const { viewport, renderingEngine } = enabledElement;
|
|
227
362
|
const { element } = viewport;
|
|
228
|
-
|
|
363
|
+
let annotations = this._getAnnotations(enabledElement);
|
|
364
|
+
if (this._virtualAnnotations && this._virtualAnnotations.length) {
|
|
365
|
+
annotations = annotations.concat(this._virtualAnnotations);
|
|
366
|
+
}
|
|
229
367
|
const camera = viewport.getCamera();
|
|
230
368
|
const filteredToolAnnotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
231
369
|
const viewportAnnotation = filteredToolAnnotations[0];
|
|
@@ -236,65 +374,135 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
236
374
|
const { clientWidth, clientHeight } = viewport.canvas;
|
|
237
375
|
const canvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
|
|
238
376
|
const data = viewportAnnotation.data;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.clippingPlanes.length >= NUM_CLIPPING_PLANES) {
|
|
243
|
-
clippingPlanes = this.clippingPlanes;
|
|
244
|
-
data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const { viewPlaneNormal, focalPoint } = camera;
|
|
377
|
+
const otherViewportAnnotations = annotations;
|
|
378
|
+
const volumeCroppingCenterCanvasMin = viewport.worldToCanvas(this.toolCenterMin);
|
|
379
|
+
const volumeCroppingCenterCanvasMax = viewport.worldToCanvas(this.toolCenterMax);
|
|
251
380
|
const referenceLines = [];
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
'
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
381
|
+
const canvasBox = [0, 0, clientWidth, clientHeight];
|
|
382
|
+
otherViewportAnnotations.forEach((annotation) => {
|
|
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
|
+
}
|
|
265
435
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
436
|
+
else {
|
|
437
|
+
otherViewport = renderingEngine.getViewport(data.viewportId);
|
|
438
|
+
otherCamera = otherViewport.getCamera();
|
|
439
|
+
clientWidth = otherViewport.canvas.clientWidth;
|
|
440
|
+
clientHeight = otherViewport.canvas.clientHeight;
|
|
441
|
+
otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
|
|
442
|
+
otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
|
|
443
|
+
otherViewportCenterWorld =
|
|
444
|
+
otherViewport.canvasToWorld(otherCanvasCenter);
|
|
269
445
|
}
|
|
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);
|
|
270
474
|
referenceLines.push([
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
lineBounds.start,
|
|
276
|
-
lineBounds.end,
|
|
277
|
-
planeTypes[planeIndex],
|
|
278
|
-
planeIndex,
|
|
475
|
+
otherViewport,
|
|
476
|
+
refLinePointMinOne,
|
|
477
|
+
refLinePointMinTwo,
|
|
478
|
+
'min',
|
|
279
479
|
]);
|
|
280
|
-
|
|
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
|
+
referenceLines.push([
|
|
489
|
+
otherViewport,
|
|
490
|
+
refLinePointMaxOne,
|
|
491
|
+
refLinePointMaxTwo,
|
|
492
|
+
'max',
|
|
493
|
+
]);
|
|
494
|
+
});
|
|
281
495
|
data.referenceLines = referenceLines;
|
|
282
496
|
const viewportColor = this._getReferenceLineColor(viewport.id);
|
|
283
|
-
const
|
|
497
|
+
const color = viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
|
|
284
498
|
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
|
-
}
|
|
291
499
|
const intersections = [];
|
|
292
500
|
for (let j = 0; j < referenceLines.length; ++j) {
|
|
293
501
|
if (j === lineIndex) {
|
|
294
502
|
continue;
|
|
295
503
|
}
|
|
296
504
|
const otherLine = referenceLines[j];
|
|
297
|
-
const intersection = lineIntersection2D(
|
|
505
|
+
const intersection = lineIntersection2D(line[1], line[2], otherLine[1], otherLine[2]);
|
|
298
506
|
if (intersection) {
|
|
299
507
|
intersections.push({
|
|
300
508
|
with: otherLine[3],
|
|
@@ -302,12 +510,32 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
302
510
|
});
|
|
303
511
|
}
|
|
304
512
|
}
|
|
305
|
-
const
|
|
513
|
+
const otherViewport = line[0];
|
|
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
|
+
}
|
|
306
533
|
const lineColors = this.configuration.lineColors || {};
|
|
307
|
-
const colorArr =
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
534
|
+
const colorArr = lineColors[orientation] ||
|
|
535
|
+
lineColors.unknown || [1.0, 0.0, 0.0];
|
|
536
|
+
const color = Array.isArray(colorArr)
|
|
537
|
+
? `rgb(${colorArr.map((v) => Math.round(v * 255)).join(',')})`
|
|
538
|
+
: colorArr;
|
|
311
539
|
const viewportControllable = this._getReferenceLineControllable(otherViewport.id);
|
|
312
540
|
const selectedViewportId = data.activeViewportIds.find((id) => id === otherViewport.id);
|
|
313
541
|
let lineWidth = this.configuration.lineWidth ?? 1.5;
|
|
@@ -317,29 +545,28 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
317
545
|
if (lineActive) {
|
|
318
546
|
lineWidth = this.configuration.activeLineWidth ?? 2.5;
|
|
319
547
|
}
|
|
320
|
-
const lineUID =
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_after', sortedIntersections[1].point, endPoint, { color, lineWidth, lineDash: [4, 4] });
|
|
548
|
+
const lineUID = `${lineIndex}`;
|
|
549
|
+
if (viewportControllable) {
|
|
550
|
+
if (intersections.length === 2) {
|
|
551
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID, intersections[0].point, intersections[1].point, {
|
|
552
|
+
color,
|
|
553
|
+
lineWidth,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (this.configuration.extendReferenceLines &&
|
|
557
|
+
intersections.length === 2) {
|
|
558
|
+
if (this.configuration.extendReferenceLines &&
|
|
559
|
+
intersections.length === 2) {
|
|
560
|
+
const sortedIntersections = intersections
|
|
561
|
+
.map((intersection) => ({
|
|
562
|
+
...intersection,
|
|
563
|
+
distance: vec2.distance(line[1], intersection.point),
|
|
564
|
+
}))
|
|
565
|
+
.sort((a, b) => a.distance - b.distance);
|
|
566
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_before', line[1], sortedIntersections[0].point, { color, lineWidth, lineDash: [4, 4] });
|
|
567
|
+
drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_after', sortedIntersections[1].point, line[2], { color, lineWidth, lineDash: [4, 4] });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
343
570
|
}
|
|
344
571
|
});
|
|
345
572
|
renderStatus = true;
|
|
@@ -353,7 +580,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
353
580
|
];
|
|
354
581
|
const circleRadius = viewportIndicatorsConfig?.circleRadius || canvasDiagonalLength * 0.01;
|
|
355
582
|
const circleUID = '0';
|
|
356
|
-
drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color
|
|
583
|
+
drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color, fill: color });
|
|
357
584
|
}
|
|
358
585
|
return renderStatus;
|
|
359
586
|
};
|
|
@@ -375,7 +602,40 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
375
602
|
if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
|
|
376
603
|
return;
|
|
377
604
|
}
|
|
378
|
-
|
|
605
|
+
const { draggingSphereIndex, toolCenter } = evt.detail;
|
|
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
|
+
}
|
|
379
639
|
}
|
|
380
640
|
};
|
|
381
641
|
this._onNewVolume = () => {
|
|
@@ -383,28 +643,72 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
383
643
|
if (viewportsInfo && viewportsInfo.length > 0) {
|
|
384
644
|
const { viewportId, renderingEngineId } = viewportsInfo[0];
|
|
385
645
|
const renderingEngine = getRenderingEngine(renderingEngineId);
|
|
386
|
-
if (!renderingEngine) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
646
|
const viewport = renderingEngine.getViewport(viewportId);
|
|
390
|
-
if (!viewport) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
647
|
const volumeActors = viewport.getActors();
|
|
394
648
|
if (volumeActors.length > 0) {
|
|
395
649
|
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
396
650
|
if (imageData) {
|
|
397
651
|
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
|
+
});
|
|
398
659
|
}
|
|
399
660
|
}
|
|
400
661
|
}
|
|
401
|
-
this.
|
|
662
|
+
this._computeToolCenter(viewportsInfo);
|
|
402
663
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
403
664
|
toolGroupId: this.toolGroupId,
|
|
404
665
|
viewportsInfo: viewportsInfo,
|
|
405
666
|
seriesInstanceUID: this.seriesInstanceUID,
|
|
406
667
|
});
|
|
407
668
|
};
|
|
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
|
+
};
|
|
408
712
|
this._activateModify = (element) => {
|
|
409
713
|
state.isInteractingWithTool = !this.configuration.mobile?.enabled;
|
|
410
714
|
element.addEventListener(Events.MOUSE_UP, this._endCallback);
|
|
@@ -456,71 +760,48 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
456
760
|
return;
|
|
457
761
|
}
|
|
458
762
|
const { handles } = viewportAnnotation.data;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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;
|
|
474
|
-
}
|
|
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];
|
|
763
|
+
if (handles.activeOperation === OPERATION.DRAG) {
|
|
764
|
+
if (handles.activeType === 'min') {
|
|
765
|
+
this.toolCenterMin[0] += delta[0];
|
|
766
|
+
this.toolCenterMin[1] += delta[1];
|
|
767
|
+
this.toolCenterMin[2] += delta[2];
|
|
479
768
|
}
|
|
480
769
|
else if (handles.activeType === 'max') {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
770
|
+
this.toolCenterMax[0] += delta[0];
|
|
771
|
+
this.toolCenterMax[1] += delta[1];
|
|
772
|
+
this.toolCenterMax[2] += delta[2];
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
this.toolCenter[0] += delta[0];
|
|
776
|
+
this.toolCenter[1] += delta[1];
|
|
777
|
+
this.toolCenter[2] += delta[2];
|
|
484
778
|
}
|
|
485
|
-
this.clippingPlanes = copyClippingPlanes(clippingPlanes);
|
|
486
779
|
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
|
-
});
|
|
498
780
|
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
499
781
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
500
782
|
toolGroupId: this.toolGroupId,
|
|
501
|
-
|
|
783
|
+
toolCenter: this.toolCenter,
|
|
784
|
+
toolCenterMin: this.toolCenterMin,
|
|
785
|
+
toolCenterMax: this.toolCenterMax,
|
|
502
786
|
handleType: handles.activeType,
|
|
787
|
+
viewportOrientation: [],
|
|
503
788
|
seriesInstanceUID: this.seriesInstanceUID,
|
|
504
789
|
});
|
|
505
790
|
}
|
|
506
791
|
};
|
|
507
792
|
this._getReferenceLineColor =
|
|
508
793
|
toolProps.configuration?.getReferenceLineColor ||
|
|
509
|
-
|
|
794
|
+
defaultReferenceLineColor;
|
|
510
795
|
this._getReferenceLineControllable =
|
|
511
|
-
toolProps.configuration?.getReferenceLineControllable ||
|
|
796
|
+
toolProps.configuration?.getReferenceLineControllable ||
|
|
797
|
+
defaultReferenceLineControllable;
|
|
512
798
|
const viewportsInfo = getToolGroup(this.toolGroupId)?.viewportsInfo;
|
|
513
799
|
eventTarget.addEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
|
|
514
800
|
if (viewportsInfo && viewportsInfo.length > 0) {
|
|
515
801
|
const { viewportId, renderingEngineId } = viewportsInfo[0];
|
|
802
|
+
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
516
803
|
const renderingEngine = getRenderingEngine(renderingEngineId);
|
|
517
|
-
if (!renderingEngine) {
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
804
|
const viewport = renderingEngine.getViewport(viewportId);
|
|
521
|
-
if (!viewport) {
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
805
|
const volumeActors = viewport.getActors();
|
|
525
806
|
if (!volumeActors || !volumeActors.length) {
|
|
526
807
|
console.warn(`VolumeCroppingControlTool: No volume actors found in viewport ${viewportId}.`);
|
|
@@ -528,11 +809,67 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
528
809
|
}
|
|
529
810
|
const imageData = volumeActors[0].actor.getMapper().getInputData();
|
|
530
811
|
if (imageData) {
|
|
812
|
+
const dimensions = imageData.getDimensions();
|
|
813
|
+
const spacing = imageData.getSpacing();
|
|
814
|
+
const origin = imageData.getOrigin();
|
|
531
815
|
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
|
+
];
|
|
532
833
|
}
|
|
533
834
|
}
|
|
534
835
|
}
|
|
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
|
+
}
|
|
535
871
|
onSetToolInactive() {
|
|
872
|
+
console.debug(`VolumeCroppingControlTool: onSetToolInactive called for tool ${this.getToolName()}`);
|
|
536
873
|
}
|
|
537
874
|
onSetToolActive() {
|
|
538
875
|
const viewportsInfo = this._getViewportsInfo();
|
|
@@ -548,7 +885,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
548
885
|
if (!anyAnnotationExists) {
|
|
549
886
|
this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
|
|
550
887
|
this._subscribeToViewportNewVolumeSet(viewportsInfo);
|
|
551
|
-
this.
|
|
888
|
+
this._computeToolCenter(viewportsInfo);
|
|
552
889
|
triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
|
|
553
890
|
toolGroupId: this.toolGroupId,
|
|
554
891
|
viewportsInfo: viewportsInfo,
|
|
@@ -572,12 +909,13 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
572
909
|
}
|
|
573
910
|
}
|
|
574
911
|
onSetToolEnabled() {
|
|
575
|
-
|
|
912
|
+
console.debug(`VolumeCroppingControlTool: onSetToolEnabled called for tool ${this.getToolName()}`);
|
|
913
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
576
914
|
}
|
|
577
915
|
onSetToolDisabled() {
|
|
916
|
+
console.debug(`VolumeCroppingControlTool: onSetToolDisabled called for tool ${this.getToolName()}`);
|
|
578
917
|
const viewportsInfo = this._getViewportsInfo();
|
|
579
918
|
this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
|
|
580
|
-
eventTarget.removeEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
|
|
581
919
|
viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
|
|
582
920
|
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
583
921
|
if (!enabledElement) {
|
|
@@ -591,24 +929,176 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
591
929
|
}
|
|
592
930
|
});
|
|
593
931
|
}
|
|
594
|
-
|
|
595
|
-
if (!
|
|
596
|
-
|
|
597
|
-
return;
|
|
932
|
+
_getOrientationFromNormal(normal) {
|
|
933
|
+
if (!normal) {
|
|
934
|
+
return null;
|
|
598
935
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
+
_syncWithVolumeCroppingTool(originalClippingPlanes) {
|
|
957
|
+
const planes = originalClippingPlanes;
|
|
958
|
+
if (planes.length >= 6) {
|
|
959
|
+
this.toolCenterMin = [
|
|
960
|
+
planes[0].origin[0],
|
|
961
|
+
planes[2].origin[1],
|
|
962
|
+
planes[4].origin[2],
|
|
963
|
+
];
|
|
964
|
+
this.toolCenterMax = [
|
|
965
|
+
planes[1].origin[0],
|
|
966
|
+
planes[3].origin[1],
|
|
967
|
+
planes[5].origin[2],
|
|
968
|
+
];
|
|
969
|
+
this.toolCenter = [
|
|
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
|
+
];
|
|
608
1088
|
}
|
|
609
1089
|
});
|
|
610
1090
|
}
|
|
611
|
-
|
|
1091
|
+
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
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();
|
|
612
1102
|
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
613
1103
|
}
|
|
614
1104
|
addNewAnnotation(evt) {
|
|
@@ -633,6 +1123,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
633
1123
|
continue;
|
|
634
1124
|
}
|
|
635
1125
|
viewportIdArray.push(otherViewport.id);
|
|
1126
|
+
i++;
|
|
636
1127
|
}
|
|
637
1128
|
data.activeViewportIds = [...viewportIdArray];
|
|
638
1129
|
data.handles.activeOperation = OPERATION.DRAG;
|
|
@@ -658,22 +1149,51 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
658
1149
|
element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
|
|
659
1150
|
});
|
|
660
1151
|
}
|
|
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
|
+
}
|
|
661
1179
|
_pointNearTool(element, annotation, canvasCoords, proximity) {
|
|
662
1180
|
const { data } = annotation;
|
|
663
1181
|
const referenceLines = data.referenceLines;
|
|
664
1182
|
const viewportIdArray = [];
|
|
665
1183
|
if (referenceLines) {
|
|
666
1184
|
for (let i = 0; i < referenceLines.length; ++i) {
|
|
667
|
-
const
|
|
668
|
-
const
|
|
1185
|
+
const otherViewport = referenceLines[i][0];
|
|
1186
|
+
const start1 = referenceLines[i][1];
|
|
1187
|
+
const end1 = referenceLines[i][2];
|
|
1188
|
+
const type = referenceLines[i][3];
|
|
1189
|
+
const distance1 = lineSegment.distanceToPoint(start1, end1, [
|
|
669
1190
|
canvasCoords[0],
|
|
670
1191
|
canvasCoords[1],
|
|
671
1192
|
]);
|
|
672
|
-
if (
|
|
1193
|
+
if (distance1 <= proximity) {
|
|
673
1194
|
viewportIdArray.push(otherViewport.id);
|
|
674
|
-
data.handles.activeOperation =
|
|
1195
|
+
data.handles.activeOperation = 1;
|
|
675
1196
|
data.handles.activeType = type;
|
|
676
|
-
data.handles.activePlaneIndex = planeIndex;
|
|
677
1197
|
}
|
|
678
1198
|
}
|
|
679
1199
|
}
|
|
@@ -681,7 +1201,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
|
|
|
681
1201
|
this.editData = {
|
|
682
1202
|
annotation,
|
|
683
1203
|
};
|
|
684
|
-
return data.handles.activeOperation ===
|
|
1204
|
+
return data.handles.activeOperation === 1 ? true : false;
|
|
685
1205
|
}
|
|
686
1206
|
}
|
|
687
1207
|
VolumeCroppingControlTool.toolName = 'VolumeCroppingControl';
|