@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.
Files changed (35) hide show
  1. package/dist/esm/tools/VolumeCroppingControlTool.d.ts +35 -10
  2. package/dist/esm/tools/VolumeCroppingControlTool.js +699 -179
  3. package/dist/esm/tools/VolumeCroppingTool.d.ts +32 -32
  4. package/dist/esm/tools/VolumeCroppingTool.js +525 -775
  5. package/dist/esm/utilities/index.d.ts +1 -2
  6. package/dist/esm/utilities/index.js +1 -2
  7. package/dist/esm/version.d.ts +1 -1
  8. package/dist/esm/version.js +1 -1
  9. package/package.json +3 -3
  10. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +0 -7
  11. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +0 -34
  12. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +0 -6
  13. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +0 -7
  14. package/dist/esm/utilities/draw3D/index.d.ts +0 -2
  15. package/dist/esm/utilities/draw3D/index.js +0 -2
  16. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +0 -6
  17. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +0 -37
  18. package/dist/esm/utilities/volumeCropping/constants.d.ts +0 -31
  19. package/dist/esm/utilities/volumeCropping/constants.js +0 -31
  20. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +0 -2
  21. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +0 -6
  22. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +0 -9
  23. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +0 -9
  24. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +0 -5
  25. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +0 -50
  26. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +0 -1
  27. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +0 -13
  28. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +0 -2
  29. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +0 -19
  30. package/dist/esm/utilities/volumeCropping/index.d.ts +0 -9
  31. package/dist/esm/utilities/volumeCropping/index.js +0 -9
  32. package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +0 -8
  33. package/dist/esm/utilities/volumeCropping/parseCornerKey.js +0 -11
  34. package/dist/esm/utilities/volumeCropping/types.d.ts +0 -5
  35. package/dist/esm/utilities/volumeCropping/types.js +0 -0
@@ -1,4 +1,6 @@
1
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';
2
4
  import { mat3, mat4, vec3 } from 'gl-matrix';
3
5
  import vtkMath from '@kitware/vtk.js/Common/Core/Math';
4
6
  import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
@@ -9,8 +11,30 @@ import { BaseTool } from './base';
9
11
  import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, triggerEvent, eventTarget, } from '@cornerstonejs/core';
10
12
  import { getToolGroup } from '../store/ToolGroupManager';
11
13
  import { Events } from '../enums';
12
- import { PLANEINDEX, SPHEREINDEX, NUM_CLIPPING_PLANES, extractVolumeDirectionVectors, parseCornerKey, copyClippingPlanes, } from '../utilities/volumeCropping';
13
- import { addLine3DBetweenPoints, calculateAdaptiveSphereRadius, } from '../utilities/draw3D';
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
+ };
14
38
  class VolumeCroppingTool extends BaseTool {
15
39
  constructor(toolProps = {}, defaultToolProps = {
16
40
  configuration: {
@@ -32,7 +56,6 @@ class VolumeCroppingTool extends BaseTool {
32
56
  grabSpherePixelDistance: 20,
33
57
  rotateIncrementDegrees: 2,
34
58
  rotateSampleDistanceFactor: 2,
35
- rotateClippingPlanesIncrementDegrees: 5,
36
59
  },
37
60
  }) {
38
61
  super(toolProps, defaultToolProps);
@@ -40,15 +63,16 @@ class VolumeCroppingTool extends BaseTool {
40
63
  this._hasResolutionChanged = false;
41
64
  this.originalClippingPlanes = [];
42
65
  this.draggingSphereIndex = null;
43
- this.rotatePlanesOnDrag = false;
66
+ this.toolCenter = [0, 0, 0];
44
67
  this.cornerDragOffset = null;
45
68
  this.faceDragOffset = null;
46
- this.volumeDirectionVectors = null;
47
69
  this.sphereStates = [];
48
70
  this.edgeLines = {};
49
71
  this.onSetToolConfiguration = () => {
72
+ console.debug('Setting tool settoolconfiguration : volumeCropping');
50
73
  };
51
74
  this.onSetToolEnabled = () => {
75
+ console.debug('Setting tool enabled: volumeCropping');
52
76
  };
53
77
  this.onCameraModified = (evt) => {
54
78
  const { element } = evt.currentTarget
@@ -91,16 +115,9 @@ class VolumeCroppingTool extends BaseTool {
91
115
  this.faceDragOffset = null;
92
116
  }
93
117
  else {
94
- const directionVector = this._getDirectionVectorForAxis(sphereState.axis);
95
- const delta = [
96
- sphereState.point[0] - mouseWorld[0],
97
- sphereState.point[1] - mouseWorld[1],
98
- sphereState.point[2] - mouseWorld[2],
99
- ];
118
+ const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
100
119
  this.faceDragOffset =
101
- delta[0] * directionVector[0] +
102
- delta[1] * directionVector[1] +
103
- delta[2] * directionVector[2];
120
+ sphereState.point[axisIdx] - mouseWorld[axisIdx];
104
121
  this.cornerDragOffset = null;
105
122
  }
106
123
  return true;
@@ -155,143 +172,118 @@ class VolumeCroppingTool extends BaseTool {
155
172
  return false;
156
173
  }
157
174
  if (sphereState.isCorner) {
158
- const newCorner = this.cornerDragOffset
159
- ? vec3.add([0, 0, 0], world, this.cornerDragOffset)
160
- : world;
161
- const oldCorner = sphereState.point;
162
- const axisFlags = parseCornerKey(sphereState.uid);
163
- const { xDir, yDir, zDir } = this._getDirectionVectors();
164
- if (!xDir || !yDir || !zDir)
165
- return false;
166
- const delta = [
167
- newCorner[0] - oldCorner[0],
168
- newCorner[1] - oldCorner[1],
169
- newCorner[2] - oldCorner[2],
170
- ];
171
- const deltaX = delta[0] * xDir[0] + delta[1] * xDir[1] + delta[2] * xDir[2];
172
- const deltaY = delta[0] * yDir[0] + delta[1] * yDir[1] + delta[2] * yDir[2];
173
- const deltaZ = delta[0] * zDir[0] + delta[1] * zDir[1] + delta[2] * zDir[2];
174
- if (axisFlags.isXMin) {
175
- const faceXMin = this.sphereStates[SPHEREINDEX.XMIN];
176
- const newPoint = [
177
- faceXMin.point[0] + deltaX * xDir[0],
178
- faceXMin.point[1] + deltaX * xDir[1],
179
- faceXMin.point[2] + deltaX * xDir[2],
180
- ];
181
- this._updateSpherePosition(SPHEREINDEX.XMIN, newPoint);
182
- }
183
- else if (axisFlags.isXMax) {
184
- const faceXMax = this.sphereStates[SPHEREINDEX.XMAX];
185
- const newPoint = [
186
- faceXMax.point[0] + deltaX * xDir[0],
187
- faceXMax.point[1] + deltaX * xDir[1],
188
- faceXMax.point[2] + deltaX * xDir[2],
189
- ];
190
- this._updateSpherePosition(SPHEREINDEX.XMAX, newPoint);
191
- }
192
- if (axisFlags.isYMin) {
193
- const faceYMin = this.sphereStates[SPHEREINDEX.YMIN];
194
- const newPoint = [
195
- faceYMin.point[0] + deltaY * yDir[0],
196
- faceYMin.point[1] + deltaY * yDir[1],
197
- faceYMin.point[2] + deltaY * yDir[2],
198
- ];
199
- this._updateSpherePosition(SPHEREINDEX.YMIN, newPoint);
200
- }
201
- else if (axisFlags.isYMax) {
202
- const faceYMax = this.sphereStates[SPHEREINDEX.YMAX];
203
- const newPoint = [
204
- faceYMax.point[0] + deltaY * yDir[0],
205
- faceYMax.point[1] + deltaY * yDir[1],
206
- faceYMax.point[2] + deltaY * yDir[2],
207
- ];
208
- this._updateSpherePosition(SPHEREINDEX.YMAX, newPoint);
209
- }
210
- if (axisFlags.isZMin) {
211
- const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN];
212
- const newPoint = [
213
- faceZMin.point[0] + deltaZ * zDir[0],
214
- faceZMin.point[1] + deltaZ * zDir[1],
215
- faceZMin.point[2] + deltaZ * zDir[2],
216
- ];
217
- this._updateSpherePosition(SPHEREINDEX.ZMIN, newPoint);
218
- }
219
- else if (axisFlags.isZMax) {
220
- const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX];
221
- const newPoint = [
222
- faceZMax.point[0] + deltaZ * zDir[0],
223
- faceZMax.point[1] + deltaZ * zDir[1],
224
- faceZMax.point[2] + deltaZ * zDir[2],
225
- ];
226
- this._updateSpherePosition(SPHEREINDEX.ZMAX, newPoint);
227
- }
228
- this._updateCornerSpheres();
175
+ const newCorner = this._calculateNewCornerPosition(world);
176
+ this._updateSpherePosition(sphereState, newCorner);
177
+ const axisFlags = this._parseCornerKey(sphereState.uid);
178
+ this._updateRelatedCorners(sphereState, newCorner, axisFlags);
229
179
  this._updateFaceSpheresFromCorners();
180
+ this._updateCornerSpheres();
230
181
  }
231
182
  else {
232
- const directionVector = this._getDirectionVectorForAxis(sphereState.axis);
233
- const delta = [
234
- world[0] - sphereState.point[0],
235
- world[1] - sphereState.point[1],
236
- world[2] - sphereState.point[2],
237
- ];
238
- const distanceAlongAxis = delta[0] * directionVector[0] +
239
- delta[1] * directionVector[1] +
240
- delta[2] * directionVector[2];
241
- const adjustedDistance = this.faceDragOffset !== null
242
- ? distanceAlongAxis + this.faceDragOffset
243
- : distanceAlongAxis;
244
- const newPoint = [
245
- sphereState.point[0] + adjustedDistance * directionVector[0],
246
- sphereState.point[1] + adjustedDistance * directionVector[1],
247
- sphereState.point[2] + adjustedDistance * directionVector[2],
248
- ];
249
- this._updateSpherePosition(this.draggingSphereIndex, newPoint);
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();
250
191
  this._updateCornerSpheresFromFaces();
251
192
  this._updateFaceSpheresFromCorners();
252
193
  this._updateCornerSpheres();
253
194
  }
254
195
  this._updateClippingPlanesFromFaceSpheres(viewport);
255
196
  viewport.render();
256
- this._notifyClippingPlanesChanged();
197
+ this._triggerToolChangedEvent(sphereState);
257
198
  return true;
258
199
  };
259
200
  this._onControlToolChange = (evt) => {
260
201
  const viewport = this._getViewport();
261
- if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
262
- return;
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
+ });
263
209
  }
264
- if (evt.detail.clippingPlanes &&
265
- evt.detail.clippingPlanes.length >= NUM_CLIPPING_PLANES) {
266
- this.originalClippingPlanes = copyClippingPlanes(evt.detail.clippingPlanes);
267
- this._updateFaceSpheresFromClippingPlanes();
268
- this._updateCornerSpheresFromFaces();
269
- this._updateFaceSpheresFromCorners();
270
- this._updateCornerSpheres();
271
- const mapper = this._getVolumeMapper(viewport);
272
- if (mapper) {
273
- this._applyClippingPlanesToMapper(mapper);
210
+ else {
211
+ if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
212
+ return;
274
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
+ this._updateCornerSpheres();
275
274
  viewport.render();
276
- this._notifyClippingPlanesChanged(viewport);
277
- }
278
- else {
279
- this._notifyClippingPlanesChanged(viewport);
280
275
  }
281
276
  };
282
277
  this._getViewportsInfo = () => {
283
- const toolGroup = getToolGroup(this.toolGroupId);
284
- return toolGroup?.viewportsInfo || [];
278
+ const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
279
+ return viewports;
285
280
  };
286
281
  this._initialize3DViewports = (viewportsInfo) => {
287
- if (!viewportsInfo?.length || !viewportsInfo[0]) {
282
+ if (!viewportsInfo || !viewportsInfo.length || !viewportsInfo[0]) {
288
283
  console.warn('VolumeCroppingTool: No viewportsInfo available for initialization of volumecroppingtool.');
289
284
  return;
290
285
  }
291
286
  const viewport = this._getViewport();
292
- if (!viewport) {
293
- return;
294
- }
295
287
  const volumeActors = viewport.getActors();
296
288
  if (!volumeActors || volumeActors.length === 0) {
297
289
  console.warn('VolumeCroppingTool: No volume actors found in the viewport.');
@@ -303,85 +295,78 @@ class VolumeCroppingTool extends BaseTool {
303
295
  return;
304
296
  }
305
297
  this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
306
- this.volumeDirectionVectors = extractVolumeDirectionVectors(imageData);
307
- const { xDir, yDir, zDir } = this.volumeDirectionVectors;
308
- const dimensions = imageData.getDimensions();
298
+ const worldBounds = imageData.getBounds();
309
299
  const cropFactor = this.configuration.initialCropFactor || 0.1;
310
- const xMin = cropFactor * dimensions[0];
311
- const xMax = (1 - cropFactor) * dimensions[0];
312
- const yMin = cropFactor * dimensions[1];
313
- const yMax = (1 - cropFactor) * dimensions[1];
314
- const zMin = cropFactor * dimensions[2];
315
- const zMax = (1 - cropFactor) * dimensions[2];
316
- const xCenter = (xMin + xMax) / 2;
317
- const yCenter = (yMin + yMax) / 2;
318
- const zCenter = (zMin + zMax) / 2;
319
- const faceXMin = imageData.indexToWorld([xMin, yCenter, zCenter]);
320
- const faceXMax = imageData.indexToWorld([xMax, yCenter, zCenter]);
321
- const faceYMin = imageData.indexToWorld([xCenter, yMin, zCenter]);
322
- const faceYMax = imageData.indexToWorld([xCenter, yMax, zCenter]);
323
- const faceZMin = imageData.indexToWorld([xCenter, yCenter, zMin]);
324
- const faceZMax = imageData.indexToWorld([xCenter, yCenter, zMax]);
325
- const planeXMin = vtkPlane.newInstance({
326
- origin: faceXMin,
327
- normal: xDir,
300
+ const xRange = worldBounds[1] - worldBounds[0];
301
+ const yRange = worldBounds[3] - worldBounds[2];
302
+ const zRange = worldBounds[5] - worldBounds[4];
303
+ const xMin = worldBounds[0] + cropFactor * xRange;
304
+ const xMax = worldBounds[1] - cropFactor * xRange;
305
+ const yMin = worldBounds[2] + cropFactor * yRange;
306
+ const yMax = worldBounds[3] - cropFactor * yRange;
307
+ const zMin = worldBounds[4] + cropFactor * zRange;
308
+ const zMax = worldBounds[5] - cropFactor * zRange;
309
+ const planes = [];
310
+ const planeXmin = vtkPlane.newInstance({
311
+ origin: [xMin, 0, 0],
312
+ normal: [1, 0, 0],
328
313
  });
329
- const planeXMax = vtkPlane.newInstance({
330
- origin: faceXMax,
331
- normal: [-xDir[0], -xDir[1], -xDir[2]],
314
+ const planeXmax = vtkPlane.newInstance({
315
+ origin: [xMax, 0, 0],
316
+ normal: [-1, 0, 0],
332
317
  });
333
- const planeYMin = vtkPlane.newInstance({
334
- origin: faceYMin,
335
- normal: yDir,
318
+ const planeYmin = vtkPlane.newInstance({
319
+ origin: [0, yMin, 0],
320
+ normal: [0, 1, 0],
336
321
  });
337
- const planeYMax = vtkPlane.newInstance({
338
- origin: faceYMax,
339
- normal: [-yDir[0], -yDir[1], -yDir[2]],
322
+ const planeYmax = vtkPlane.newInstance({
323
+ origin: [0, yMax, 0],
324
+ normal: [0, -1, 0],
340
325
  });
341
- const planeZMin = vtkPlane.newInstance({
342
- origin: faceZMin,
343
- normal: zDir,
326
+ const planeZmin = vtkPlane.newInstance({
327
+ origin: [0, 0, zMin],
328
+ normal: [0, 0, 1],
344
329
  });
345
- const planeZMax = vtkPlane.newInstance({
346
- origin: faceZMax,
347
- normal: [-zDir[0], -zDir[1], -zDir[2]],
330
+ const planeZmax = vtkPlane.newInstance({
331
+ origin: [0, 0, zMax],
332
+ normal: [0, 0, -1],
348
333
  });
349
- const planes = [
350
- planeXMin,
351
- planeXMax,
352
- planeYMin,
353
- planeYMax,
354
- planeZMin,
355
- planeZMax,
356
- ];
334
+ const mapper = viewport
335
+ .getDefaultActor()
336
+ .actor.getMapper();
337
+ planes.push(planeXmin);
338
+ planes.push(planeXmax);
339
+ planes.push(planeYmin);
340
+ planes.push(planeYmax);
341
+ planes.push(planeZmin);
342
+ planes.push(planeZmax);
357
343
  const originalPlanes = planes.map((plane) => ({
358
344
  origin: [...plane.getOrigin()],
359
345
  normal: [...plane.getNormal()],
360
346
  }));
361
347
  this.originalClippingPlanes = originalPlanes;
362
- const diag0 = imageData.indexToWorld([0, 0, 0]);
363
- const diag1 = imageData.indexToWorld([
364
- dimensions[0],
365
- dimensions[1],
366
- dimensions[2],
367
- ]);
368
- const diagonal = vec3.distance(diag0, diag1);
369
- const adaptiveRadius = calculateAdaptiveSphereRadius(diagonal, this.configuration);
370
- this._addSphere(viewport, faceXMin, 'x', 'min', null, adaptiveRadius);
371
- this._addSphere(viewport, faceXMax, 'x', 'max', null, adaptiveRadius);
372
- this._addSphere(viewport, faceYMin, 'y', 'min', null, adaptiveRadius);
373
- this._addSphere(viewport, faceYMax, 'y', 'max', null, adaptiveRadius);
374
- this._addSphere(viewport, faceZMin, 'z', 'min', null, adaptiveRadius);
375
- this._addSphere(viewport, faceZMax, 'z', 'max', null, adaptiveRadius);
348
+ const sphereXminPoint = [xMin, (yMax + yMin) / 2, (zMax + zMin) / 2];
349
+ const sphereXmaxPoint = [xMax, (yMax + yMin) / 2, (zMax + zMin) / 2];
350
+ const sphereYminPoint = [(xMax + xMin) / 2, yMin, (zMax + zMin) / 2];
351
+ const sphereYmaxPoint = [(xMax + xMin) / 2, yMax, (zMax + zMin) / 2];
352
+ const sphereZminPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMin];
353
+ const sphereZmaxPoint = [(xMax + xMin) / 2, (yMax + yMin) / 2, zMax];
354
+ const adaptiveRadius = this._calculateAdaptiveSphereRadius(Math.sqrt(xRange * xRange + yRange * yRange + zRange * zRange));
355
+ this._addSphere(viewport, sphereXminPoint, 'x', 'min', null, adaptiveRadius);
356
+ this._addSphere(viewport, sphereXmaxPoint, 'x', 'max', null, adaptiveRadius);
357
+ this._addSphere(viewport, sphereYminPoint, 'y', 'min', null, adaptiveRadius);
358
+ this._addSphere(viewport, sphereYmaxPoint, 'y', 'max', null, adaptiveRadius);
359
+ this._addSphere(viewport, sphereZminPoint, 'z', 'min', null, adaptiveRadius);
360
+ this._addSphere(viewport, sphereZmaxPoint, 'z', 'max', null, adaptiveRadius);
376
361
  const corners = [
377
- imageData.indexToWorld([xMin, yMin, zMin]),
378
- imageData.indexToWorld([xMin, yMin, zMax]),
379
- imageData.indexToWorld([xMin, yMax, zMin]),
380
- imageData.indexToWorld([xMin, yMax, zMax]),
381
- imageData.indexToWorld([xMax, yMin, zMin]),
382
- imageData.indexToWorld([xMax, yMin, zMax]),
383
- imageData.indexToWorld([xMax, yMax, zMin]),
384
- imageData.indexToWorld([xMax, yMax, zMax]),
362
+ [xMin, yMin, zMin],
363
+ [xMin, yMin, zMax],
364
+ [xMin, yMax, zMin],
365
+ [xMin, yMax, zMax],
366
+ [xMax, yMin, zMin],
367
+ [xMax, yMin, zMax],
368
+ [xMax, yMax, zMin],
369
+ [xMax, yMax, zMax],
385
370
  ];
386
371
  const cornerKeys = [
387
372
  'XMIN_YMIN_ZMIN',
@@ -410,24 +395,21 @@ class VolumeCroppingTool extends BaseTool {
410
395
  ['XMAX_YMIN_ZMIN', 'XMAX_YMIN_ZMAX'],
411
396
  ['XMAX_YMAX_ZMIN', 'XMAX_YMAX_ZMAX'],
412
397
  ];
413
- edgeCornerPairs.forEach(([key1, key2]) => {
398
+ edgeCornerPairs.forEach(([key1, key2], i) => {
414
399
  const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
415
400
  const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
416
401
  if (state1 && state2) {
417
402
  const uid = `edge_${key1}_${key2}`;
418
- const { actor, source } = addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid, this.configuration.showHandles);
403
+ const { actor, source } = this._addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid);
419
404
  this.edgeLines[uid] = { actor, source, key1, key2 };
420
405
  }
421
406
  });
422
- const mapper = viewport
423
- .getDefaultActor()
424
- .actor.getMapper();
425
- mapper.addClippingPlane(planeXMin);
426
- mapper.addClippingPlane(planeXMax);
427
- mapper.addClippingPlane(planeYMin);
428
- mapper.addClippingPlane(planeYMax);
429
- mapper.addClippingPlane(planeZMin);
430
- mapper.addClippingPlane(planeZMax);
407
+ mapper.addClippingPlane(planeXmin);
408
+ mapper.addClippingPlane(planeXmax);
409
+ mapper.addClippingPlane(planeYmin);
410
+ mapper.addClippingPlane(planeYmax);
411
+ mapper.addClippingPlane(planeZmin);
412
+ mapper.addClippingPlane(planeZmax);
431
413
  eventTarget.addEventListener(Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, (evt) => {
432
414
  this._onControlToolChange(evt);
433
415
  });
@@ -441,14 +423,107 @@ class VolumeCroppingTool extends BaseTool {
441
423
  return { viewport, world };
442
424
  };
443
425
  this._getViewport = () => {
444
- const viewportsInfo = this._getViewportsInfo();
445
- if (!viewportsInfo || viewportsInfo.length === 0) {
446
- return null;
447
- }
448
- const [viewport3D] = viewportsInfo;
426
+ const [viewport3D] = this._getViewportsInfo();
449
427
  const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
450
- const viewport = renderingEngine?.getViewport(viewport3D.viewportId);
451
- return viewport || null;
428
+ return renderingEngine.getViewport(viewport3D.viewportId);
429
+ };
430
+ this._handleCornerSphereMovement = (sphereState, world, viewport) => {
431
+ const newCorner = this._calculateNewCornerPosition(world);
432
+ this._updateSpherePosition(sphereState, newCorner);
433
+ const axisFlags = this._parseCornerKey(sphereState.uid);
434
+ this._updateRelatedCorners(sphereState, newCorner, axisFlags);
435
+ this._updateAfterCornerMovement(viewport);
436
+ };
437
+ this._handleFaceSphereMovement = (sphereState, world, viewport) => {
438
+ const axisIdx = { x: 0, y: 1, z: 2 }[sphereState.axis];
439
+ let newValue = world[axisIdx];
440
+ if (this.faceDragOffset !== null) {
441
+ newValue += this.faceDragOffset;
442
+ }
443
+ sphereState.point[axisIdx] = newValue;
444
+ sphereState.sphereSource.setCenter(...sphereState.point);
445
+ sphereState.sphereSource.modified();
446
+ this._updateAfterFaceMovement(viewport);
447
+ };
448
+ this._calculateNewCornerPosition = (world) => {
449
+ let newCorner = [world[0], world[1], world[2]];
450
+ if (this.cornerDragOffset) {
451
+ newCorner = [
452
+ world[0] + this.cornerDragOffset[0],
453
+ world[1] + this.cornerDragOffset[1],
454
+ world[2] + this.cornerDragOffset[2],
455
+ ];
456
+ }
457
+ return newCorner;
458
+ };
459
+ this._parseCornerKey = (uid) => {
460
+ const cornerKey = uid.replace('corner_', '');
461
+ return {
462
+ isXMin: cornerKey.includes('XMIN'),
463
+ isXMax: cornerKey.includes('XMAX'),
464
+ isYMin: cornerKey.includes('YMIN'),
465
+ isYMax: cornerKey.includes('YMAX'),
466
+ isZMin: cornerKey.includes('ZMIN'),
467
+ isZMax: cornerKey.includes('ZMAX'),
468
+ };
469
+ };
470
+ this._updateSpherePosition = (sphereState, newPosition) => {
471
+ sphereState.point = newPosition;
472
+ sphereState.sphereSource.setCenter(...newPosition);
473
+ sphereState.sphereSource.modified();
474
+ };
475
+ this._updateRelatedCorners = (draggedSphere, newCorner, axisFlags) => {
476
+ this.sphereStates.forEach((state) => {
477
+ if (!state.isCorner || state === draggedSphere) {
478
+ return;
479
+ }
480
+ const key = state.uid.replace('corner_', '');
481
+ const shouldUpdate = this._shouldUpdateCorner(key, axisFlags);
482
+ if (shouldUpdate) {
483
+ this._updateCornerCoordinates(state, newCorner, key, axisFlags);
484
+ }
485
+ });
486
+ };
487
+ this._shouldUpdateCorner = (cornerKey, axisFlags) => {
488
+ return ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
489
+ (axisFlags.isXMax && cornerKey.includes('XMAX')) ||
490
+ (axisFlags.isYMin && cornerKey.includes('YMIN')) ||
491
+ (axisFlags.isYMax && cornerKey.includes('YMAX')) ||
492
+ (axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
493
+ (axisFlags.isZMax && cornerKey.includes('ZMAX')));
494
+ };
495
+ this._updateCornerCoordinates = (state, newCorner, cornerKey, axisFlags) => {
496
+ if ((axisFlags.isXMin && cornerKey.includes('XMIN')) ||
497
+ (axisFlags.isXMax && cornerKey.includes('XMAX'))) {
498
+ state.point[0] = newCorner[0];
499
+ }
500
+ if ((axisFlags.isYMin && cornerKey.includes('YMIN')) ||
501
+ (axisFlags.isYMax && cornerKey.includes('YMAX'))) {
502
+ state.point[1] = newCorner[1];
503
+ }
504
+ if ((axisFlags.isZMin && cornerKey.includes('ZMIN')) ||
505
+ (axisFlags.isZMax && cornerKey.includes('ZMAX'))) {
506
+ state.point[2] = newCorner[2];
507
+ }
508
+ state.sphereSource.setCenter(...state.point);
509
+ state.sphereSource.modified();
510
+ };
511
+ this._updateAfterCornerMovement = (viewport) => {
512
+ this._updateFaceSpheresFromCorners();
513
+ this._updateCornerSpheres();
514
+ this._updateClippingPlanesFromFaceSpheres(viewport);
515
+ };
516
+ this._updateAfterFaceMovement = (viewport) => {
517
+ this._updateCornerSpheresFromFaces();
518
+ this._updateClippingPlanesFromFaceSpheres(viewport);
519
+ };
520
+ this._triggerToolChangedEvent = (sphereState) => {
521
+ triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
522
+ toolCenter: sphereState.point,
523
+ axis: sphereState.isCorner ? 'corner' : sphereState.axis,
524
+ draggingSphereIndex: this.draggingSphereIndex,
525
+ seriesInstanceUID: this.seriesInstanceUID,
526
+ });
452
527
  };
453
528
  this._onNewVolume = () => {
454
529
  const viewportsInfo = this._getViewportsInfo();
@@ -457,207 +532,6 @@ class VolumeCroppingTool extends BaseTool {
457
532
  this.edgeLines = {};
458
533
  this._initialize3DViewports(viewportsInfo);
459
534
  };
460
- this._rotateClippingPlanes = (evt) => {
461
- const { element, currentPoints, lastPoints } = evt.detail;
462
- const currentPointsCanvas = currentPoints.canvas;
463
- const lastPointsCanvas = lastPoints.canvas;
464
- const rotateIncrementDegrees = this.configuration.rotateClippingPlanesIncrementDegrees ??
465
- this.configuration.rotateIncrementDegrees ??
466
- 5;
467
- const enabledElement = getEnabledElement(element);
468
- const { viewport } = enabledElement;
469
- const camera = viewport.getCamera();
470
- const width = element.clientWidth;
471
- const height = element.clientHeight;
472
- const normalizedPosition = [
473
- currentPointsCanvas[0] / width,
474
- currentPointsCanvas[1] / height,
475
- ];
476
- const normalizedPreviousPosition = [
477
- lastPointsCanvas[0] / width,
478
- lastPointsCanvas[1] / height,
479
- ];
480
- const normalizedCenter = [0.5, 0.5];
481
- const radsq = (1.0 + Math.abs(normalizedCenter[0])) ** 2.0;
482
- const op = [normalizedPreviousPosition[0], 0, 0];
483
- const oe = [normalizedPosition[0], 0, 0];
484
- const opsq = op[0] ** 2;
485
- const oesq = oe[0] ** 2;
486
- const lop = opsq > radsq ? 0 : Math.sqrt(radsq - opsq);
487
- const loe = oesq > radsq ? 0 : Math.sqrt(radsq - oesq);
488
- const nop = [op[0], 0, lop];
489
- vtkMath.normalize(nop);
490
- const noe = [oe[0], 0, loe];
491
- vtkMath.normalize(noe);
492
- const dot = vtkMath.dot(nop, noe);
493
- if (Math.abs(dot) > 0.0001) {
494
- const angleX = 20 *
495
- Math.acos(vtkMath.clampValue(dot, -1.0, 1.0)) *
496
- Math.sign(normalizedPosition[0] - normalizedPreviousPosition[0]) *
497
- rotateIncrementDegrees;
498
- const upVec = camera.viewUp;
499
- const atV = camera.viewPlaneNormal;
500
- const rightV = [0, 0, 0];
501
- const forwardV = [0, 0, 0];
502
- vtkMath.cross(upVec, atV, rightV);
503
- vtkMath.normalize(rightV);
504
- vtkMath.cross(atV, rightV, forwardV);
505
- vtkMath.normalize(forwardV);
506
- const angleY = 20 *
507
- (normalizedPosition[1] - normalizedPreviousPosition[1]) *
508
- rotateIncrementDegrees;
509
- let rotationCenter;
510
- if (this.sphereStates.length >= NUM_CLIPPING_PLANES) {
511
- const faces = [
512
- this.sphereStates[SPHEREINDEX.XMIN],
513
- this.sphereStates[SPHEREINDEX.XMAX],
514
- this.sphereStates[SPHEREINDEX.YMIN],
515
- this.sphereStates[SPHEREINDEX.YMAX],
516
- this.sphereStates[SPHEREINDEX.ZMIN],
517
- this.sphereStates[SPHEREINDEX.ZMAX],
518
- ];
519
- rotationCenter = [
520
- (faces[0].point[0] +
521
- faces[1].point[0] +
522
- faces[2].point[0] +
523
- faces[3].point[0] +
524
- faces[4].point[0] +
525
- faces[5].point[0]) /
526
- NUM_CLIPPING_PLANES,
527
- (faces[0].point[1] +
528
- faces[1].point[1] +
529
- faces[2].point[1] +
530
- faces[3].point[1] +
531
- faces[4].point[1] +
532
- faces[5].point[1]) /
533
- NUM_CLIPPING_PLANES,
534
- (faces[0].point[2] +
535
- faces[1].point[2] +
536
- faces[2].point[2] +
537
- faces[3].point[2] +
538
- faces[4].point[2] +
539
- faces[5].point[2]) /
540
- NUM_CLIPPING_PLANES,
541
- ];
542
- }
543
- else {
544
- rotationCenter = [0, 0, 0];
545
- }
546
- const transformX = mat4.identity(new Float32Array(16));
547
- mat4.translate(transformX, transformX, rotationCenter);
548
- mat4.rotate(transformX, transformX, (angleX * Math.PI) / 180, forwardV);
549
- mat4.translate(transformX, transformX, [
550
- -rotationCenter[0],
551
- -rotationCenter[1],
552
- -rotationCenter[2],
553
- ]);
554
- const transformY = mat4.identity(new Float32Array(16));
555
- mat4.translate(transformY, transformY, rotationCenter);
556
- mat4.rotate(transformY, transformY, (angleY * Math.PI) / 180, rightV);
557
- mat4.translate(transformY, transformY, [
558
- -rotationCenter[0],
559
- -rotationCenter[1],
560
- -rotationCenter[2],
561
- ]);
562
- const transform = mat4.create();
563
- mat4.multiply(transform, transformY, transformX);
564
- const normalTransformX4 = mat4.identity(new Float32Array(16));
565
- mat4.rotate(normalTransformX4, normalTransformX4, (angleX * Math.PI) / 180, forwardV);
566
- const normalTransformX = mat3.create();
567
- mat3.fromMat4(normalTransformX, normalTransformX4);
568
- const normalTransformY4 = mat4.identity(new Float32Array(16));
569
- mat4.rotate(normalTransformY4, normalTransformY4, (angleY * Math.PI) / 180, rightV);
570
- const normalTransformY = mat3.create();
571
- mat3.fromMat4(normalTransformY, normalTransformY4);
572
- const normalTransform = mat3.create();
573
- mat3.multiply(normalTransform, normalTransformY, normalTransformX);
574
- for (let i = 0; i < this.originalClippingPlanes.length; ++i) {
575
- const plane = this.originalClippingPlanes[i];
576
- const originVec = vec3.fromValues(plane.origin[0], plane.origin[1], plane.origin[2]);
577
- vec3.transformMat4(originVec, originVec, transform);
578
- plane.origin = [originVec[0], originVec[1], originVec[2]];
579
- const normalVec = vec3.fromValues(plane.normal[0], plane.normal[1], plane.normal[2]);
580
- vec3.transformMat3(normalVec, normalVec, normalTransform);
581
- vec3.normalize(normalVec, normalVec);
582
- plane.normal = [normalVec[0], normalVec[1], normalVec[2]];
583
- }
584
- if (this.sphereStates.length >= NUM_CLIPPING_PLANES) {
585
- this.sphereStates[SPHEREINDEX.XMIN].point = [
586
- ...this.originalClippingPlanes[PLANEINDEX.XMIN].origin,
587
- ];
588
- this.sphereStates[SPHEREINDEX.XMAX].point = [
589
- ...this.originalClippingPlanes[PLANEINDEX.XMAX].origin,
590
- ];
591
- this.sphereStates[SPHEREINDEX.YMIN].point = [
592
- ...this.originalClippingPlanes[PLANEINDEX.YMIN].origin,
593
- ];
594
- this.sphereStates[SPHEREINDEX.YMAX].point = [
595
- ...this.originalClippingPlanes[PLANEINDEX.YMAX].origin,
596
- ];
597
- this.sphereStates[SPHEREINDEX.ZMIN].point = [
598
- ...this.originalClippingPlanes[PLANEINDEX.ZMIN].origin,
599
- ];
600
- this.sphereStates[SPHEREINDEX.ZMAX].point = [
601
- ...this.originalClippingPlanes[PLANEINDEX.ZMAX].origin,
602
- ];
603
- [
604
- SPHEREINDEX.XMIN,
605
- SPHEREINDEX.XMAX,
606
- SPHEREINDEX.YMIN,
607
- SPHEREINDEX.YMAX,
608
- SPHEREINDEX.ZMIN,
609
- SPHEREINDEX.ZMAX,
610
- ].forEach((idx) => {
611
- const s = this.sphereStates[idx];
612
- s.sphereSource.setCenter(...s.point);
613
- s.sphereSource.modified();
614
- });
615
- const cornerIndices = [
616
- SPHEREINDEX.XMIN_YMIN_ZMIN,
617
- SPHEREINDEX.XMIN_YMIN_ZMAX,
618
- SPHEREINDEX.XMIN_YMAX_ZMIN,
619
- SPHEREINDEX.XMIN_YMAX_ZMAX,
620
- SPHEREINDEX.XMAX_YMIN_ZMIN,
621
- SPHEREINDEX.XMAX_YMIN_ZMAX,
622
- SPHEREINDEX.XMAX_YMAX_ZMIN,
623
- SPHEREINDEX.XMAX_YMAX_ZMAX,
624
- ];
625
- cornerIndices.forEach((idx) => {
626
- const cornerState = this.sphereStates[idx];
627
- if (cornerState) {
628
- const cornerVec = vec3.fromValues(cornerState.point[0], cornerState.point[1], cornerState.point[2]);
629
- vec3.transformMat4(cornerVec, cornerVec, transform);
630
- cornerState.point = [
631
- cornerVec[0],
632
- cornerVec[1],
633
- cornerVec[2],
634
- ];
635
- cornerState.sphereSource.setCenter(...cornerState.point);
636
- cornerState.sphereSource.modified();
637
- }
638
- });
639
- Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
640
- const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
641
- const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
642
- if (state1 && state2) {
643
- const points = source.getPoints();
644
- points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
645
- points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
646
- points.modified();
647
- source.modified();
648
- }
649
- });
650
- }
651
- this._updateClippingPlanes(viewport);
652
- viewport.render();
653
- triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, {
654
- originalClippingPlanes: this.originalClippingPlanes,
655
- viewportId: viewport.id,
656
- renderingEngineId: viewport.renderingEngineId,
657
- seriesInstanceUID: this.seriesInstanceUID,
658
- });
659
- }
660
- };
661
535
  this._rotateCamera = (viewport, centerWorld, axis, angle) => {
662
536
  const vtkCamera = viewport.getVtkActiveCamera();
663
537
  const viewUp = vtkCamera.getViewUp();
@@ -689,55 +563,59 @@ class VolumeCroppingTool extends BaseTool {
689
563
  this.mouseDragCallback = this._dragCallback.bind(this);
690
564
  }
691
565
  onSetToolActive() {
692
- const viewportsInfo = this._getViewportsInfo();
693
- const subscribeToElementResize = () => {
694
- viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
695
- if (!this._resizeObservers.has(viewportId)) {
696
- const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId) || { viewport: null };
697
- if (!viewport) {
698
- return;
699
- }
700
- const { element } = viewport;
701
- const resizeObserver = new ResizeObserver(() => {
702
- const element = getEnabledElementByIds(viewportId, renderingEngineId);
703
- if (!element) {
566
+ if (this.sphereStates && this.sphereStates.length > 0) {
567
+ if (this.configuration.showHandles) {
568
+ this.setHandlesVisible(false);
569
+ this.setClippingPlanesVisible(false);
570
+ }
571
+ else {
572
+ this.setHandlesVisible(true);
573
+ this.setClippingPlanesVisible(true);
574
+ }
575
+ }
576
+ else {
577
+ const viewportsInfo = this._getViewportsInfo();
578
+ const subscribeToElementResize = () => {
579
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
580
+ if (!this._resizeObservers.has(viewportId)) {
581
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId) || { viewport: null };
582
+ if (!viewport) {
704
583
  return;
705
584
  }
706
- const { viewport } = element;
707
- const viewPresentation = viewport.getViewPresentation();
708
- viewport.resetCamera();
709
- viewport.setViewPresentation(viewPresentation);
710
- viewport.render();
711
- });
712
- resizeObserver.observe(element);
713
- this._resizeObservers.set(viewportId, resizeObserver);
585
+ const { element } = viewport;
586
+ const resizeObserver = new ResizeObserver(() => {
587
+ const element = getEnabledElementByIds(viewportId, renderingEngineId);
588
+ if (!element) {
589
+ return;
590
+ }
591
+ const { viewport } = element;
592
+ const viewPresentation = viewport.getViewPresentation();
593
+ viewport.resetCamera();
594
+ viewport.setViewPresentation(viewPresentation);
595
+ viewport.render();
596
+ });
597
+ resizeObserver.observe(element);
598
+ this._resizeObservers.set(viewportId, resizeObserver);
599
+ }
600
+ });
601
+ };
602
+ subscribeToElementResize();
603
+ this._viewportAddedListener = (evt) => {
604
+ if (evt.detail.toolGroupId === this.toolGroupId) {
605
+ subscribeToElementResize();
714
606
  }
715
- });
716
- };
717
- subscribeToElementResize();
718
- this._viewportAddedListener = (evt) => {
719
- if (evt.detail.toolGroupId === this.toolGroupId) {
720
- subscribeToElementResize();
721
- }
722
- };
723
- eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
724
- this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
725
- this._subscribeToViewportNewVolumeSet(viewportsInfo);
726
- if (this.sphereStates && this.sphereStates.length === 0) {
727
- this.originalClippingPlanes = [];
607
+ };
608
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
609
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
610
+ this._subscribeToViewportNewVolumeSet(viewportsInfo);
728
611
  this._initialize3DViewports(viewportsInfo);
729
- }
730
- this.configuration.showClippingPlanes = false;
731
- this.configuration.showHandles = false;
732
- const viewport = this._getViewport();
733
- if (viewport &&
734
- this.originalClippingPlanes &&
735
- this.originalClippingPlanes.length > 0) {
736
- this._updateClippingPlanes(viewport);
737
612
  if (this.sphereStates && this.sphereStates.length > 0) {
738
- this._updateHandlesVisibility();
613
+ this.setHandlesVisible(true);
614
+ }
615
+ else {
616
+ this.originalClippingPlanes = [];
617
+ this._initialize3DViewports(viewportsInfo);
739
618
  }
740
- viewport.render();
741
619
  }
742
620
  }
743
621
  onSetToolDisabled() {
@@ -755,7 +633,30 @@ class VolumeCroppingTool extends BaseTool {
755
633
  setHandlesVisible(visible) {
756
634
  this.configuration.showHandles = visible;
757
635
  if (visible) {
758
- this._updateFaceSpheresFromClippingPlanes();
636
+ this.sphereStates[SPHEREINDEX.XMIN].point[0] =
637
+ this.originalClippingPlanes[PLANEINDEX.XMIN].origin[0];
638
+ this.sphereStates[SPHEREINDEX.XMAX].point[0] =
639
+ this.originalClippingPlanes[PLANEINDEX.XMAX].origin[0];
640
+ this.sphereStates[SPHEREINDEX.YMIN].point[1] =
641
+ this.originalClippingPlanes[PLANEINDEX.YMIN].origin[1];
642
+ this.sphereStates[SPHEREINDEX.YMAX].point[1] =
643
+ this.originalClippingPlanes[PLANEINDEX.YMAX].origin[1];
644
+ this.sphereStates[SPHEREINDEX.ZMIN].point[2] =
645
+ this.originalClippingPlanes[PLANEINDEX.ZMIN].origin[2];
646
+ this.sphereStates[SPHEREINDEX.ZMAX].point[2] =
647
+ this.originalClippingPlanes[PLANEINDEX.ZMAX].origin[2];
648
+ [
649
+ SPHEREINDEX.XMIN,
650
+ SPHEREINDEX.XMAX,
651
+ SPHEREINDEX.YMIN,
652
+ SPHEREINDEX.YMAX,
653
+ SPHEREINDEX.ZMIN,
654
+ SPHEREINDEX.ZMAX,
655
+ ].forEach((idx) => {
656
+ const s = this.sphereStates[idx];
657
+ s.sphereSource.setCenter(...s.point);
658
+ s.sphereSource.modified();
659
+ });
759
660
  this._updateCornerSpheres();
760
661
  }
761
662
  this._updateHandlesVisibility();
@@ -775,38 +676,14 @@ class VolumeCroppingTool extends BaseTool {
775
676
  this.configuration.showClippingPlanes = visible;
776
677
  const viewport = this._getViewport();
777
678
  this._updateClippingPlanes(viewport);
778
- if (this.sphereStates && this.sphereStates.length > 0) {
779
- this.configuration.showHandles = visible;
780
- this._updateHandlesVisibility();
781
- }
782
- if (visible &&
783
- viewport &&
784
- this.originalClippingPlanes?.length >= NUM_CLIPPING_PLANES) {
785
- this._notifyClippingPlanesChanged(viewport);
786
- }
787
679
  viewport.render();
788
680
  }
789
- getRotatePlanesOnDrag() {
790
- return this.rotatePlanesOnDrag;
791
- }
792
- setRotatePlanesOnDrag(enable) {
793
- this.rotatePlanesOnDrag = enable;
794
- const viewport = this._getViewport();
795
- if (viewport) {
796
- viewport.render();
797
- }
798
- }
799
681
  _dragCallback(evt) {
800
682
  const { element, currentPoints, lastPoints } = evt.detail;
801
683
  if (this.draggingSphereIndex !== null) {
802
684
  this._onMouseMoveSphere(evt);
803
685
  }
804
686
  else {
805
- const shiftKey = evt.detail.event?.shiftKey ?? false;
806
- if (this.rotatePlanesOnDrag === true || shiftKey) {
807
- this._rotateClippingPlanes(evt);
808
- return;
809
- }
810
687
  const currentPointsCanvas = currentPoints.canvas;
811
688
  const lastPointsCanvas = lastPoints.canvas;
812
689
  const { rotateIncrementDegrees } = this.configuration;
@@ -862,6 +739,13 @@ class VolumeCroppingTool extends BaseTool {
862
739
  }
863
740
  _updateClippingPlanes(viewport) {
864
741
  const actorEntry = viewport.getDefaultActor();
742
+ if (!actorEntry || !actorEntry.actor) {
743
+ if (!viewport._missingActorWarned) {
744
+ console.warn('VolumeCroppingTool._updateClippingPlanes: No default actor found in viewport.');
745
+ viewport._missingActorWarned = true;
746
+ }
747
+ return;
748
+ }
865
749
  const actor = actorEntry.actor;
866
750
  const mapper = actor.getMapper();
867
751
  const matrix = actor.getMatrix();
@@ -913,6 +797,35 @@ class VolumeCroppingTool extends BaseTool {
913
797
  }
914
798
  });
915
799
  }
800
+ _addLine3DBetweenPoints(viewport, point1, point2, color = [0.7, 0.7, 0.7], uid = '') {
801
+ if (point1[0] === point2[0] &&
802
+ point1[1] === point2[1] &&
803
+ point1[2] === point2[2]) {
804
+ return { actor: null, source: null };
805
+ }
806
+ const points = vtkPoints.newInstance();
807
+ points.setNumberOfPoints(2);
808
+ points.setPoint(0, point1[0], point1[1], point1[2]);
809
+ points.setPoint(1, point2[0], point2[1], point2[2]);
810
+ const lines = vtkCellArray.newInstance({ values: [2, 0, 1] });
811
+ const polyData = vtkPolyData.newInstance();
812
+ polyData.setPoints(points);
813
+ polyData.setLines(lines);
814
+ const mapper = vtkMapper.newInstance();
815
+ mapper.setInputData(polyData);
816
+ const actor = vtkActor.newInstance();
817
+ actor.setMapper(mapper);
818
+ actor.getProperty().setColor(...color);
819
+ actor.getProperty().setLineWidth(0.5);
820
+ actor.getProperty().setOpacity(1.0);
821
+ actor.getProperty().setInterpolationToFlat();
822
+ actor.getProperty().setAmbient(1.0);
823
+ actor.getProperty().setDiffuse(0.0);
824
+ actor.getProperty().setSpecular(0.0);
825
+ actor.setVisibility(this.configuration.showHandles);
826
+ viewport.addActor({ actor, uid });
827
+ return { actor, source: polyData };
828
+ }
916
829
  _addSphere(viewport, point, axis, position, cornerKey = null, adaptiveRadius) {
917
830
  const uid = cornerKey ? `corner_${cornerKey}` : `${axis}_${position}`;
918
831
  const sphereState = this.sphereStates.find((s) => s.uid === uid);
@@ -941,10 +854,9 @@ class VolumeCroppingTool extends BaseTool {
941
854
  color = sphereColors.CORONAL || [0.0, 1.0, 0.0];
942
855
  }
943
856
  const idx = this.sphereStates.findIndex((s) => s.uid === uid);
944
- const pointCopy = [point[0], point[1], point[2]];
945
857
  if (idx === -1) {
946
858
  this.sphereStates.push({
947
- point: pointCopy,
859
+ point: point.slice(),
948
860
  axis,
949
861
  uid,
950
862
  sphereSource,
@@ -954,144 +866,88 @@ class VolumeCroppingTool extends BaseTool {
954
866
  });
955
867
  }
956
868
  else {
957
- this.sphereStates[idx].point = pointCopy;
869
+ this.sphereStates[idx].point = point.slice();
958
870
  this.sphereStates[idx].sphereSource = sphereSource;
959
871
  }
872
+ const existingActors = viewport.getActors();
873
+ const existing = existingActors.find((a) => a.uid === uid);
874
+ if (existing) {
875
+ return;
876
+ }
960
877
  sphereActor.getProperty().setColor(color);
961
878
  sphereActor.setVisibility(this.configuration.showHandles);
962
879
  viewport.addActor({ actor: sphereActor, uid: uid });
963
880
  }
964
- _getDirectionVectorForAxis(axis) {
965
- if (this.originalClippingPlanes &&
966
- this.originalClippingPlanes.length >= NUM_CLIPPING_PLANES) {
967
- switch (axis) {
968
- case 'x':
969
- return this.originalClippingPlanes[PLANEINDEX.XMIN]
970
- .normal;
971
- case 'y':
972
- return this.originalClippingPlanes[PLANEINDEX.YMIN]
973
- .normal;
974
- case 'z':
975
- return this.originalClippingPlanes[PLANEINDEX.ZMIN]
976
- .normal;
977
- default:
978
- return [1, 0, 0];
979
- }
980
- }
981
- if (!this.volumeDirectionVectors) {
982
- if (axis === 'x')
983
- return [1, 0, 0];
984
- if (axis === 'y')
985
- return [0, 1, 0];
986
- if (axis === 'z')
987
- return [0, 0, 1];
988
- return [1, 0, 0];
989
- }
990
- switch (axis) {
991
- case 'x':
992
- return this.volumeDirectionVectors.xDir;
993
- case 'y':
994
- return this.volumeDirectionVectors.yDir;
995
- case 'z':
996
- return this.volumeDirectionVectors.zDir;
997
- default:
998
- return [1, 0, 0];
999
- }
1000
- }
1001
- _getVolumeActor(viewport) {
1002
- const vp = viewport || this._getViewport();
1003
- return vp?.getDefaultActor()?.actor;
1004
- }
1005
- _getVolumeMapper(viewport) {
1006
- const actor = this._getVolumeActor(viewport);
1007
- return actor?.getMapper();
1008
- }
1009
- _applyClippingPlanesToMapper(mapper) {
1010
- mapper.removeAllClippingPlanes();
1011
- for (let i = 0; i < NUM_CLIPPING_PLANES; ++i) {
1012
- const plane = vtkPlane.newInstance({
1013
- origin: this.originalClippingPlanes[i].origin,
1014
- normal: this.originalClippingPlanes[i].normal,
1015
- });
1016
- mapper.addClippingPlane(plane);
1017
- }
881
+ _calculateAdaptiveSphereRadius(diagonal) {
882
+ const baseRadius = this.configuration.sphereRadius !== undefined
883
+ ? this.configuration.sphereRadius
884
+ : 8;
885
+ const scaleFactor = this.configuration.sphereRadiusScale || 0.01;
886
+ const adaptiveRadius = diagonal * scaleFactor;
887
+ const minRadius = this.configuration.minSphereRadius || 2;
888
+ const maxRadius = this.configuration.maxSphereRadius || 50;
889
+ return Math.max(minRadius, Math.min(maxRadius, adaptiveRadius));
1018
890
  }
1019
891
  _updateClippingPlanesFromFaceSpheres(viewport) {
1020
892
  const mapper = viewport.getDefaultActor().actor.getMapper();
1021
- this.originalClippingPlanes[PLANEINDEX.XMIN].origin = [
893
+ this.originalClippingPlanes[0].origin = [
1022
894
  ...this.sphereStates[SPHEREINDEX.XMIN].point,
1023
895
  ];
1024
- this.originalClippingPlanes[PLANEINDEX.XMAX].origin = [
896
+ this.originalClippingPlanes[1].origin = [
1025
897
  ...this.sphereStates[SPHEREINDEX.XMAX].point,
1026
898
  ];
1027
- this.originalClippingPlanes[PLANEINDEX.YMIN].origin = [
899
+ this.originalClippingPlanes[2].origin = [
1028
900
  ...this.sphereStates[SPHEREINDEX.YMIN].point,
1029
901
  ];
1030
- this.originalClippingPlanes[PLANEINDEX.YMAX].origin = [
902
+ this.originalClippingPlanes[3].origin = [
1031
903
  ...this.sphereStates[SPHEREINDEX.YMAX].point,
1032
904
  ];
1033
- this.originalClippingPlanes[PLANEINDEX.ZMIN].origin = [
905
+ this.originalClippingPlanes[4].origin = [
1034
906
  ...this.sphereStates[SPHEREINDEX.ZMIN].point,
1035
907
  ];
1036
- this.originalClippingPlanes[PLANEINDEX.ZMAX].origin = [
908
+ this.originalClippingPlanes[5].origin = [
1037
909
  ...this.sphereStates[SPHEREINDEX.ZMAX].point,
1038
910
  ];
1039
- this._applyClippingPlanesToMapper(mapper);
911
+ mapper.removeAllClippingPlanes();
912
+ for (let i = 0; i < 6; ++i) {
913
+ const origin = this.originalClippingPlanes[i].origin;
914
+ const normal = this.originalClippingPlanes[i].normal;
915
+ const plane = vtkPlane.newInstance({
916
+ origin,
917
+ normal,
918
+ });
919
+ mapper.addClippingPlane(plane);
920
+ }
1040
921
  }
1041
922
  _updateCornerSpheresFromFaces() {
1042
- const { xDir, yDir, zDir } = this._getDirectionVectors();
1043
- if (!xDir || !yDir || !zDir)
1044
- return;
1045
- const faceXMin = this.sphereStates[SPHEREINDEX.XMIN].point;
1046
- const faceXMax = this.sphereStates[SPHEREINDEX.XMAX].point;
1047
- const faceYMin = this.sphereStates[SPHEREINDEX.YMIN].point;
1048
- const faceYMax = this.sphereStates[SPHEREINDEX.YMAX].point;
1049
- const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN].point;
1050
- const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX].point;
923
+ const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
924
+ const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
925
+ const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
926
+ const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
927
+ const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
928
+ const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
1051
929
  const corners = [
1052
- {
1053
- key: 'XMIN_YMIN_ZMIN',
1054
- pos: this._calculateCornerFromFaces(faceXMin, faceYMin, faceZMin, xDir, yDir, zDir),
1055
- },
1056
- {
1057
- key: 'XMIN_YMIN_ZMAX',
1058
- pos: this._calculateCornerFromFaces(faceXMin, faceYMin, faceZMax, xDir, yDir, zDir),
1059
- },
1060
- {
1061
- key: 'XMIN_YMAX_ZMIN',
1062
- pos: this._calculateCornerFromFaces(faceXMin, faceYMax, faceZMin, xDir, yDir, zDir),
1063
- },
1064
- {
1065
- key: 'XMIN_YMAX_ZMAX',
1066
- pos: this._calculateCornerFromFaces(faceXMin, faceYMax, faceZMax, xDir, yDir, zDir),
1067
- },
1068
- {
1069
- key: 'XMAX_YMIN_ZMIN',
1070
- pos: this._calculateCornerFromFaces(faceXMax, faceYMin, faceZMin, xDir, yDir, zDir),
1071
- },
1072
- {
1073
- key: 'XMAX_YMIN_ZMAX',
1074
- pos: this._calculateCornerFromFaces(faceXMax, faceYMin, faceZMax, xDir, yDir, zDir),
1075
- },
1076
- {
1077
- key: 'XMAX_YMAX_ZMIN',
1078
- pos: this._calculateCornerFromFaces(faceXMax, faceYMax, faceZMin, xDir, yDir, zDir),
1079
- },
1080
- {
1081
- key: 'XMAX_YMAX_ZMAX',
1082
- pos: this._calculateCornerFromFaces(faceXMax, faceYMax, faceZMax, xDir, yDir, zDir),
1083
- },
930
+ { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
931
+ { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
932
+ { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
933
+ { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
934
+ { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
935
+ { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
936
+ { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
937
+ { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
1084
938
  ];
1085
939
  for (const corner of corners) {
1086
- const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
1087
- if (stateIndex !== -1) {
1088
- this._updateSpherePosition(stateIndex, corner.pos);
940
+ const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
941
+ if (state) {
942
+ state.point[0] = corner.pos[0];
943
+ state.point[1] = corner.pos[1];
944
+ state.point[2] = corner.pos[2];
945
+ state.sphereSource.setCenter(...state.point);
946
+ state.sphereSource.modified();
1089
947
  }
1090
948
  }
1091
949
  }
1092
950
  _updateFaceSpheresFromCorners() {
1093
- if (!this.volumeDirectionVectors)
1094
- return;
1095
951
  const corners = [
1096
952
  this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMIN].point,
1097
953
  this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMAX].point,
@@ -1102,100 +958,93 @@ class VolumeCroppingTool extends BaseTool {
1102
958
  this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMIN].point,
1103
959
  this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMAX].point,
1104
960
  ];
1105
- const faceXMin = this._averagePoints([
1106
- corners[0],
1107
- corners[1],
1108
- corners[2],
1109
- corners[3],
1110
- ]);
1111
- const faceXMax = this._averagePoints([
1112
- corners[4],
1113
- corners[5],
1114
- corners[6],
1115
- corners[7],
1116
- ]);
1117
- const faceYMin = this._averagePoints([
1118
- corners[0],
1119
- corners[1],
1120
- corners[4],
1121
- corners[5],
1122
- ]);
1123
- const faceYMax = this._averagePoints([
1124
- corners[2],
1125
- corners[3],
1126
- corners[6],
1127
- corners[7],
1128
- ]);
1129
- const faceZMin = this._averagePoints([
1130
- corners[0],
1131
- corners[2],
1132
- corners[4],
1133
- corners[6],
1134
- ]);
1135
- const faceZMax = this._averagePoints([
1136
- corners[1],
1137
- corners[3],
1138
- corners[5],
1139
- corners[7],
1140
- ]);
1141
- this._updateSpherePosition(SPHEREINDEX.XMIN, faceXMin);
1142
- this._updateSpherePosition(SPHEREINDEX.XMAX, faceXMax);
1143
- this._updateSpherePosition(SPHEREINDEX.YMIN, faceYMin);
1144
- this._updateSpherePosition(SPHEREINDEX.YMAX, faceYMax);
1145
- this._updateSpherePosition(SPHEREINDEX.ZMIN, faceZMin);
1146
- this._updateSpherePosition(SPHEREINDEX.ZMAX, faceZMax);
961
+ const xs = corners.map((p) => p[0]);
962
+ const ys = corners.map((p) => p[1]);
963
+ const zs = corners.map((p) => p[2]);
964
+ const xMin = Math.min(...xs), xMax = Math.max(...xs);
965
+ const yMin = Math.min(...ys), yMax = Math.max(...ys);
966
+ const zMin = Math.min(...zs), zMax = Math.max(...zs);
967
+ this.sphereStates[SPHEREINDEX.XMIN].point = [
968
+ xMin,
969
+ (yMin + yMax) / 2,
970
+ (zMin + zMax) / 2,
971
+ ];
972
+ this.sphereStates[SPHEREINDEX.XMAX].point = [
973
+ xMax,
974
+ (yMin + yMax) / 2,
975
+ (zMin + zMax) / 2,
976
+ ];
977
+ this.sphereStates[SPHEREINDEX.YMIN].point = [
978
+ (xMin + xMax) / 2,
979
+ yMin,
980
+ (zMin + zMax) / 2,
981
+ ];
982
+ this.sphereStates[SPHEREINDEX.YMAX].point = [
983
+ (xMin + xMax) / 2,
984
+ yMax,
985
+ (zMin + zMax) / 2,
986
+ ];
987
+ this.sphereStates[SPHEREINDEX.ZMIN].point = [
988
+ (xMin + xMax) / 2,
989
+ (yMin + yMax) / 2,
990
+ zMin,
991
+ ];
992
+ this.sphereStates[SPHEREINDEX.ZMAX].point = [
993
+ (xMin + xMax) / 2,
994
+ (yMin + yMax) / 2,
995
+ zMax,
996
+ ];
997
+ [
998
+ SPHEREINDEX.XMIN,
999
+ SPHEREINDEX.XMAX,
1000
+ SPHEREINDEX.YMIN,
1001
+ SPHEREINDEX.YMAX,
1002
+ SPHEREINDEX.ZMIN,
1003
+ SPHEREINDEX.ZMAX,
1004
+ ].forEach((idx) => {
1005
+ const s = this.sphereStates[idx];
1006
+ s.sphereSource.setCenter(...s.point);
1007
+ s.sphereSource.modified();
1008
+ });
1147
1009
  }
1148
1010
  _updateCornerSpheres() {
1149
- const { xDir, yDir, zDir } = this._getDirectionVectors();
1150
- if (!xDir || !yDir || !zDir)
1151
- return;
1152
- const faceXMin = this.sphereStates[SPHEREINDEX.XMIN].point;
1153
- const faceXMax = this.sphereStates[SPHEREINDEX.XMAX].point;
1154
- const faceYMin = this.sphereStates[SPHEREINDEX.YMIN].point;
1155
- const faceYMax = this.sphereStates[SPHEREINDEX.YMAX].point;
1156
- const faceZMin = this.sphereStates[SPHEREINDEX.ZMIN].point;
1157
- const faceZMax = this.sphereStates[SPHEREINDEX.ZMAX].point;
1011
+ const xMin = this.sphereStates[SPHEREINDEX.XMIN].point[0];
1012
+ const xMax = this.sphereStates[SPHEREINDEX.XMAX].point[0];
1013
+ const yMin = this.sphereStates[SPHEREINDEX.YMIN].point[1];
1014
+ const yMax = this.sphereStates[SPHEREINDEX.YMAX].point[1];
1015
+ const zMin = this.sphereStates[SPHEREINDEX.ZMIN].point[2];
1016
+ const zMax = this.sphereStates[SPHEREINDEX.ZMAX].point[2];
1158
1017
  const corners = [
1159
- {
1160
- key: 'XMIN_YMIN_ZMIN',
1161
- pos: this._calculateCornerFromProjection(faceXMin, faceYMin, faceZMin, xDir, yDir, zDir),
1162
- },
1163
- {
1164
- key: 'XMIN_YMIN_ZMAX',
1165
- pos: this._calculateCornerFromProjection(faceXMin, faceYMin, faceZMax, xDir, yDir, zDir),
1166
- },
1167
- {
1168
- key: 'XMIN_YMAX_ZMIN',
1169
- pos: this._calculateCornerFromProjection(faceXMin, faceYMax, faceZMin, xDir, yDir, zDir),
1170
- },
1171
- {
1172
- key: 'XMIN_YMAX_ZMAX',
1173
- pos: this._calculateCornerFromProjection(faceXMin, faceYMax, faceZMax, xDir, yDir, zDir),
1174
- },
1175
- {
1176
- key: 'XMAX_YMIN_ZMIN',
1177
- pos: this._calculateCornerFromProjection(faceXMax, faceYMin, faceZMin, xDir, yDir, zDir),
1178
- },
1179
- {
1180
- key: 'XMAX_YMIN_ZMAX',
1181
- pos: this._calculateCornerFromProjection(faceXMax, faceYMin, faceZMax, xDir, yDir, zDir),
1182
- },
1183
- {
1184
- key: 'XMAX_YMAX_ZMIN',
1185
- pos: this._calculateCornerFromProjection(faceXMax, faceYMax, faceZMin, xDir, yDir, zDir),
1186
- },
1187
- {
1188
- key: 'XMAX_YMAX_ZMAX',
1189
- pos: this._calculateCornerFromProjection(faceXMax, faceYMax, faceZMax, xDir, yDir, zDir),
1190
- },
1018
+ { key: 'XMIN_YMIN_ZMIN', pos: [xMin, yMin, zMin] },
1019
+ { key: 'XMIN_YMIN_ZMAX', pos: [xMin, yMin, zMax] },
1020
+ { key: 'XMIN_YMAX_ZMIN', pos: [xMin, yMax, zMin] },
1021
+ { key: 'XMIN_YMAX_ZMAX', pos: [xMin, yMax, zMax] },
1022
+ { key: 'XMAX_YMIN_ZMIN', pos: [xMax, yMin, zMin] },
1023
+ { key: 'XMAX_YMIN_ZMAX', pos: [xMax, yMin, zMax] },
1024
+ { key: 'XMAX_YMAX_ZMIN', pos: [xMax, yMax, zMin] },
1025
+ { key: 'XMAX_YMAX_ZMAX', pos: [xMax, yMax, zMax] },
1191
1026
  ];
1192
1027
  for (const corner of corners) {
1193
- const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
1194
- if (stateIndex !== -1) {
1195
- this._updateSpherePosition(stateIndex, corner.pos);
1028
+ const state = this.sphereStates.find((s) => s.uid === `corner_${corner.key}`);
1029
+ if (state) {
1030
+ state.point[0] = corner.pos[0];
1031
+ state.point[1] = corner.pos[1];
1032
+ state.point[2] = corner.pos[2];
1033
+ state.sphereSource.setCenter(...state.point);
1034
+ state.sphereSource.modified();
1196
1035
  }
1197
1036
  }
1198
- this._updateEdgeLines();
1037
+ Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
1038
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
1039
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
1040
+ if (state1 && state2) {
1041
+ const points = source.getPoints();
1042
+ points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
1043
+ points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
1044
+ points.modified();
1045
+ source.modified();
1046
+ }
1047
+ });
1199
1048
  }
1200
1049
  _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1201
1050
  viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
@@ -1211,105 +1060,6 @@ class VolumeCroppingTool extends BaseTool {
1211
1060
  element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1212
1061
  });
1213
1062
  }
1214
- _getDirectionVectors() {
1215
- const hasPlanes = this.originalClippingPlanes?.length >= NUM_CLIPPING_PLANES;
1216
- return {
1217
- xDir: hasPlanes
1218
- ? this.originalClippingPlanes[PLANEINDEX.XMIN].normal
1219
- : this.volumeDirectionVectors?.xDir || [1, 0, 0],
1220
- yDir: hasPlanes
1221
- ? this.originalClippingPlanes[PLANEINDEX.YMIN].normal
1222
- : this.volumeDirectionVectors?.yDir || [0, 1, 0],
1223
- zDir: hasPlanes
1224
- ? this.originalClippingPlanes[PLANEINDEX.ZMIN].normal
1225
- : this.volumeDirectionVectors?.zDir || [0, 0, 1],
1226
- };
1227
- }
1228
- _updateSpherePosition(sphereIndex, newPoint) {
1229
- const state = this.sphereStates[sphereIndex];
1230
- if (state) {
1231
- state.point = newPoint;
1232
- state.sphereSource.setCenter(...newPoint);
1233
- state.sphereSource.modified();
1234
- }
1235
- }
1236
- _updateFaceSpheresFromClippingPlanes() {
1237
- const faceMappings = [
1238
- { sphereIdx: SPHEREINDEX.XMIN, planeIdx: PLANEINDEX.XMIN },
1239
- { sphereIdx: SPHEREINDEX.XMAX, planeIdx: PLANEINDEX.XMAX },
1240
- { sphereIdx: SPHEREINDEX.YMIN, planeIdx: PLANEINDEX.YMIN },
1241
- { sphereIdx: SPHEREINDEX.YMAX, planeIdx: PLANEINDEX.YMAX },
1242
- { sphereIdx: SPHEREINDEX.ZMIN, planeIdx: PLANEINDEX.ZMIN },
1243
- { sphereIdx: SPHEREINDEX.ZMAX, planeIdx: PLANEINDEX.ZMAX },
1244
- ];
1245
- faceMappings.forEach(({ sphereIdx, planeIdx }) => {
1246
- const newPoint = [
1247
- ...this.originalClippingPlanes[planeIdx].origin,
1248
- ];
1249
- this._updateSpherePosition(sphereIdx, newPoint);
1250
- });
1251
- }
1252
- _averagePoints(points) {
1253
- const sum = points.reduce((acc, p) => [acc[0] + p[0], acc[1] + p[1], acc[2] + p[2]], [0, 0, 0]);
1254
- return [
1255
- sum[0] / points.length,
1256
- sum[1] / points.length,
1257
- sum[2] / points.length,
1258
- ];
1259
- }
1260
- _notifyClippingPlanesChanged(viewport) {
1261
- const eventData = {
1262
- originalClippingPlanes: this.originalClippingPlanes,
1263
- seriesInstanceUID: this.seriesInstanceUID,
1264
- };
1265
- if (viewport) {
1266
- eventData.viewportId = viewport.id;
1267
- eventData.renderingEngineId = viewport.renderingEngineId;
1268
- }
1269
- triggerEvent(eventTarget, Events.VOLUMECROPPING_TOOL_CHANGED, eventData);
1270
- }
1271
- _calculateCornerFromFaces(faceX, faceY, faceZ, xDir, yDir, zDir) {
1272
- const deltaXY = [
1273
- faceY[0] - faceX[0],
1274
- faceY[1] - faceX[1],
1275
- faceY[2] - faceX[2],
1276
- ];
1277
- const distY = deltaXY[0] * yDir[0] + deltaXY[1] * yDir[1] + deltaXY[2] * yDir[2];
1278
- const deltaXZ = [
1279
- faceZ[0] - faceX[0],
1280
- faceZ[1] - faceX[1],
1281
- faceZ[2] - faceX[2],
1282
- ];
1283
- const distZ = deltaXZ[0] * zDir[0] + deltaXZ[1] * zDir[1] + deltaXZ[2] * zDir[2];
1284
- return [
1285
- faceX[0] + distY * yDir[0] + distZ * zDir[0],
1286
- faceX[1] + distY * yDir[1] + distZ * zDir[1],
1287
- faceX[2] + distY * yDir[2] + distZ * zDir[2],
1288
- ];
1289
- }
1290
- _calculateCornerFromProjection(faceX, faceY, faceZ, xDir, yDir, zDir) {
1291
- const dX = faceX[0] * xDir[0] + faceX[1] * xDir[1] + faceX[2] * xDir[2];
1292
- const dY = faceY[0] * yDir[0] + faceY[1] * yDir[1] + faceY[2] * yDir[2];
1293
- const dZ = faceZ[0] * zDir[0] + faceZ[1] * zDir[1] + faceZ[2] * zDir[2];
1294
- return [
1295
- dX * xDir[0] + dY * yDir[0] + dZ * zDir[0],
1296
- dX * xDir[1] + dY * yDir[1] + dZ * zDir[1],
1297
- dX * xDir[2] + dY * yDir[2] + dZ * zDir[2],
1298
- ];
1299
- }
1300
- _updateEdgeLines() {
1301
- Object.values(this.edgeLines).forEach(({ source, key1, key2 }) => {
1302
- const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
1303
- const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
1304
- if (state1 && state2) {
1305
- const points = source.getPoints();
1306
- points.setPoint(0, state1.point[0], state1.point[1], state1.point[2]);
1307
- points.setPoint(1, state2.point[0], state2.point[1], state2.point[2]);
1308
- points.modified();
1309
- source.modified();
1310
- }
1311
- });
1312
- }
1313
1063
  }
1314
1064
  VolumeCroppingTool.toolName = 'VolumeCropping';
1315
1065
  export default VolumeCroppingTool;