@cornerstonejs/tools 3.31.14 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1064 @@
1
+ import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
2
+ import vtkPoints from '@kitware/vtk.js/Common/Core/Points';
3
+ import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray';
4
+ import { mat3, mat4, vec3 } from 'gl-matrix';
5
+ import vtkMath from '@kitware/vtk.js/Common/Core/Math';
6
+ import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
7
+ import vtkSphereSource from '@kitware/vtk.js/Filters/Sources/SphereSource';
8
+ import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
9
+ import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
10
+ import { BaseTool } from './base';
11
+ import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, triggerEvent, eventTarget, } from '@cornerstonejs/core';
12
+ import { getToolGroup } from '../store/ToolGroupManager';
13
+ import { Events } from '../enums';
14
+ const PLANEINDEX = {
15
+ XMIN: 0,
16
+ XMAX: 1,
17
+ YMIN: 2,
18
+ YMAX: 3,
19
+ ZMIN: 4,
20
+ ZMAX: 5,
21
+ };
22
+ const SPHEREINDEX = {
23
+ XMIN: 0,
24
+ XMAX: 1,
25
+ YMIN: 2,
26
+ YMAX: 3,
27
+ ZMIN: 4,
28
+ ZMAX: 5,
29
+ XMIN_YMIN_ZMIN: 6,
30
+ XMIN_YMIN_ZMAX: 7,
31
+ XMIN_YMAX_ZMIN: 8,
32
+ XMIN_YMAX_ZMAX: 9,
33
+ XMAX_YMIN_ZMIN: 10,
34
+ XMAX_YMIN_ZMAX: 11,
35
+ XMAX_YMAX_ZMIN: 12,
36
+ XMAX_YMAX_ZMAX: 13,
37
+ };
38
+ class VolumeCroppingTool extends BaseTool {
39
+ constructor(toolProps = {}, defaultToolProps = {
40
+ configuration: {
41
+ showCornerSpheres: true,
42
+ showHandles: true,
43
+ showClippingPlanes: true,
44
+ mobile: {
45
+ enabled: false,
46
+ opacity: 0.8,
47
+ },
48
+ initialCropFactor: 0.08,
49
+ sphereColors: {
50
+ SAGITTAL: [1.0, 1.0, 0.0],
51
+ CORONAL: [0.0, 1.0, 0.0],
52
+ AXIAL: [1.0, 0.0, 0.0],
53
+ CORNERS: [0.0, 0.0, 1.0],
54
+ },
55
+ sphereRadius: 8,
56
+ grabSpherePixelDistance: 20,
57
+ rotateIncrementDegrees: 2,
58
+ rotateSampleDistanceFactor: 2,
59
+ },
60
+ }) {
61
+ super(toolProps, defaultToolProps);
62
+ this._resizeObservers = new Map();
63
+ this._hasResolutionChanged = false;
64
+ this.originalClippingPlanes = [];
65
+ this.draggingSphereIndex = null;
66
+ this.toolCenter = [0, 0, 0];
67
+ this.cornerDragOffset = null;
68
+ this.faceDragOffset = null;
69
+ this.sphereStates = [];
70
+ this.edgeLines = {};
71
+ this.onSetToolConfiguration = () => {
72
+ console.debug('Setting tool settoolconfiguration : volumeCropping');
73
+ };
74
+ this.onSetToolEnabled = () => {
75
+ console.debug('Setting tool enabled: volumeCropping');
76
+ };
77
+ this.onCameraModified = (evt) => {
78
+ const { element } = evt.currentTarget
79
+ ? { element: evt.currentTarget }
80
+ : evt.detail;
81
+ const enabledElement = getEnabledElement(element);
82
+ this._updateClippingPlanes(enabledElement.viewport);
83
+ enabledElement.viewport.render();
84
+ };
85
+ this.preMouseDownCallback = (evt) => {
86
+ const eventDetail = evt.detail;
87
+ const { element } = eventDetail;
88
+ const enabledElement = getEnabledElement(element);
89
+ const { viewport } = enabledElement;
90
+ const actorEntry = viewport.getDefaultActor();
91
+ const actor = actorEntry.actor;
92
+ const mapper = actor.getMapper();
93
+ const mouseCanvas = [
94
+ evt.detail.currentPoints.canvas[0],
95
+ evt.detail.currentPoints.canvas[1],
96
+ ];
97
+ this.draggingSphereIndex = null;
98
+ this.cornerDragOffset = null;
99
+ this.faceDragOffset = null;
100
+ for (let i = 0; i < this.sphereStates.length; ++i) {
101
+ const sphereCanvas = viewport.worldToCanvas(this.sphereStates[i].point);
102
+ const dist = Math.sqrt(Math.pow(mouseCanvas[0] - sphereCanvas[0], 2) +
103
+ Math.pow(mouseCanvas[1] - sphereCanvas[1], 2));
104
+ if (dist < this.configuration.grabSpherePixelDistance) {
105
+ this.draggingSphereIndex = i;
106
+ element.style.cursor = 'grabbing';
107
+ const sphereState = this.sphereStates[i];
108
+ const mouseWorld = viewport.canvasToWorld(mouseCanvas);
109
+ if (sphereState.isCorner) {
110
+ this.cornerDragOffset = [
111
+ sphereState.point[0] - mouseWorld[0],
112
+ sphereState.point[1] - mouseWorld[1],
113
+ sphereState.point[2] - mouseWorld[2],
114
+ ];
115
+ this.faceDragOffset = null;
116
+ }
117
+ else {
118
+ const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
119
+ this.faceDragOffset =
120
+ sphereState.point[axisIdx] - mouseWorld[axisIdx];
121
+ this.cornerDragOffset = null;
122
+ }
123
+ return true;
124
+ }
125
+ }
126
+ const hasSampleDistance = 'getSampleDistance' in mapper || 'getCurrentSampleDistance' in mapper;
127
+ if (!hasSampleDistance) {
128
+ return true;
129
+ }
130
+ const originalSampleDistance = mapper.getSampleDistance();
131
+ if (!this._hasResolutionChanged) {
132
+ const { rotateSampleDistanceFactor } = this.configuration;
133
+ mapper.setSampleDistance(originalSampleDistance * rotateSampleDistanceFactor);
134
+ this._hasResolutionChanged = true;
135
+ if (this.cleanUp !== null) {
136
+ document.removeEventListener('mouseup', this.cleanUp);
137
+ }
138
+ this.cleanUp = () => {
139
+ mapper.setSampleDistance(originalSampleDistance);
140
+ evt.target.style.cursor = '';
141
+ if (this.draggingSphereIndex !== null) {
142
+ const sphereState = this.sphereStates[this.draggingSphereIndex];
143
+ const [viewport3D] = this._getViewportsInfo();
144
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
145
+ const viewport = renderingEngine.getViewport(viewport3D.viewportId);
146
+ if (sphereState.isCorner) {
147
+ this._updateCornerSpheres();
148
+ this._updateFaceSpheresFromCorners();
149
+ this._updateClippingPlanesFromFaceSpheres(viewport);
150
+ }
151
+ }
152
+ this.draggingSphereIndex = null;
153
+ this.cornerDragOffset = null;
154
+ this.faceDragOffset = null;
155
+ viewport.render();
156
+ this._hasResolutionChanged = false;
157
+ };
158
+ document.addEventListener('mouseup', this.cleanUp, { once: true });
159
+ }
160
+ return true;
161
+ };
162
+ this._onMouseMoveSphere = (evt) => {
163
+ if (this.draggingSphereIndex === null) {
164
+ return false;
165
+ }
166
+ const sphereState = this.sphereStates[this.draggingSphereIndex];
167
+ if (!sphereState) {
168
+ return false;
169
+ }
170
+ const { viewport, world } = this._getViewportAndWorldCoords(evt);
171
+ if (!viewport || !world) {
172
+ return false;
173
+ }
174
+ if (sphereState.isCorner) {
175
+ const newCorner = this._calculateNewCornerPosition(world);
176
+ this._updateSpherePosition(sphereState, newCorner);
177
+ const axisFlags = this._parseCornerKey(sphereState.uid);
178
+ this._updateRelatedCorners(sphereState, newCorner, axisFlags);
179
+ this._updateFaceSpheresFromCorners();
180
+ this._updateCornerSpheres();
181
+ }
182
+ else {
183
+ const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
184
+ let newValue = world[axisIdx];
185
+ if (this.faceDragOffset !== null) {
186
+ newValue += this.faceDragOffset;
187
+ }
188
+ sphereState.point[axisIdx] = newValue;
189
+ sphereState.sphereSource.setCenter(...sphereState.point);
190
+ sphereState.sphereSource.modified();
191
+ this._updateCornerSpheresFromFaces();
192
+ this._updateFaceSpheresFromCorners();
193
+ this._updateCornerSpheres();
194
+ }
195
+ this._updateClippingPlanesFromFaceSpheres(viewport);
196
+ viewport.render();
197
+ this._triggerToolChangedEvent(sphereState);
198
+ return true;
199
+ };
200
+ this._onControlToolChange = (evt) => {
201
+ const viewport = this._getViewport();
202
+ if (!evt.detail.toolCenter) {
203
+ triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
204
+ originalClippingPlanes: this.originalClippingPlanes,
205
+ viewportId: viewport.id,
206
+ renderingEngineId: viewport.renderingEngineId,
207
+ seriesInstanceUID: this.seriesInstanceUID,
208
+ });
209
+ }
210
+ else {
211
+ if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
212
+ return;
213
+ }
214
+ const isMin = evt.detail.handleType === 'min';
215
+ const toolCenter = isMin
216
+ ? evt.detail.toolCenterMin
217
+ : evt.detail.toolCenterMax;
218
+ const normals = isMin
219
+ ? [
220
+ [1, 0, 0],
221
+ [0, 1, 0],
222
+ [0, 0, 1],
223
+ ]
224
+ : [
225
+ [-1, 0, 0],
226
+ [0, -1, 0],
227
+ [0, 0, -1],
228
+ ];
229
+ const planeIndices = isMin
230
+ ? [PLANEINDEX.XMIN, PLANEINDEX.YMIN, PLANEINDEX.ZMIN]
231
+ : [PLANEINDEX.XMAX, PLANEINDEX.YMAX, PLANEINDEX.ZMAX];
232
+ const sphereIndices = isMin
233
+ ? [SPHEREINDEX.XMIN, SPHEREINDEX.YMIN, SPHEREINDEX.ZMIN]
234
+ : [SPHEREINDEX.XMAX, SPHEREINDEX.YMAX, SPHEREINDEX.ZMAX];
235
+ const axes = ['x', 'y', 'z'];
236
+ const orientationAxes = [
237
+ Enums.OrientationAxis.SAGITTAL,
238
+ Enums.OrientationAxis.CORONAL,
239
+ Enums.OrientationAxis.AXIAL,
240
+ ];
241
+ for (let i = 0; i < 3; ++i) {
242
+ const origin = [0, 0, 0];
243
+ origin[i] = toolCenter[i];
244
+ const plane = vtkPlane.newInstance({
245
+ origin,
246
+ normal: normals[i],
247
+ });
248
+ this.originalClippingPlanes[planeIndices[i]].origin = plane.getOrigin();
249
+ this.sphereStates[sphereIndices[i]].point[i] = plane.getOrigin()[i];
250
+ this.sphereStates[sphereIndices[i]].sphereSource.setCenter(...this.sphereStates[sphereIndices[i]].point);
251
+ this.sphereStates[sphereIndices[i]].sphereSource.modified();
252
+ const otherSphere = this.sphereStates.find((s, idx) => s.axis === axes[i] && idx !== sphereIndices[i]);
253
+ const newCenter = (otherSphere.point[i] + plane.getOrigin()[i]) / 2;
254
+ this.sphereStates.forEach((state) => {
255
+ if (!state.isCorner &&
256
+ state.axis !== axes[i] &&
257
+ !evt.detail.viewportOrientation.includes(orientationAxes[i])) {
258
+ state.point[i] = newCenter;
259
+ state.sphereSource.setCenter(state.point);
260
+ state.sphereActor.getProperty().setColor(state.color);
261
+ state.sphereSource.modified();
262
+ }
263
+ });
264
+ const volumeActor = viewport.getDefaultActor()?.actor;
265
+ if (volumeActor) {
266
+ const mapper = volumeActor.getMapper();
267
+ const clippingPlanes = mapper.getClippingPlanes();
268
+ if (clippingPlanes) {
269
+ clippingPlanes[planeIndices[i]].setOrigin(plane.getOrigin());
270
+ }
271
+ }
272
+ }
273
+ viewport.render();
274
+ }
275
+ };
276
+ this._getViewportsInfo = () => {
277
+ const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
278
+ return viewports;
279
+ };
280
+ this._initialize3DViewports = (viewportsInfo) => {
281
+ if (!viewportsInfo || !viewportsInfo.length || !viewportsInfo[0]) {
282
+ console.warn('VolumeCroppingTool: No viewportsInfo available for initialization of volumecroppingtool.');
283
+ return;
284
+ }
285
+ const viewport = this._getViewport();
286
+ const volumeActors = viewport.getActors();
287
+ if (!volumeActors || volumeActors.length === 0) {
288
+ console.warn('VolumeCroppingTool: No volume actors found in the viewport.');
289
+ return;
290
+ }
291
+ const imageData = volumeActors[0].actor.getMapper().getInputData();
292
+ if (!imageData) {
293
+ console.warn('VolumeCroppingTool: No image data found for volume actor.');
294
+ return;
295
+ }
296
+ this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
297
+ const worldBounds = imageData.getBounds();
298
+ const cropFactor = this.configuration.initialCropFactor || 0.1;
299
+ const xRange = worldBounds[1] - worldBounds[0];
300
+ const yRange = worldBounds[3] - worldBounds[2];
301
+ const zRange = worldBounds[5] - worldBounds[4];
302
+ const xMin = worldBounds[0] + cropFactor * xRange;
303
+ const xMax = worldBounds[1] - cropFactor * xRange;
304
+ const yMin = worldBounds[2] + cropFactor * yRange;
305
+ const yMax = worldBounds[3] - cropFactor * yRange;
306
+ const zMin = worldBounds[4] + cropFactor * zRange;
307
+ const zMax = worldBounds[5] - cropFactor * zRange;
308
+ const planes = [];
309
+ const planeXmin = vtkPlane.newInstance({
310
+ origin: [xMin, 0, 0],
311
+ normal: [1, 0, 0],
312
+ });
313
+ const planeXmax = vtkPlane.newInstance({
314
+ origin: [xMax, 0, 0],
315
+ normal: [-1, 0, 0],
316
+ });
317
+ const planeYmin = vtkPlane.newInstance({
318
+ origin: [0, yMin, 0],
319
+ normal: [0, 1, 0],
320
+ });
321
+ const planeYmax = vtkPlane.newInstance({
322
+ origin: [0, yMax, 0],
323
+ normal: [0, -1, 0],
324
+ });
325
+ const planeZmin = vtkPlane.newInstance({
326
+ origin: [0, 0, zMin],
327
+ normal: [0, 0, 1],
328
+ });
329
+ const planeZmax = vtkPlane.newInstance({
330
+ origin: [0, 0, zMax],
331
+ normal: [0, 0, -1],
332
+ });
333
+ const mapper = viewport
334
+ .getDefaultActor()
335
+ .actor.getMapper();
336
+ planes.push(planeXmin);
337
+ planes.push(planeXmax);
338
+ planes.push(planeYmin);
339
+ planes.push(planeYmax);
340
+ planes.push(planeZmin);
341
+ planes.push(planeZmax);
342
+ const originalPlanes = planes.map((plane) => ({
343
+ origin: [...plane.getOrigin()],
344
+ normal: [...plane.getNormal()],
345
+ }));
346
+ this.originalClippingPlanes = originalPlanes;
347
+ const sphereXminPoint = [xMin, (yMax + yMin) / 2, (zMax + zMin) / 2];
348
+ const sphereXmaxPoint = [xMax, (yMax + yMin) / 2, (zMax + zMin) / 2];
349
+ const sphereYminPoint = [(xMax + xMin) / 2, yMin, (zMax + zMin) / 2];
350
+ const sphereYmaxPoint = [(xMax + xMin) / 2, yMax, (zMax + zMin) / 2];
351
+ const sphereZminPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMin];
352
+ const sphereZmaxPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMax];
353
+ const adaptiveRadius = this._calculateAdaptiveSphereRadius(Math.sqrt(xRange * xRange + yRange * yRange + zRange * zRange));
354
+ this._addSphere(viewport, sphereXminPoint, 'x', 'min', null, adaptiveRadius);
355
+ this._addSphere(viewport, sphereXmaxPoint, 'x', 'max', null, adaptiveRadius);
356
+ this._addSphere(viewport, sphereYminPoint, 'y', 'min', null, adaptiveRadius);
357
+ this._addSphere(viewport, sphereYmaxPoint, 'y', 'max', null, adaptiveRadius);
358
+ this._addSphere(viewport, sphereZminPoint, 'z', 'min', null, adaptiveRadius);
359
+ this._addSphere(viewport, sphereZmaxPoint, 'z', 'max', null, adaptiveRadius);
360
+ const corners = [
361
+ [xMin, yMin, zMin],
362
+ [xMin, yMin, zMax],
363
+ [xMin, yMax, zMin],
364
+ [xMin, yMax, zMax],
365
+ [xMax, yMin, zMin],
366
+ [xMax, yMin, zMax],
367
+ [xMax, yMax, zMin],
368
+ [xMax, yMax, zMax],
369
+ ];
370
+ const cornerKeys = [
371
+ 'XMIN_YMIN_ZMIN',
372
+ 'XMIN_YMIN_ZMAX',
373
+ 'XMIN_YMAX_ZMIN',
374
+ 'XMIN_YMAX_ZMAX',
375
+ 'XMAX_YMIN_ZMIN',
376
+ 'XMAX_YMIN_ZMAX',
377
+ 'XMAX_YMAX_ZMIN',
378
+ 'XMAX_YMAX_ZMAX',
379
+ ];
380
+ for (let i = 0; i < corners.length; i++) {
381
+ this._addSphere(viewport, corners[i], 'corner', null, cornerKeys[i], adaptiveRadius);
382
+ }
383
+ const edgeCornerPairs = [
384
+ ['XMIN_YMIN_ZMIN', 'XMAX_YMIN_ZMIN'],
385
+ ['XMIN_YMIN_ZMAX', 'XMAX_YMIN_ZMAX'],
386
+ ['XMIN_YMAX_ZMIN', 'XMAX_YMAX_ZMIN'],
387
+ ['XMIN_YMAX_ZMAX', 'XMAX_YMAX_ZMAX'],
388
+ ['XMIN_YMIN_ZMIN', 'XMIN_YMAX_ZMIN'],
389
+ ['XMIN_YMIN_ZMAX', 'XMIN_YMAX_ZMAX'],
390
+ ['XMAX_YMIN_ZMIN', 'XMAX_YMAX_ZMIN'],
391
+ ['XMAX_YMIN_ZMAX', 'XMAX_YMAX_ZMAX'],
392
+ ['XMIN_YMIN_ZMIN', 'XMIN_YMIN_ZMAX'],
393
+ ['XMIN_YMAX_ZMIN', 'XMIN_YMAX_ZMAX'],
394
+ ['XMAX_YMIN_ZMIN', 'XMAX_YMIN_ZMAX'],
395
+ ['XMAX_YMAX_ZMIN', 'XMAX_YMAX_ZMAX'],
396
+ ];
397
+ edgeCornerPairs.forEach(([key1, key2], i) => {
398
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
399
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
400
+ if (state1 && state2) {
401
+ const uid = `edge_${key1}_${key2}`;
402
+ const { actor, source } = this._addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid);
403
+ this.edgeLines[uid] = { actor, source, key1, key2 };
404
+ }
405
+ });
406
+ mapper.addClippingPlane(planeXmin);
407
+ mapper.addClippingPlane(planeXmax);
408
+ mapper.addClippingPlane(planeYmin);
409
+ mapper.addClippingPlane(planeYmax);
410
+ mapper.addClippingPlane(planeZmin);
411
+ mapper.addClippingPlane(planeZmax);
412
+ eventTarget.addEventListener(Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, (evt) => {
413
+ this._onControlToolChange(evt);
414
+ });
415
+ viewport.render();
416
+ };
417
+ this._getViewportAndWorldCoords = (evt) => {
418
+ const viewport = this._getViewport();
419
+ const x = evt.detail.currentPoints.canvas[0];
420
+ const y = evt.detail.currentPoints.canvas[1];
421
+ const world = viewport.canvasToWorld([x, y]);
422
+ return { viewport, world };
423
+ };
424
+ this._getViewport = () => {
425
+ const [viewport3D] = this._getViewportsInfo();
426
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
427
+ return renderingEngine.getViewport(viewport3D.viewportId);
428
+ };
429
+ this._handleCornerSphereMovement = (sphereState, world, viewport) => {
430
+ const newCorner = this._calculateNewCornerPosition(world);
431
+ this._updateSpherePosition(sphereState, newCorner);
432
+ const axisFlags = this._parseCornerKey(sphereState.uid);
433
+ this._updateRelatedCorners(sphereState, newCorner, axisFlags);
434
+ this._updateAfterCornerMovement(viewport);
435
+ };
436
+ this._handleFaceSphereMovement = (sphereState, world, viewport) => {
437
+ const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
438
+ let newValue = world[axisIdx];
439
+ if (this.faceDragOffset !== null) {
440
+ newValue += this.faceDragOffset;
441
+ }
442
+ sphereState.point[axisIdx] = newValue;
443
+ sphereState.sphereSource.setCenter(...sphereState.point);
444
+ sphereState.sphereSource.modified();
445
+ this._updateAfterFaceMovement(viewport);
446
+ };
447
+ this._calculateNewCornerPosition = (world) => {
448
+ let newCorner = [world[0], world[1], world[2]];
449
+ if (this.cornerDragOffset) {
450
+ newCorner = [
451
+ world[0] + this.cornerDragOffset[0],
452
+ world[1] + this.cornerDragOffset[1],
453
+ world[2] + this.cornerDragOffset[2],
454
+ ];
455
+ }
456
+ return newCorner;
457
+ };
458
+ this._parseCornerKey = (uid) => {
459
+ const cornerKey = uid.replace('corner_', '');
460
+ return {
461
+ isXMin: cornerKey.includes('XMIN'),
462
+ isXMax: cornerKey.includes('XMAX'),
463
+ isYMin: cornerKey.includes('YMIN'),
464
+ isYMax: cornerKey.includes('YMAX'),
465
+ isZMin: cornerKey.includes('ZMIN'),
466
+ isZMax: cornerKey.includes('ZMAX'),
467
+ };
468
+ };
469
+ this._updateSpherePosition = (sphereState, newPosition) => {
470
+ sphereState.point = newPosition;
471
+ sphereState.sphereSource.setCenter(...newPosition);
472
+ sphereState.sphereSource.modified();
473
+ };
474
+ this._updateRelatedCorners = (draggedSphere, newCorner, axisFlags) => {
475
+ this.sphereStates.forEach((state) => {
476
+ if (!state.isCorner || state === draggedSphere) {
477
+ return;
478
+ }
479
+ const key = state.uid.replace('corner_', '');
480
+ const shouldUpdate = this._shouldUpdateCorner(key, axisFlags);
481
+ if (shouldUpdate) {
482
+ this._updateCornerCoordinates(state, newCorner, key, axisFlags);
483
+ }
484
+ });
485
+ };
486
+ this._shouldUpdateCorner = (cornerKey, axisFlags) => {
487
+ return ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
488
+ (axisFlags.isXMax && cornerKey.includes('XMAX')) ||
489
+ (axisFlags.isYMin && cornerKey.includes('YMIN')) ||
490
+ (axisFlags.isYMax && cornerKey.includes('YMAX')) ||
491
+ (axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
492
+ (axisFlags.isZMax && cornerKey.includes('ZMAX')));
493
+ };
494
+ this._updateCornerCoordinates = (state, newCorner, cornerKey, axisFlags) => {
495
+ if ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
496
+ (axisFlags.isXMax && cornerKey.includes('XMAX'))) {
497
+ state.point[0] = newCorner[0];
498
+ }
499
+ if ((axisFlags.isYMin && cornerKey.includes('YMIN')) ||
500
+ (axisFlags.isYMax && cornerKey.includes('YMAX'))) {
501
+ state.point[1] = newCorner[1];
502
+ }
503
+ if ((axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
504
+ (axisFlags.isZMax && cornerKey.includes('ZMAX'))) {
505
+ state.point[2] = newCorner[2];
506
+ }
507
+ state.sphereSource.setCenter(...state.point);
508
+ state.sphereSource.modified();
509
+ };
510
+ this._updateAfterCornerMovement = (viewport) => {
511
+ this._updateFaceSpheresFromCorners();
512
+ this._updateCornerSpheres();
513
+ this._updateClippingPlanesFromFaceSpheres(viewport);
514
+ };
515
+ this._updateAfterFaceMovement = (viewport) => {
516
+ this._updateCornerSpheresFromFaces();
517
+ this._updateClippingPlanesFromFaceSpheres(viewport);
518
+ };
519
+ this._triggerToolChangedEvent = (sphereState) => {
520
+ triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
521
+ toolCenter: sphereState.point,
522
+ axis: sphereState.isCorner ? 'corner' : sphereState.axis,
523
+ draggingSphereIndex: this.draggingSphereIndex,
524
+ seriesInstanceUID: this.seriesInstanceUID,
525
+ });
526
+ };
527
+ this._onNewVolume = () => {
528
+ const viewportsInfo = this._getViewportsInfo();
529
+ this.originalClippingPlanes = [];
530
+ this.sphereStates = [];
531
+ this.edgeLines = {};
532
+ this._initialize3DViewports(viewportsInfo);
533
+ };
534
+ this._rotateCamera = (viewport, centerWorld, axis, angle) => {
535
+ const vtkCamera = viewport.getVtkActiveCamera();
536
+ const viewUp = vtkCamera.getViewUp();
537
+ const focalPoint = vtkCamera.getFocalPoint();
538
+ const position = vtkCamera.getPosition();
539
+ const newPosition = [0, 0, 0];
540
+ const newFocalPoint = [0, 0, 0];
541
+ const newViewUp = [0, 0, 0];
542
+ const transform = mat4.identity(new Float32Array(16));
543
+ mat4.translate(transform, transform, centerWorld);
544
+ mat4.rotate(transform, transform, angle, axis);
545
+ mat4.translate(transform, transform, [
546
+ -centerWorld[0],
547
+ -centerWorld[1],
548
+ -centerWorld[2],
549
+ ]);
550
+ vec3.transformMat4(newPosition, position, transform);
551
+ vec3.transformMat4(newFocalPoint, focalPoint, transform);
552
+ mat4.identity(transform);
553
+ mat4.rotate(transform, transform, angle, axis);
554
+ vec3.transformMat4(newViewUp, viewUp, transform);
555
+ viewport.setCamera({
556
+ position: newPosition,
557
+ viewUp: newViewUp,
558
+ focalPoint: newFocalPoint,
559
+ });
560
+ };
561
+ this.touchDragCallback = this._dragCallback.bind(this);
562
+ this.mouseDragCallback = this._dragCallback.bind(this);
563
+ }
564
+ onSetToolActive() {
565
+ if (this.sphereStates && this.sphereStates.length > 0) {
566
+ if (this.configuration.showHandles) {
567
+ this.setHandlesVisible(false);
568
+ this.setClippingPlanesVisible(false);
569
+ }
570
+ else {
571
+ this.setHandlesVisible(true);
572
+ this.setClippingPlanesVisible(true);
573
+ }
574
+ }
575
+ else {
576
+ const viewportsInfo = this._getViewportsInfo();
577
+ const subscribeToElementResize = () => {
578
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
579
+ if (!this._resizeObservers.has(viewportId)) {
580
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId) || { viewport: null };
581
+ if (!viewport) {
582
+ return;
583
+ }
584
+ const { element } = viewport;
585
+ const resizeObserver = new ResizeObserver(() => {
586
+ const element = getEnabledElementByIds(viewportId, renderingEngineId);
587
+ if (!element) {
588
+ return;
589
+ }
590
+ const { viewport } = element;
591
+ const viewPresentation = viewport.getViewPresentation();
592
+ viewport.resetCamera();
593
+ viewport.setViewPresentation(viewPresentation);
594
+ viewport.render();
595
+ });
596
+ resizeObserver.observe(element);
597
+ this._resizeObservers.set(viewportId, resizeObserver);
598
+ }
599
+ });
600
+ };
601
+ subscribeToElementResize();
602
+ this._viewportAddedListener = (evt) => {
603
+ if (evt.detail.toolGroupId === this.toolGroupId) {
604
+ subscribeToElementResize();
605
+ }
606
+ };
607
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
608
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
609
+ this._subscribeToViewportNewVolumeSet(viewportsInfo);
610
+ this._initialize3DViewports(viewportsInfo);
611
+ if (this.sphereStates && this.sphereStates.length > 0) {
612
+ this.setHandlesVisible(true);
613
+ }
614
+ else {
615
+ this.originalClippingPlanes = [];
616
+ this._initialize3DViewports(viewportsInfo);
617
+ }
618
+ }
619
+ }
620
+ onSetToolDisabled() {
621
+ this._resizeObservers.forEach((resizeObserver, viewportId) => {
622
+ resizeObserver.disconnect();
623
+ this._resizeObservers.delete(viewportId);
624
+ });
625
+ if (this._viewportAddedListener) {
626
+ eventTarget.removeEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
627
+ this._viewportAddedListener = null;
628
+ }
629
+ const viewportsInfo = this._getViewportsInfo();
630
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
631
+ }
632
+ setHandlesVisible(visible) {
633
+ this.configuration.showHandles = visible;
634
+ if (visible) {
635
+ this.sphereStates[SPHEREINDEX.XMIN].point[0] =
636
+ this.originalClippingPlanes[PLANEINDEX.XMIN].origin[0];
637
+ this.sphereStates[SPHEREINDEX.XMAX].point[0] =
638
+ this.originalClippingPlanes[PLANEINDEX.XMAX].origin[0];
639
+ this.sphereStates[SPHEREINDEX.YMIN].point[1] =
640
+ this.originalClippingPlanes[PLANEINDEX.YMIN].origin[1];
641
+ this.sphereStates[SPHEREINDEX.YMAX].point[1] =
642
+ this.originalClippingPlanes[PLANEINDEX.YMAX].origin[1];
643
+ this.sphereStates[SPHEREINDEX.ZMIN].point[2] =
644
+ this.originalClippingPlanes[PLANEINDEX.ZMIN].origin[2];
645
+ this.sphereStates[SPHEREINDEX.ZMAX].point[2] =
646
+ this.originalClippingPlanes[PLANEINDEX.ZMAX].origin[2];
647
+ [
648
+ SPHEREINDEX.XMIN,
649
+ SPHEREINDEX.XMAX,
650
+ SPHEREINDEX.YMIN,
651
+ SPHEREINDEX.YMAX,
652
+ SPHEREINDEX.ZMIN,
653
+ SPHEREINDEX.ZMAX,
654
+ ].forEach((idx) => {
655
+ const s = this.sphereStates[idx];
656
+ s.sphereSource.setCenter(...s.point);
657
+ s.sphereSource.modified();
658
+ });
659
+ this._updateCornerSpheres();
660
+ }
661
+ this._updateHandlesVisibility();
662
+ const viewportsInfo = this._getViewportsInfo();
663
+ const [viewport3D] = viewportsInfo;
664
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
665
+ const viewport = renderingEngine.getViewport(viewport3D.viewportId);
666
+ viewport.render();
667
+ }
668
+ getHandlesVisible() {
669
+ return this.configuration.showHandles;
670
+ }
671
+ getClippingPlanesVisible() {
672
+ return this.configuration.showClippingPlanes;
673
+ }
674
+ setClippingPlanesVisible(visible) {
675
+ this.configuration.showClippingPlanes = visible;
676
+ const viewport = this._getViewport();
677
+ this._updateClippingPlanes(viewport);
678
+ viewport.render();
679
+ }
680
+ _dragCallback(evt) {
681
+ const { element, currentPoints, lastPoints } = evt.detail;
682
+ if (this.draggingSphereIndex !== null) {
683
+ this._onMouseMoveSphere(evt);
684
+ }
685
+ else {
686
+ const currentPointsCanvas = currentPoints.canvas;
687
+ const lastPointsCanvas = lastPoints.canvas;
688
+ const { rotateIncrementDegrees } = this.configuration;
689
+ const enabledElement = getEnabledElement(element);
690
+ const { viewport } = enabledElement;
691
+ const camera = viewport.getCamera();
692
+ const width = element.clientWidth;
693
+ const height = element.clientHeight;
694
+ const normalizedPosition = [
695
+ currentPointsCanvas[0] / width,
696
+ currentPointsCanvas[1] / height,
697
+ ];
698
+ const normalizedPreviousPosition = [
699
+ lastPointsCanvas[0] / width,
700
+ lastPointsCanvas[1] / height,
701
+ ];
702
+ const center = [width * 0.5, height * 0.5];
703
+ const centerWorld = viewport.canvasToWorld(center);
704
+ const normalizedCenter = [0.5, 0.5];
705
+ const radsq = (1.0 + Math.abs(normalizedCenter[0])) ** 2.0;
706
+ const op = [normalizedPreviousPosition[0], 0, 0];
707
+ const oe = [normalizedPosition[0], 0, 0];
708
+ const opsq = op[0] ** 2;
709
+ const oesq = oe[0] ** 2;
710
+ const lop = opsq > radsq ? 0 : Math.sqrt(radsq - opsq);
711
+ const loe = oesq > radsq ? 0 : Math.sqrt(radsq - oesq);
712
+ const nop = [op[0], 0, lop];
713
+ vtkMath.normalize(nop);
714
+ const noe = [oe[0], 0, loe];
715
+ vtkMath.normalize(noe);
716
+ const dot = vtkMath.dot(nop, noe);
717
+ if (Math.abs(dot) > 0.0001) {
718
+ const angleX = -2 *
719
+ Math.acos(vtkMath.clampValue(dot, -1.0, 1.0)) *
720
+ Math.sign(normalizedPosition[0] - normalizedPreviousPosition[0]) *
721
+ rotateIncrementDegrees;
722
+ const upVec = camera.viewUp;
723
+ const atV = camera.viewPlaneNormal;
724
+ const rightV = [0, 0, 0];
725
+ const forwardV = [0, 0, 0];
726
+ vtkMath.cross(upVec, atV, rightV);
727
+ vtkMath.normalize(rightV);
728
+ vtkMath.cross(atV, rightV, forwardV);
729
+ vtkMath.normalize(forwardV);
730
+ vtkMath.normalize(upVec);
731
+ this._rotateCamera(viewport, centerWorld, forwardV, angleX);
732
+ const angleY = (normalizedPreviousPosition[1] - normalizedPosition[1]) *
733
+ rotateIncrementDegrees;
734
+ this._rotateCamera(viewport, centerWorld, rightV, angleY);
735
+ }
736
+ viewport.render();
737
+ }
738
+ }
739
+ _updateClippingPlanes(viewport) {
740
+ const actorEntry = viewport.getDefaultActor();
741
+ if (!actorEntry || !actorEntry.actor) {
742
+ if (!viewport._missingActorWarned) {
743
+ console.warn('VolumeCroppingTool._updateClippingPlanes: No default actor found in viewport.');
744
+ viewport._missingActorWarned = true;
745
+ }
746
+ return;
747
+ }
748
+ const actor = actorEntry.actor;
749
+ const mapper = actor.getMapper();
750
+ const matrix = actor.getMatrix();
751
+ if (!this.configuration.showClippingPlanes) {
752
+ mapper.removeAllClippingPlanes();
753
+ return;
754
+ }
755
+ const rot = mat3.create();
756
+ mat3.fromMat4(rot, matrix);
757
+ const normalMatrix = mat3.create();
758
+ mat3.invert(normalMatrix, rot);
759
+ mat3.transpose(normalMatrix, normalMatrix);
760
+ const originalPlanes = this.originalClippingPlanes;
761
+ if (!originalPlanes || !originalPlanes.length) {
762
+ return;
763
+ }
764
+ mapper.removeAllClippingPlanes();
765
+ const transformedOrigins = [];
766
+ const transformedNormals = [];
767
+ for (let i = 0; i < originalPlanes.length; ++i) {
768
+ const plane = originalPlanes[i];
769
+ const oVec = vec3.create();
770
+ vec3.transformMat4(oVec, new Float32Array(plane.origin), matrix);
771
+ const o = [oVec[0], oVec[1], oVec[2]];
772
+ const nVec = vec3.create();
773
+ vec3.transformMat3(nVec, new Float32Array(plane.normal), normalMatrix);
774
+ vec3.normalize(nVec, nVec);
775
+ const n = [nVec[0], nVec[1], nVec[2]];
776
+ transformedOrigins.push(o);
777
+ transformedNormals.push(n);
778
+ }
779
+ for (let i = 0; i < transformedOrigins.length; ++i) {
780
+ const planeInstance = vtkPlane.newInstance({
781
+ origin: transformedOrigins[i],
782
+ normal: transformedNormals[i],
783
+ });
784
+ mapper.addClippingPlane(planeInstance);
785
+ }
786
+ }
787
+ _updateHandlesVisibility() {
788
+ this.sphereStates.forEach((state) => {
789
+ if (state.sphereActor) {
790
+ state.sphereActor.setVisibility(this.configuration.showHandles);
791
+ }
792
+ });
793
+ Object.values(this.edgeLines).forEach(({ actor }) => {
794
+ if (actor) {
795
+ actor.setVisibility(this.configuration.showHandles);
796
+ }
797
+ });
798
+ }
799
+ _addLine3DBetweenPoints(viewport, point1, point2, color = [0.7, 0.7, 0.7], uid = '') {
800
+ if (point1[0] === point2[0] &&
801
+ point1[1] === point2[1] &&
802
+ point1[2] === point2[2]) {
803
+ return { actor: null, source: null };
804
+ }
805
+ const points = vtkPoints.newInstance();
806
+ points.setNumberOfPoints(2);
807
+ points.setPoint(0, point1[0], point1[1], point1[2]);
808
+ points.setPoint(1, point2[0], point2[1], point2[2]);
809
+ const lines = vtkCellArray.newInstance({ values: [2, 0, 1] });
810
+ const polyData = vtkPolyData.newInstance();
811
+ polyData.setPoints(points);
812
+ polyData.setLines(lines);
813
+ const mapper = vtkMapper.newInstance();
814
+ mapper.setInputData(polyData);
815
+ const actor = vtkActor.newInstance();
816
+ actor.setMapper(mapper);
817
+ actor.getProperty().setColor(...color);
818
+ actor.getProperty().setLineWidth(0.5);
819
+ actor.getProperty().setOpacity(1.0);
820
+ actor.getProperty().setInterpolationToFlat();
821
+ actor.getProperty().setAmbient(1.0);
822
+ actor.getProperty().setDiffuse(0.0);
823
+ actor.getProperty().setSpecular(0.0);
824
+ actor.setVisibility(this.configuration.showHandles);
825
+ viewport.addActor({ actor, uid });
826
+ return { actor, source: polyData };
827
+ }
828
+ _addSphere(viewport, point, axis, position, cornerKey = null, adaptiveRadius) {
829
+ const uid = cornerKey ? `corner_${cornerKey}` : `${axis}_${position}`;
830
+ const sphereState = this.sphereStates.find((s) => s.uid === uid);
831
+ if (sphereState) {
832
+ return;
833
+ }
834
+ const sphereSource = vtkSphereSource.newInstance();
835
+ sphereSource.setCenter(point);
836
+ sphereSource.setRadius(adaptiveRadius);
837
+ const sphereMapper = vtkMapper.newInstance();
838
+ sphereMapper.setInputConnection(sphereSource.getOutputPort());
839
+ const sphereActor = vtkActor.newInstance();
840
+ sphereActor.setMapper(sphereMapper);
841
+ let color = [0.0, 1.0, 0.0];
842
+ const sphereColors = this.configuration.sphereColors || {};
843
+ if (cornerKey) {
844
+ color = sphereColors.CORNERS || [0.0, 0.0, 1.0];
845
+ }
846
+ else if (axis === 'z') {
847
+ color = sphereColors.AXIAL || [1.0, 0.0, 0.0];
848
+ }
849
+ else if (axis === 'x') {
850
+ color = sphereColors.SAGITTAL || [1.0, 1.0, 0.0];
851
+ }
852
+ else if (axis === 'y') {
853
+ color = sphereColors.CORONAL || [0.0, 1.0, 0.0];
854
+ }
855
+ const idx = this.sphereStates.findIndex((s) => s.uid === uid);
856
+ if (idx === -1) {
857
+ this.sphereStates.push({
858
+ point: point.slice(),
859
+ axis,
860
+ uid,
861
+ sphereSource,
862
+ sphereActor,
863
+ isCorner: !!cornerKey,
864
+ color,
865
+ });
866
+ }
867
+ else {
868
+ this.sphereStates[idx].point = point.slice();
869
+ this.sphereStates[idx].sphereSource = sphereSource;
870
+ }
871
+ const existingActors = viewport.getActors();
872
+ const existing = existingActors.find((a) => a.uid === uid);
873
+ if (existing) {
874
+ return;
875
+ }
876
+ sphereActor.getProperty().setColor(color);
877
+ sphereActor.setVisibility(this.configuration.showHandles);
878
+ viewport.addActor({ actor: sphereActor, uid: uid });
879
+ }
880
+ _calculateAdaptiveSphereRadius(diagonal) {
881
+ const baseRadius = this.configuration.sphereRadius !== undefined
882
+ ? this.configuration.sphereRadius
883
+ : 8;
884
+ const scaleFactor = this.configuration.sphereRadiusScale || 0.01;
885
+ const adaptiveRadius = diagonal * scaleFactor;
886
+ const minRadius = this.configuration.minSphereRadius || 2;
887
+ const maxRadius = this.configuration.maxSphereRadius || 50;
888
+ return Math.max(minRadius, Math.min(maxRadius, adaptiveRadius));
889
+ }
890
+ _updateClippingPlanesFromFaceSpheres(viewport) {
891
+ const mapper = viewport.getDefaultActor().actor.getMapper();
892
+ this.originalClippingPlanes[0].origin = [
893
+ ...this.sphereStates[SPHEREINDEX.XMIN].point,
894
+ ];
895
+ this.originalClippingPlanes[1].origin = [
896
+ ...this.sphereStates[SPHEREINDEX.XMAX].point,
897
+ ];
898
+ this.originalClippingPlanes[2].origin = [
899
+ ...this.sphereStates[SPHEREINDEX.YMIN].point,
900
+ ];
901
+ this.originalClippingPlanes[3].origin = [
902
+ ...this.sphereStates[SPHEREINDEX.YMAX].point,
903
+ ];
904
+ this.originalClippingPlanes[4].origin = [
905
+ ...this.sphereStates[SPHEREINDEX.ZMIN].point,
906
+ ];
907
+ this.originalClippingPlanes[5].origin = [
908
+ ...this.sphereStates[SPHEREINDEX.ZMAX].point,
909
+ ];
910
+ mapper.removeAllClippingPlanes();
911
+ for (let i = 0; i < 6; ++i) {
912
+ const origin = this.originalClippingPlanes[i].origin;
913
+ const normal = this.originalClippingPlanes[i].normal;
914
+ const plane = vtkPlane.newInstance({
915
+ origin,
916
+ normal,
917
+ });
918
+ mapper.addClippingPlane(plane);
919
+ }
920
+ }
921
+ _updateCornerSpheresFromFaces() {
922
+ const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
923
+ const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
924
+ const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
925
+ const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
926
+ const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
927
+ const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
928
+ const corners = [
929
+ { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
930
+ { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
931
+ { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
932
+ { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
933
+ { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
934
+ { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
935
+ { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
936
+ { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
937
+ ];
938
+ for (const corner of corners) {
939
+ const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
940
+ if (state) {
941
+ state.point[0] = corner.pos[0];
942
+ state.point[1] = corner.pos[1];
943
+ state.point[2] = corner.pos[2];
944
+ state.sphereSource.setCenter(...state.point);
945
+ state.sphereSource.modified();
946
+ }
947
+ }
948
+ }
949
+ _updateFaceSpheresFromCorners() {
950
+ const corners = [
951
+ this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMIN].point,
952
+ this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMAX].point,
953
+ this.sphereStates[SPHEREINDEX.XMIN_YMAX_ZMIN].point,
954
+ this.sphereStates[SPHEREINDEX.XMIN_YMAX_ZMAX].point,
955
+ this.sphereStates[SPHEREINDEX.XMAX_YMIN_ZMIN].point,
956
+ this.sphereStates[SPHEREINDEX.XMAX_YMIN_ZMAX].point,
957
+ this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMIN].point,
958
+ this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMAX].point,
959
+ ];
960
+ const xs = corners.map((p) => p[0]);
961
+ const ys = corners.map((p) => p[1]);
962
+ const zs = corners.map((p) => p[2]);
963
+ const xMin = Math.min(...xs), xMax = Math.max(...xs);
964
+ const yMin = Math.min(...ys), yMax = Math.max(...ys);
965
+ const zMin = Math.min(...zs), zMax = Math.max(...zs);
966
+ this.sphereStates[SPHEREINDEX.XMIN].point = [
967
+ xMin,
968
+ (yMin + yMax) / 2,
969
+ (zMin + zMax) / 2,
970
+ ];
971
+ this.sphereStates[SPHEREINDEX.XMAX].point = [
972
+ xMax,
973
+ (yMin + yMax) / 2,
974
+ (zMin + zMax) / 2,
975
+ ];
976
+ this.sphereStates[SPHEREINDEX.YMIN].point = [
977
+ (xMin + xMax) / 2,
978
+ yMin,
979
+ (zMin + zMax) / 2,
980
+ ];
981
+ this.sphereStates[SPHEREINDEX.YMAX].point = [
982
+ (xMin + xMax) / 2,
983
+ yMax,
984
+ (zMin + zMax) / 2,
985
+ ];
986
+ this.sphereStates[SPHEREINDEX.ZMIN].point = [
987
+ (xMin + xMax) / 2,
988
+ (yMin + yMax) / 2,
989
+ zMin,
990
+ ];
991
+ this.sphereStates[SPHEREINDEX.ZMAX].point = [
992
+ (xMin + xMax) / 2,
993
+ (yMin + yMax) / 2,
994
+ zMax,
995
+ ];
996
+ [
997
+ SPHEREINDEX.XMIN,
998
+ SPHEREINDEX.XMAX,
999
+ SPHEREINDEX.YMIN,
1000
+ SPHEREINDEX.YMAX,
1001
+ SPHEREINDEX.ZMIN,
1002
+ SPHEREINDEX.ZMAX,
1003
+ ].forEach((idx) => {
1004
+ const s = this.sphereStates[idx];
1005
+ s.sphereSource.setCenter(...s.point);
1006
+ s.sphereSource.modified();
1007
+ });
1008
+ }
1009
+ _updateCornerSpheres() {
1010
+ const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
1011
+ const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
1012
+ const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
1013
+ const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
1014
+ const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
1015
+ const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
1016
+ const corners = [
1017
+ { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
1018
+ { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
1019
+ { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
1020
+ { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
1021
+ { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
1022
+ { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
1023
+ { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
1024
+ { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
1025
+ ];
1026
+ for (const corner of corners) {
1027
+ const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
1028
+ if (state) {
1029
+ state.point[0] = corner.pos[0];
1030
+ state.point[1] = corner.pos[1];
1031
+ state.point[2] = corner.pos[2];
1032
+ state.sphereSource.setCenter(...state.point);
1033
+ state.sphereSource.modified();
1034
+ }
1035
+ }
1036
+ Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
1037
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
1038
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
1039
+ if (state1 && state2) {
1040
+ const points = source.getPoints();
1041
+ points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
1042
+ points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
1043
+ points.modified();
1044
+ source.modified();
1045
+ }
1046
+ });
1047
+ }
1048
+ _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1049
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
1050
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1051
+ const { element } = viewport;
1052
+ element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1053
+ });
1054
+ }
1055
+ _subscribeToViewportNewVolumeSet(viewports) {
1056
+ viewports.forEach(({ viewportId, renderingEngineId }) => {
1057
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1058
+ const { element } = viewport;
1059
+ element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1060
+ });
1061
+ }
1062
+ }
1063
+ VolumeCroppingTool.toolName = 'VolumeCropping';
1064
+ export default VolumeCroppingTool;