@cornerstonejs/tools 4.0.0-beta.2 → 4.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/dist/esm/enums/Events.d.ts +2 -0
  3. package/dist/esm/enums/Events.js +2 -0
  4. package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +5 -1
  5. package/dist/esm/index.d.ts +2 -2
  6. package/dist/esm/index.js +2 -2
  7. package/dist/esm/stateManagement/annotation/annotationVisibility.js +2 -0
  8. package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +1 -0
  9. package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +11 -0
  10. package/dist/esm/stateManagement/segmentation/config/segmentationVisibility.js +3 -0
  11. package/dist/esm/stateManagement/segmentation/helpers/normalizeSegmentationInput.js +13 -0
  12. package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +11 -0
  13. package/dist/esm/store/ToolGroupManager/ToolGroup.js +10 -11
  14. package/dist/esm/tools/CrosshairsTool.d.ts +13 -12
  15. package/dist/esm/tools/CrosshairsTool.js +10 -3
  16. package/dist/esm/tools/OrientationMarkerTool.js +4 -2
  17. package/dist/esm/tools/OverlayGridTool.d.ts +2 -2
  18. package/dist/esm/tools/SegmentationIntersectionTool.d.ts +8 -5
  19. package/dist/esm/tools/SegmentationIntersectionTool.js +1 -1
  20. package/dist/esm/tools/VolumeCroppingControlTool.d.ts +91 -0
  21. package/dist/esm/tools/VolumeCroppingControlTool.js +1208 -0
  22. package/dist/esm/tools/VolumeCroppingTool.d.ts +96 -0
  23. package/dist/esm/tools/VolumeCroppingTool.js +1065 -0
  24. package/dist/esm/tools/WindowLevelTool.js +1 -1
  25. package/dist/esm/tools/annotation/ProbeTool.js +1 -0
  26. package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/utils/generateConvexHullFromContour.js +3 -3
  27. package/dist/esm/tools/annotation/VideoRedactionTool.d.ts +1 -1
  28. package/dist/esm/tools/annotation/VideoRedactionTool.js +1 -1
  29. package/dist/esm/tools/base/AnnotationTool.d.ts +1 -11
  30. package/dist/esm/tools/base/BaseTool.d.ts +2 -0
  31. package/dist/esm/tools/base/BaseTool.js +6 -0
  32. package/dist/esm/tools/index.d.ts +3 -1
  33. package/dist/esm/tools/index.js +3 -1
  34. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +30 -16
  35. package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +2 -0
  36. package/dist/esm/tools/segmentation/LabelmapBaseTool.js +11 -0
  37. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +9 -3
  38. package/dist/esm/tools/segmentation/SegmentLabelTool.js +17 -5
  39. package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +1 -0
  40. package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -0
  41. package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -6
  42. package/dist/esm/tools/segmentation/strategies/fillCircle.js +53 -30
  43. package/dist/esm/tools/segmentation/strategies/fillRectangle.js +26 -24
  44. package/dist/esm/tools/segmentation/strategies/fillSphere.js +14 -12
  45. package/dist/esm/types/AnnotationTypes.d.ts +22 -19
  46. package/dist/esm/types/ContourAnnotation.d.ts +1 -0
  47. package/dist/esm/types/index.d.ts +2 -2
  48. package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +17 -5
  49. package/dist/esm/utilities/segmentation/getBrushToolInstances.js +2 -2
  50. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.d.ts +1 -1
  51. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js +20 -10
  52. package/dist/esm/utilities/stackPrefetch/stackPrefetch.js +12 -2
  53. package/dist/esm/version.d.ts +1 -1
  54. package/dist/esm/version.js +1 -1
  55. package/package.json +5 -4
@@ -0,0 +1,1065 @@
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
+ this._updateCornerSpheres();
274
+ viewport.render();
275
+ }
276
+ };
277
+ this._getViewportsInfo = () => {
278
+ const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
279
+ return viewports;
280
+ };
281
+ this._initialize3DViewports = (viewportsInfo) => {
282
+ if (!viewportsInfo || !viewportsInfo.length || !viewportsInfo[0]) {
283
+ console.warn('VolumeCroppingTool: No viewportsInfo available for initialization of volumecroppingtool.');
284
+ return;
285
+ }
286
+ const viewport = this._getViewport();
287
+ const volumeActors = viewport.getActors();
288
+ if (!volumeActors || volumeActors.length === 0) {
289
+ console.warn('VolumeCroppingTool: No volume actors found in the viewport.');
290
+ return;
291
+ }
292
+ const imageData = volumeActors[0].actor.getMapper().getInputData();
293
+ if (!imageData) {
294
+ console.warn('VolumeCroppingTool: No image data found for volume actor.');
295
+ return;
296
+ }
297
+ this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
298
+ const worldBounds = imageData.getBounds();
299
+ const cropFactor = this.configuration.initialCropFactor || 0.1;
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],
313
+ });
314
+ const planeXmax = vtkPlane.newInstance({
315
+ origin: [xMax, 0, 0],
316
+ normal: [-1, 0, 0],
317
+ });
318
+ const planeYmin = vtkPlane.newInstance({
319
+ origin: [0, yMin, 0],
320
+ normal: [0, 1, 0],
321
+ });
322
+ const planeYmax = vtkPlane.newInstance({
323
+ origin: [0, yMax, 0],
324
+ normal: [0, -1, 0],
325
+ });
326
+ const planeZmin = vtkPlane.newInstance({
327
+ origin: [0, 0, zMin],
328
+ normal: [0, 0, 1],
329
+ });
330
+ const planeZmax = vtkPlane.newInstance({
331
+ origin: [0, 0, zMax],
332
+ normal: [0, 0, -1],
333
+ });
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);
343
+ const originalPlanes = planes.map((plane) => ({
344
+ origin: [...plane.getOrigin()],
345
+ normal: [...plane.getNormal()],
346
+ }));
347
+ this.originalClippingPlanes = originalPlanes;
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);
361
+ const corners = [
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],
370
+ ];
371
+ const cornerKeys = [
372
+ 'XMIN_YMIN_ZMIN',
373
+ 'XMIN_YMIN_ZMAX',
374
+ 'XMIN_YMAX_ZMIN',
375
+ 'XMIN_YMAX_ZMAX',
376
+ 'XMAX_YMIN_ZMIN',
377
+ 'XMAX_YMIN_ZMAX',
378
+ 'XMAX_YMAX_ZMIN',
379
+ 'XMAX_YMAX_ZMAX',
380
+ ];
381
+ for (let i = 0; i < corners.length; i++) {
382
+ this._addSphere(viewport, corners[i], 'corner', null, cornerKeys[i], adaptiveRadius);
383
+ }
384
+ const edgeCornerPairs = [
385
+ ['XMIN_YMIN_ZMIN', 'XMAX_YMIN_ZMIN'],
386
+ ['XMIN_YMIN_ZMAX', 'XMAX_YMIN_ZMAX'],
387
+ ['XMIN_YMAX_ZMIN', 'XMAX_YMAX_ZMIN'],
388
+ ['XMIN_YMAX_ZMAX', 'XMAX_YMAX_ZMAX'],
389
+ ['XMIN_YMIN_ZMIN', 'XMIN_YMAX_ZMIN'],
390
+ ['XMIN_YMIN_ZMAX', 'XMIN_YMAX_ZMAX'],
391
+ ['XMAX_YMIN_ZMIN', 'XMAX_YMAX_ZMIN'],
392
+ ['XMAX_YMIN_ZMAX', 'XMAX_YMAX_ZMAX'],
393
+ ['XMIN_YMIN_ZMIN', 'XMIN_YMIN_ZMAX'],
394
+ ['XMIN_YMAX_ZMIN', 'XMIN_YMAX_ZMAX'],
395
+ ['XMAX_YMIN_ZMIN', 'XMAX_YMIN_ZMAX'],
396
+ ['XMAX_YMAX_ZMIN', 'XMAX_YMAX_ZMAX'],
397
+ ];
398
+ edgeCornerPairs.forEach(([key1, key2], i) => {
399
+ const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
400
+ const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
401
+ if (state1 && state2) {
402
+ const uid = `edge_${key1}_${key2}`;
403
+ const { actor, source } = this._addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid);
404
+ this.edgeLines[uid] = { actor, source, key1, key2 };
405
+ }
406
+ });
407
+ mapper.addClippingPlane(planeXmin);
408
+ mapper.addClippingPlane(planeXmax);
409
+ mapper.addClippingPlane(planeYmin);
410
+ mapper.addClippingPlane(planeYmax);
411
+ mapper.addClippingPlane(planeZmin);
412
+ mapper.addClippingPlane(planeZmax);
413
+ eventTarget.addEventListener(Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, (evt) => {
414
+ this._onControlToolChange(evt);
415
+ });
416
+ viewport.render();
417
+ };
418
+ this._getViewportAndWorldCoords = (evt) => {
419
+ const viewport = this._getViewport();
420
+ const x = evt.detail.currentPoints.canvas[0];
421
+ const y = evt.detail.currentPoints.canvas[1];
422
+ const world = viewport.canvasToWorld([x, y]);
423
+ return { viewport, world };
424
+ };
425
+ this._getViewport = () => {
426
+ const [viewport3D] = this._getViewportsInfo();
427
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
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
+ });
527
+ };
528
+ this._onNewVolume = () => {
529
+ const viewportsInfo = this._getViewportsInfo();
530
+ this.originalClippingPlanes = [];
531
+ this.sphereStates = [];
532
+ this.edgeLines = {};
533
+ this._initialize3DViewports(viewportsInfo);
534
+ };
535
+ this._rotateCamera = (viewport, centerWorld, axis, angle) => {
536
+ const vtkCamera = viewport.getVtkActiveCamera();
537
+ const viewUp = vtkCamera.getViewUp();
538
+ const focalPoint = vtkCamera.getFocalPoint();
539
+ const position = vtkCamera.getPosition();
540
+ const newPosition = [0, 0, 0];
541
+ const newFocalPoint = [0, 0, 0];
542
+ const newViewUp = [0, 0, 0];
543
+ const transform = mat4.identity(new Float32Array(16));
544
+ mat4.translate(transform, transform, centerWorld);
545
+ mat4.rotate(transform, transform, angle, axis);
546
+ mat4.translate(transform, transform, [
547
+ -centerWorld[0],
548
+ -centerWorld[1],
549
+ -centerWorld[2],
550
+ ]);
551
+ vec3.transformMat4(newPosition, position, transform);
552
+ vec3.transformMat4(newFocalPoint, focalPoint, transform);
553
+ mat4.identity(transform);
554
+ mat4.rotate(transform, transform, angle, axis);
555
+ vec3.transformMat4(newViewUp, viewUp, transform);
556
+ viewport.setCamera({
557
+ position: newPosition,
558
+ viewUp: newViewUp,
559
+ focalPoint: newFocalPoint,
560
+ });
561
+ };
562
+ this.touchDragCallback = this._dragCallback.bind(this);
563
+ this.mouseDragCallback = this._dragCallback.bind(this);
564
+ }
565
+ onSetToolActive() {
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) {
583
+ return;
584
+ }
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();
606
+ }
607
+ };
608
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
609
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
610
+ this._subscribeToViewportNewVolumeSet(viewportsInfo);
611
+ this._initialize3DViewports(viewportsInfo);
612
+ if (this.sphereStates && this.sphereStates.length > 0) {
613
+ this.setHandlesVisible(true);
614
+ }
615
+ else {
616
+ this.originalClippingPlanes = [];
617
+ this._initialize3DViewports(viewportsInfo);
618
+ }
619
+ }
620
+ }
621
+ onSetToolDisabled() {
622
+ this._resizeObservers.forEach((resizeObserver, viewportId) => {
623
+ resizeObserver.disconnect();
624
+ this._resizeObservers.delete(viewportId);
625
+ });
626
+ if (this._viewportAddedListener) {
627
+ eventTarget.removeEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._viewportAddedListener);
628
+ this._viewportAddedListener = null;
629
+ }
630
+ const viewportsInfo = this._getViewportsInfo();
631
+ this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
632
+ }
633
+ setHandlesVisible(visible) {
634
+ this.configuration.showHandles = visible;
635
+ if (visible) {
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
+ });
660
+ this._updateCornerSpheres();
661
+ }
662
+ this._updateHandlesVisibility();
663
+ const viewportsInfo = this._getViewportsInfo();
664
+ const [viewport3D] = viewportsInfo;
665
+ const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
666
+ const viewport = renderingEngine.getViewport(viewport3D.viewportId);
667
+ viewport.render();
668
+ }
669
+ getHandlesVisible() {
670
+ return this.configuration.showHandles;
671
+ }
672
+ getClippingPlanesVisible() {
673
+ return this.configuration.showClippingPlanes;
674
+ }
675
+ setClippingPlanesVisible(visible) {
676
+ this.configuration.showClippingPlanes = visible;
677
+ const viewport = this._getViewport();
678
+ this._updateClippingPlanes(viewport);
679
+ viewport.render();
680
+ }
681
+ _dragCallback(evt) {
682
+ const { element, currentPoints, lastPoints } = evt.detail;
683
+ if (this.draggingSphereIndex !== null) {
684
+ this._onMouseMoveSphere(evt);
685
+ }
686
+ else {
687
+ const currentPointsCanvas = currentPoints.canvas;
688
+ const lastPointsCanvas = lastPoints.canvas;
689
+ const { rotateIncrementDegrees } = this.configuration;
690
+ const enabledElement = getEnabledElement(element);
691
+ const { viewport } = enabledElement;
692
+ const camera = viewport.getCamera();
693
+ const width = element.clientWidth;
694
+ const height = element.clientHeight;
695
+ const normalizedPosition = [
696
+ currentPointsCanvas[0] / width,
697
+ currentPointsCanvas[1] / height,
698
+ ];
699
+ const normalizedPreviousPosition = [
700
+ lastPointsCanvas[0] / width,
701
+ lastPointsCanvas[1] / height,
702
+ ];
703
+ const center = [width * 0.5, height * 0.5];
704
+ const centerWorld = viewport.canvasToWorld(center);
705
+ const normalizedCenter = [0.5, 0.5];
706
+ const radsq = (1.0 + Math.abs(normalizedCenter[0])) ** 2.0;
707
+ const op = [normalizedPreviousPosition[0], 0, 0];
708
+ const oe = [normalizedPosition[0], 0, 0];
709
+ const opsq = op[0] ** 2;
710
+ const oesq = oe[0] ** 2;
711
+ const lop = opsq > radsq ? 0 : Math.sqrt(radsq - opsq);
712
+ const loe = oesq > radsq ? 0 : Math.sqrt(radsq - oesq);
713
+ const nop = [op[0], 0, lop];
714
+ vtkMath.normalize(nop);
715
+ const noe = [oe[0], 0, loe];
716
+ vtkMath.normalize(noe);
717
+ const dot = vtkMath.dot(nop, noe);
718
+ if (Math.abs(dot) > 0.0001) {
719
+ const angleX = -2 *
720
+ Math.acos(vtkMath.clampValue(dot, -1.0, 1.0)) *
721
+ Math.sign(normalizedPosition[0] - normalizedPreviousPosition[0]) *
722
+ rotateIncrementDegrees;
723
+ const upVec = camera.viewUp;
724
+ const atV = camera.viewPlaneNormal;
725
+ const rightV = [0, 0, 0];
726
+ const forwardV = [0, 0, 0];
727
+ vtkMath.cross(upVec, atV, rightV);
728
+ vtkMath.normalize(rightV);
729
+ vtkMath.cross(atV, rightV, forwardV);
730
+ vtkMath.normalize(forwardV);
731
+ vtkMath.normalize(upVec);
732
+ this._rotateCamera(viewport, centerWorld, forwardV, angleX);
733
+ const angleY = (normalizedPreviousPosition[1] - normalizedPosition[1]) *
734
+ rotateIncrementDegrees;
735
+ this._rotateCamera(viewport, centerWorld, rightV, angleY);
736
+ }
737
+ viewport.render();
738
+ }
739
+ }
740
+ _updateClippingPlanes(viewport) {
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
+ }
749
+ const actor = actorEntry.actor;
750
+ const mapper = actor.getMapper();
751
+ const matrix = actor.getMatrix();
752
+ if (!this.configuration.showClippingPlanes) {
753
+ mapper.removeAllClippingPlanes();
754
+ return;
755
+ }
756
+ const rot = mat3.create();
757
+ mat3.fromMat4(rot, matrix);
758
+ const normalMatrix = mat3.create();
759
+ mat3.invert(normalMatrix, rot);
760
+ mat3.transpose(normalMatrix, normalMatrix);
761
+ const originalPlanes = this.originalClippingPlanes;
762
+ if (!originalPlanes || !originalPlanes.length) {
763
+ return;
764
+ }
765
+ mapper.removeAllClippingPlanes();
766
+ const transformedOrigins = [];
767
+ const transformedNormals = [];
768
+ for (let i = 0; i < originalPlanes.length; ++i) {
769
+ const plane = originalPlanes[i];
770
+ const oVec = vec3.create();
771
+ vec3.transformMat4(oVec, new Float32Array(plane.origin), matrix);
772
+ const o = [oVec[0], oVec[1], oVec[2]];
773
+ const nVec = vec3.create();
774
+ vec3.transformMat3(nVec, new Float32Array(plane.normal), normalMatrix);
775
+ vec3.normalize(nVec, nVec);
776
+ const n = [nVec[0], nVec[1], nVec[2]];
777
+ transformedOrigins.push(o);
778
+ transformedNormals.push(n);
779
+ }
780
+ for (let i = 0; i < transformedOrigins.length; ++i) {
781
+ const planeInstance = vtkPlane.newInstance({
782
+ origin: transformedOrigins[i],
783
+ normal: transformedNormals[i],
784
+ });
785
+ mapper.addClippingPlane(planeInstance);
786
+ }
787
+ }
788
+ _updateHandlesVisibility() {
789
+ this.sphereStates.forEach((state) => {
790
+ if (state.sphereActor) {
791
+ state.sphereActor.setVisibility(this.configuration.showHandles);
792
+ }
793
+ });
794
+ Object.values(this.edgeLines).forEach(({ actor }) => {
795
+ if (actor) {
796
+ actor.setVisibility(this.configuration.showHandles);
797
+ }
798
+ });
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
+ }
829
+ _addSphere(viewport, point, axis, position, cornerKey = null, adaptiveRadius) {
830
+ const uid = cornerKey ? `corner_${cornerKey}` : `${axis}_${position}`;
831
+ const sphereState = this.sphereStates.find((s) => s.uid === uid);
832
+ if (sphereState) {
833
+ return;
834
+ }
835
+ const sphereSource = vtkSphereSource.newInstance();
836
+ sphereSource.setCenter(point);
837
+ sphereSource.setRadius(adaptiveRadius);
838
+ const sphereMapper = vtkMapper.newInstance();
839
+ sphereMapper.setInputConnection(sphereSource.getOutputPort());
840
+ const sphereActor = vtkActor.newInstance();
841
+ sphereActor.setMapper(sphereMapper);
842
+ let color = [0.0, 1.0, 0.0];
843
+ const sphereColors = this.configuration.sphereColors || {};
844
+ if (cornerKey) {
845
+ color = sphereColors.CORNERS || [0.0, 0.0, 1.0];
846
+ }
847
+ else if (axis === 'z') {
848
+ color = sphereColors.AXIAL || [1.0, 0.0, 0.0];
849
+ }
850
+ else if (axis === 'x') {
851
+ color = sphereColors.SAGITTAL || [1.0, 1.0, 0.0];
852
+ }
853
+ else if (axis === 'y') {
854
+ color = sphereColors.CORONAL || [0.0, 1.0, 0.0];
855
+ }
856
+ const idx = this.sphereStates.findIndex((s) => s.uid === uid);
857
+ if (idx === -1) {
858
+ this.sphereStates.push({
859
+ point: point.slice(),
860
+ axis,
861
+ uid,
862
+ sphereSource,
863
+ sphereActor,
864
+ isCorner: !!cornerKey,
865
+ color,
866
+ });
867
+ }
868
+ else {
869
+ this.sphereStates[idx].point = point.slice();
870
+ this.sphereStates[idx].sphereSource = sphereSource;
871
+ }
872
+ const existingActors = viewport.getActors();
873
+ const existing = existingActors.find((a) => a.uid === uid);
874
+ if (existing) {
875
+ return;
876
+ }
877
+ sphereActor.getProperty().setColor(color);
878
+ sphereActor.setVisibility(this.configuration.showHandles);
879
+ viewport.addActor({ actor: sphereActor, uid: uid });
880
+ }
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));
890
+ }
891
+ _updateClippingPlanesFromFaceSpheres(viewport) {
892
+ const mapper = viewport.getDefaultActor().actor.getMapper();
893
+ this.originalClippingPlanes[0].origin = [
894
+ ...this.sphereStates[SPHEREINDEX.XMIN].point,
895
+ ];
896
+ this.originalClippingPlanes[1].origin = [
897
+ ...this.sphereStates[SPHEREINDEX.XMAX].point,
898
+ ];
899
+ this.originalClippingPlanes[2].origin = [
900
+ ...this.sphereStates[SPHEREINDEX.YMIN].point,
901
+ ];
902
+ this.originalClippingPlanes[3].origin = [
903
+ ...this.sphereStates[SPHEREINDEX.YMAX].point,
904
+ ];
905
+ this.originalClippingPlanes[4].origin = [
906
+ ...this.sphereStates[SPHEREINDEX.ZMIN].point,
907
+ ];
908
+ this.originalClippingPlanes[5].origin = [
909
+ ...this.sphereStates[SPHEREINDEX.ZMAX].point,
910
+ ];
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
+ }
921
+ }
922
+ _updateCornerSpheresFromFaces() {
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];
929
+ const corners = [
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] },
938
+ ];
939
+ for (const corner of corners) {
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();
947
+ }
948
+ }
949
+ }
950
+ _updateFaceSpheresFromCorners() {
951
+ const corners = [
952
+ this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMIN].point,
953
+ this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMAX].point,
954
+ this.sphereStates[SPHEREINDEX.XMIN_YMAX_ZMIN].point,
955
+ this.sphereStates[SPHEREINDEX.XMIN_YMAX_ZMAX].point,
956
+ this.sphereStates[SPHEREINDEX.XMAX_YMIN_ZMIN].point,
957
+ this.sphereStates[SPHEREINDEX.XMAX_YMIN_ZMAX].point,
958
+ this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMIN].point,
959
+ this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMAX].point,
960
+ ];
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
+ });
1009
+ }
1010
+ _updateCornerSpheres() {
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];
1017
+ const corners = [
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] },
1026
+ ];
1027
+ for (const corner of corners) {
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();
1035
+ }
1036
+ }
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
+ });
1048
+ }
1049
+ _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1050
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
1051
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1052
+ const { element } = viewport;
1053
+ element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1054
+ });
1055
+ }
1056
+ _subscribeToViewportNewVolumeSet(viewports) {
1057
+ viewports.forEach(({ viewportId, renderingEngineId }) => {
1058
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1059
+ const { element } = viewport;
1060
+ element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1061
+ });
1062
+ }
1063
+ }
1064
+ VolumeCroppingTool.toolName = 'VolumeCropping';
1065
+ export default VolumeCroppingTool;