@cornerstonejs/tools 4.18.3 → 4.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/tools/VolumeCroppingControlTool.d.ts +10 -35
- package/dist/esm/tools/VolumeCroppingControlTool.js +179 -699
- package/dist/esm/tools/VolumeCroppingTool.d.ts +32 -32
- package/dist/esm/tools/VolumeCroppingTool.js +775 -525
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +7 -0
- package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +34 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +6 -0
- package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +7 -0
- package/dist/esm/utilities/draw3D/index.d.ts +2 -0
- package/dist/esm/utilities/draw3D/index.js +2 -0
- package/dist/esm/utilities/index.d.ts +2 -1
- package/dist/esm/utilities/index.js +2 -1
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +6 -0
- package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +37 -0
- package/dist/esm/utilities/volumeCropping/constants.d.ts +31 -0
- package/dist/esm/utilities/volumeCropping/constants.js +31 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +6 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +9 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +50 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +1 -0
- package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +13 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +2 -0
- package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +19 -0
- package/dist/esm/utilities/volumeCropping/index.d.ts +9 -0
- package/dist/esm/utilities/volumeCropping/index.js +9 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +8 -0
- package/dist/esm/utilities/volumeCropping/parseCornerKey.js +11 -0
- package/dist/esm/utilities/volumeCropping/types.d.ts +5 -0
- package/dist/esm/utilities/volumeCropping/types.js +0 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,4 @@
|
|
|
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';
|
|
4
2
|
import { mat3, mat4, vec3 } from 'gl-matrix';
|
|
5
3
|
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
|
|
6
4
|
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
|
|
@@ -11,30 +9,8 @@ import { BaseTool } from './base';
|
|
|
11
9
|
import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, triggerEvent, eventTarget, } from '@cornerstonejs/core';
|
|
12
10
|
import { getToolGroup } from '../store/ToolGroupManager';
|
|
13
11
|
import { Events } from '../enums';
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
};
|
|
12
|
+
import { PLANEINDEX, SPHEREINDEX, NUM_CLIPPING_PLANES, extractVolumeDirectionVectors, parseCornerKey, copyClippingPlanes, } from '../utilities/volumeCropping';
|
|
13
|
+
import { addLine3DBetweenPoints, calculateAdaptiveSphereRadius, } from '../utilities/draw3D';
|
|
38
14
|
class VolumeCroppingTool extends BaseTool {
|
|
39
15
|
constructor(toolProps = {}, defaultToolProps = {
|
|
40
16
|
configuration: {
|
|
@@ -56,6 +32,7 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
56
32
|
grabSpherePixelDistance: 20,
|
|
57
33
|
rotateIncrementDegrees: 2,
|
|
58
34
|
rotateSampleDistanceFactor: 2,
|
|
35
|
+
rotateClippingPlanesIncrementDegrees: 5,
|
|
59
36
|
},
|
|
60
37
|
}) {
|
|
61
38
|
super(toolProps, defaultToolProps);
|
|
@@ -63,16 +40,15 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
63
40
|
this._hasResolutionChanged = false;
|
|
64
41
|
this.originalClippingPlanes = [];
|
|
65
42
|
this.draggingSphereIndex = null;
|
|
66
|
-
this.
|
|
43
|
+
this.rotatePlanesOnDrag = false;
|
|
67
44
|
this.cornerDragOffset = null;
|
|
68
45
|
this.faceDragOffset = null;
|
|
46
|
+
this.volumeDirectionVectors = null;
|
|
69
47
|
this.sphereStates = [];
|
|
70
48
|
this.edgeLines = {};
|
|
71
49
|
this.onSetToolConfiguration = () => {
|
|
72
|
-
console.debug('Setting tool settoolconfiguration : volumeCropping');
|
|
73
50
|
};
|
|
74
51
|
this.onSetToolEnabled = () => {
|
|
75
|
-
console.debug('Setting tool enabled: volumeCropping');
|
|
76
52
|
};
|
|
77
53
|
this.onCameraModified = (evt) => {
|
|
78
54
|
const { element } = evt.currentTarget
|
|
@@ -115,9 +91,16 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
115
91
|
this.faceDragOffset = null;
|
|
116
92
|
}
|
|
117
93
|
else {
|
|
118
|
-
const
|
|
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
|
+
];
|
|
119
100
|
this.faceDragOffset =
|
|
120
|
-
|
|
101
|
+
delta[0] * directionVector[0] +
|
|
102
|
+
delta[1] * directionVector[1] +
|
|
103
|
+
delta[2] * directionVector[2];
|
|
121
104
|
this.cornerDragOffset = null;
|
|
122
105
|
}
|
|
123
106
|
return true;
|
|
@@ -172,118 +155,143 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
172
155
|
return false;
|
|
173
156
|
}
|
|
174
157
|
if (sphereState.isCorner) {
|
|
175
|
-
const newCorner = this.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
}
|
|
180
228
|
this._updateCornerSpheres();
|
|
229
|
+
this._updateFaceSpheresFromCorners();
|
|
181
230
|
}
|
|
182
231
|
else {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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);
|
|
191
250
|
this._updateCornerSpheresFromFaces();
|
|
192
251
|
this._updateFaceSpheresFromCorners();
|
|
193
252
|
this._updateCornerSpheres();
|
|
194
253
|
}
|
|
195
254
|
this._updateClippingPlanesFromFaceSpheres(viewport);
|
|
196
255
|
viewport.render();
|
|
197
|
-
this.
|
|
256
|
+
this._notifyClippingPlanesChanged();
|
|
198
257
|
return true;
|
|
199
258
|
};
|
|
200
259
|
this._onControlToolChange = (evt) => {
|
|
201
260
|
const viewport = this._getViewport();
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
originalClippingPlanes: this.originalClippingPlanes,
|
|
205
|
-
viewportId: viewport.id,
|
|
206
|
-
renderingEngineId: viewport.renderingEngineId,
|
|
207
|
-
seriesInstanceUID: this.seriesInstanceUID,
|
|
208
|
-
});
|
|
261
|
+
if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
|
|
262
|
+
return;
|
|
209
263
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
}
|
|
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();
|
|
273
270
|
this._updateCornerSpheres();
|
|
271
|
+
const mapper = this._getVolumeMapper(viewport);
|
|
272
|
+
if (mapper) {
|
|
273
|
+
this._applyClippingPlanesToMapper(mapper);
|
|
274
|
+
}
|
|
274
275
|
viewport.render();
|
|
276
|
+
this._notifyClippingPlanesChanged(viewport);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this._notifyClippingPlanesChanged(viewport);
|
|
275
280
|
}
|
|
276
281
|
};
|
|
277
282
|
this._getViewportsInfo = () => {
|
|
278
|
-
const
|
|
279
|
-
return
|
|
283
|
+
const toolGroup = getToolGroup(this.toolGroupId);
|
|
284
|
+
return toolGroup?.viewportsInfo || [];
|
|
280
285
|
};
|
|
281
286
|
this._initialize3DViewports = (viewportsInfo) => {
|
|
282
|
-
if (!viewportsInfo
|
|
287
|
+
if (!viewportsInfo?.length || !viewportsInfo[0]) {
|
|
283
288
|
console.warn('VolumeCroppingTool: No viewportsInfo available for initialization of volumecroppingtool.');
|
|
284
289
|
return;
|
|
285
290
|
}
|
|
286
291
|
const viewport = this._getViewport();
|
|
292
|
+
if (!viewport) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
287
295
|
const volumeActors = viewport.getActors();
|
|
288
296
|
if (!volumeActors || volumeActors.length === 0) {
|
|
289
297
|
console.warn('VolumeCroppingTool: No volume actors found in the viewport.');
|
|
@@ -295,78 +303,85 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
295
303
|
return;
|
|
296
304
|
}
|
|
297
305
|
this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
|
|
298
|
-
|
|
306
|
+
this.volumeDirectionVectors = extractVolumeDirectionVectors(imageData);
|
|
307
|
+
const { xDir, yDir, zDir } = this.volumeDirectionVectors;
|
|
308
|
+
const dimensions = imageData.getDimensions();
|
|
299
309
|
const cropFactor = this.configuration.initialCropFactor || 0.1;
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
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,
|
|
313
328
|
});
|
|
314
|
-
const
|
|
315
|
-
origin:
|
|
316
|
-
normal: [-
|
|
329
|
+
const planeXMax = vtkPlane.newInstance({
|
|
330
|
+
origin: faceXMax,
|
|
331
|
+
normal: [-xDir[0], -xDir[1], -xDir[2]],
|
|
317
332
|
});
|
|
318
|
-
const
|
|
319
|
-
origin:
|
|
320
|
-
normal:
|
|
333
|
+
const planeYMin = vtkPlane.newInstance({
|
|
334
|
+
origin: faceYMin,
|
|
335
|
+
normal: yDir,
|
|
321
336
|
});
|
|
322
|
-
const
|
|
323
|
-
origin:
|
|
324
|
-
normal: [0, -1,
|
|
337
|
+
const planeYMax = vtkPlane.newInstance({
|
|
338
|
+
origin: faceYMax,
|
|
339
|
+
normal: [-yDir[0], -yDir[1], -yDir[2]],
|
|
325
340
|
});
|
|
326
|
-
const
|
|
327
|
-
origin:
|
|
328
|
-
normal:
|
|
341
|
+
const planeZMin = vtkPlane.newInstance({
|
|
342
|
+
origin: faceZMin,
|
|
343
|
+
normal: zDir,
|
|
329
344
|
});
|
|
330
|
-
const
|
|
331
|
-
origin:
|
|
332
|
-
normal: [0,
|
|
345
|
+
const planeZMax = vtkPlane.newInstance({
|
|
346
|
+
origin: faceZMax,
|
|
347
|
+
normal: [-zDir[0], -zDir[1], -zDir[2]],
|
|
333
348
|
});
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
planes.push(planeZmax);
|
|
349
|
+
const planes = [
|
|
350
|
+
planeXMin,
|
|
351
|
+
planeXMax,
|
|
352
|
+
planeYMin,
|
|
353
|
+
planeYMax,
|
|
354
|
+
planeZMin,
|
|
355
|
+
planeZMax,
|
|
356
|
+
];
|
|
343
357
|
const originalPlanes = planes.map((plane) => ({
|
|
344
358
|
origin: [...plane.getOrigin()],
|
|
345
359
|
normal: [...plane.getNormal()],
|
|
346
360
|
}));
|
|
347
361
|
this.originalClippingPlanes = originalPlanes;
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
this._addSphere(viewport,
|
|
357
|
-
this._addSphere(viewport,
|
|
358
|
-
this._addSphere(viewport,
|
|
359
|
-
this._addSphere(viewport,
|
|
360
|
-
this._addSphere(viewport,
|
|
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);
|
|
361
376
|
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],
|
|
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]),
|
|
370
385
|
];
|
|
371
386
|
const cornerKeys = [
|
|
372
387
|
'XMIN_YMIN_ZMIN',
|
|
@@ -395,21 +410,24 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
395
410
|
['XMAX_YMIN_ZMIN', 'XMAX_YMIN_ZMAX'],
|
|
396
411
|
['XMAX_YMAX_ZMIN', 'XMAX_YMAX_ZMAX'],
|
|
397
412
|
];
|
|
398
|
-
edgeCornerPairs.forEach(([key1, key2]
|
|
413
|
+
edgeCornerPairs.forEach(([key1, key2]) => {
|
|
399
414
|
const state1 = this.sphereStates.find((s) => s.uid === `corner_${key1}`);
|
|
400
415
|
const state2 = this.sphereStates.find((s) => s.uid === `corner_${key2}`);
|
|
401
416
|
if (state1 && state2) {
|
|
402
417
|
const uid = `edge_${key1}_${key2}`;
|
|
403
|
-
const { actor, source } =
|
|
418
|
+
const { actor, source } = addLine3DBetweenPoints(viewport, state1.point, state2.point, [0.7, 0.7, 0.7], uid, this.configuration.showHandles);
|
|
404
419
|
this.edgeLines[uid] = { actor, source, key1, key2 };
|
|
405
420
|
}
|
|
406
421
|
});
|
|
407
|
-
mapper
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
mapper.addClippingPlane(
|
|
411
|
-
mapper.addClippingPlane(
|
|
412
|
-
mapper.addClippingPlane(
|
|
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);
|
|
413
431
|
eventTarget.addEventListener(Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, (evt) => {
|
|
414
432
|
this._onControlToolChange(evt);
|
|
415
433
|
});
|
|
@@ -423,107 +441,14 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
423
441
|
return { viewport, world };
|
|
424
442
|
};
|
|
425
443
|
this._getViewport = () => {
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
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];
|
|
444
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
445
|
+
if (!viewportsInfo || viewportsInfo.length === 0) {
|
|
446
|
+
return null;
|
|
507
447
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
});
|
|
448
|
+
const [viewport3D] = viewportsInfo;
|
|
449
|
+
const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
|
|
450
|
+
const viewport = renderingEngine?.getViewport(viewport3D.viewportId);
|
|
451
|
+
return viewport || null;
|
|
527
452
|
};
|
|
528
453
|
this._onNewVolume = () => {
|
|
529
454
|
const viewportsInfo = this._getViewportsInfo();
|
|
@@ -532,6 +457,207 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
532
457
|
this.edgeLines = {};
|
|
533
458
|
this._initialize3DViewports(viewportsInfo);
|
|
534
459
|
};
|
|
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
|
+
};
|
|
535
661
|
this._rotateCamera = (viewport, centerWorld, axis, angle) => {
|
|
536
662
|
const vtkCamera = viewport.getVtkActiveCamera();
|
|
537
663
|
const viewUp = vtkCamera.getViewUp();
|
|
@@ -563,59 +689,55 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
563
689
|
this.mouseDragCallback = this._dragCallback.bind(this);
|
|
564
690
|
}
|
|
565
691
|
onSetToolActive() {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
this.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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) {
|
|
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) {
|
|
583
704
|
return;
|
|
584
705
|
}
|
|
585
|
-
const {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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();
|
|
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);
|
|
606
714
|
}
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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 = [];
|
|
611
728
|
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);
|
|
612
737
|
if (this.sphereStates && this.sphereStates.length > 0) {
|
|
613
|
-
this.
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
this.originalClippingPlanes = [];
|
|
617
|
-
this._initialize3DViewports(viewportsInfo);
|
|
738
|
+
this._updateHandlesVisibility();
|
|
618
739
|
}
|
|
740
|
+
viewport.render();
|
|
619
741
|
}
|
|
620
742
|
}
|
|
621
743
|
onSetToolDisabled() {
|
|
@@ -633,30 +755,7 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
633
755
|
setHandlesVisible(visible) {
|
|
634
756
|
this.configuration.showHandles = visible;
|
|
635
757
|
if (visible) {
|
|
636
|
-
this.
|
|
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
|
-
});
|
|
758
|
+
this._updateFaceSpheresFromClippingPlanes();
|
|
660
759
|
this._updateCornerSpheres();
|
|
661
760
|
}
|
|
662
761
|
this._updateHandlesVisibility();
|
|
@@ -676,14 +775,38 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
676
775
|
this.configuration.showClippingPlanes = visible;
|
|
677
776
|
const viewport = this._getViewport();
|
|
678
777
|
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
|
+
}
|
|
679
787
|
viewport.render();
|
|
680
788
|
}
|
|
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
|
+
}
|
|
681
799
|
_dragCallback(evt) {
|
|
682
800
|
const { element, currentPoints, lastPoints } = evt.detail;
|
|
683
801
|
if (this.draggingSphereIndex !== null) {
|
|
684
802
|
this._onMouseMoveSphere(evt);
|
|
685
803
|
}
|
|
686
804
|
else {
|
|
805
|
+
const shiftKey = evt.detail.event?.shiftKey ?? false;
|
|
806
|
+
if (this.rotatePlanesOnDrag === true || shiftKey) {
|
|
807
|
+
this._rotateClippingPlanes(evt);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
687
810
|
const currentPointsCanvas = currentPoints.canvas;
|
|
688
811
|
const lastPointsCanvas = lastPoints.canvas;
|
|
689
812
|
const { rotateIncrementDegrees } = this.configuration;
|
|
@@ -739,13 +862,6 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
739
862
|
}
|
|
740
863
|
_updateClippingPlanes(viewport) {
|
|
741
864
|
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
865
|
const actor = actorEntry.actor;
|
|
750
866
|
const mapper = actor.getMapper();
|
|
751
867
|
const matrix = actor.getMatrix();
|
|
@@ -797,35 +913,6 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
797
913
|
}
|
|
798
914
|
});
|
|
799
915
|
}
|
|
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
916
|
_addSphere(viewport, point, axis, position, cornerKey = null, adaptiveRadius) {
|
|
830
917
|
const uid = cornerKey ? `corner_${cornerKey}` : `${axis}_${position}`;
|
|
831
918
|
const sphereState = this.sphereStates.find((s) => s.uid === uid);
|
|
@@ -854,9 +941,10 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
854
941
|
color = sphereColors.CORONAL || [0.0, 1.0, 0.0];
|
|
855
942
|
}
|
|
856
943
|
const idx = this.sphereStates.findIndex((s) => s.uid === uid);
|
|
944
|
+
const pointCopy = [point[0], point[1], point[2]];
|
|
857
945
|
if (idx === -1) {
|
|
858
946
|
this.sphereStates.push({
|
|
859
|
-
point:
|
|
947
|
+
point: pointCopy,
|
|
860
948
|
axis,
|
|
861
949
|
uid,
|
|
862
950
|
sphereSource,
|
|
@@ -866,88 +954,144 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
866
954
|
});
|
|
867
955
|
}
|
|
868
956
|
else {
|
|
869
|
-
this.sphereStates[idx].point =
|
|
957
|
+
this.sphereStates[idx].point = pointCopy;
|
|
870
958
|
this.sphereStates[idx].sphereSource = sphereSource;
|
|
871
959
|
}
|
|
872
|
-
const existingActors = viewport.getActors();
|
|
873
|
-
const existing = existingActors.find((a) => a.uid === uid);
|
|
874
|
-
if (existing) {
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
960
|
sphereActor.getProperty().setColor(color);
|
|
878
961
|
sphereActor.setVisibility(this.configuration.showHandles);
|
|
879
962
|
viewport.addActor({ actor: sphereActor, uid: uid });
|
|
880
963
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
+
}
|
|
890
1018
|
}
|
|
891
1019
|
_updateClippingPlanesFromFaceSpheres(viewport) {
|
|
892
1020
|
const mapper = viewport.getDefaultActor().actor.getMapper();
|
|
893
|
-
this.originalClippingPlanes[
|
|
1021
|
+
this.originalClippingPlanes[PLANEINDEX.XMIN].origin = [
|
|
894
1022
|
...this.sphereStates[SPHEREINDEX.XMIN].point,
|
|
895
1023
|
];
|
|
896
|
-
this.originalClippingPlanes[
|
|
1024
|
+
this.originalClippingPlanes[PLANEINDEX.XMAX].origin = [
|
|
897
1025
|
...this.sphereStates[SPHEREINDEX.XMAX].point,
|
|
898
1026
|
];
|
|
899
|
-
this.originalClippingPlanes[
|
|
1027
|
+
this.originalClippingPlanes[PLANEINDEX.YMIN].origin = [
|
|
900
1028
|
...this.sphereStates[SPHEREINDEX.YMIN].point,
|
|
901
1029
|
];
|
|
902
|
-
this.originalClippingPlanes[
|
|
1030
|
+
this.originalClippingPlanes[PLANEINDEX.YMAX].origin = [
|
|
903
1031
|
...this.sphereStates[SPHEREINDEX.YMAX].point,
|
|
904
1032
|
];
|
|
905
|
-
this.originalClippingPlanes[
|
|
1033
|
+
this.originalClippingPlanes[PLANEINDEX.ZMIN].origin = [
|
|
906
1034
|
...this.sphereStates[SPHEREINDEX.ZMIN].point,
|
|
907
1035
|
];
|
|
908
|
-
this.originalClippingPlanes[
|
|
1036
|
+
this.originalClippingPlanes[PLANEINDEX.ZMAX].origin = [
|
|
909
1037
|
...this.sphereStates[SPHEREINDEX.ZMAX].point,
|
|
910
1038
|
];
|
|
911
|
-
|
|
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
|
-
}
|
|
1039
|
+
this._applyClippingPlanesToMapper(mapper);
|
|
921
1040
|
}
|
|
922
1041
|
_updateCornerSpheresFromFaces() {
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
const
|
|
928
|
-
const
|
|
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;
|
|
929
1051
|
const corners = [
|
|
930
|
-
{
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
{
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
+
},
|
|
938
1084
|
];
|
|
939
1085
|
for (const corner of corners) {
|
|
940
|
-
const
|
|
941
|
-
if (
|
|
942
|
-
|
|
943
|
-
state.point[1] = corner.pos[1];
|
|
944
|
-
state.point[2] = corner.pos[2];
|
|
945
|
-
state.sphereSource.setCenter(...state.point);
|
|
946
|
-
state.sphereSource.modified();
|
|
1086
|
+
const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
|
|
1087
|
+
if (stateIndex !== -1) {
|
|
1088
|
+
this._updateSpherePosition(stateIndex, corner.pos);
|
|
947
1089
|
}
|
|
948
1090
|
}
|
|
949
1091
|
}
|
|
950
1092
|
_updateFaceSpheresFromCorners() {
|
|
1093
|
+
if (!this.volumeDirectionVectors)
|
|
1094
|
+
return;
|
|
951
1095
|
const corners = [
|
|
952
1096
|
this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMIN].point,
|
|
953
1097
|
this.sphereStates[SPHEREINDEX.XMIN_YMIN_ZMAX].point,
|
|
@@ -958,93 +1102,100 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
958
1102
|
this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMIN].point,
|
|
959
1103
|
this.sphereStates[SPHEREINDEX.XMAX_YMAX_ZMAX].point,
|
|
960
1104
|
];
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
];
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
SPHEREINDEX.ZMAX,
|
|
1004
|
-
].forEach((idx) => {
|
|
1005
|
-
const s = this.sphereStates[idx];
|
|
1006
|
-
s.sphereSource.setCenter(...s.point);
|
|
1007
|
-
s.sphereSource.modified();
|
|
1008
|
-
});
|
|
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);
|
|
1009
1147
|
}
|
|
1010
1148
|
_updateCornerSpheres() {
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
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;
|
|
1017
1158
|
const corners = [
|
|
1018
|
-
{
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
{
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
+
},
|
|
1026
1191
|
];
|
|
1027
1192
|
for (const corner of corners) {
|
|
1028
|
-
const
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1031
|
-
state.point[1] = corner.pos[1];
|
|
1032
|
-
state.point[2] = corner.pos[2];
|
|
1033
|
-
state.sphereSource.setCenter(...state.point);
|
|
1034
|
-
state.sphereSource.modified();
|
|
1193
|
+
const stateIndex = this.sphereStates.findIndex((s) => s.uid === `corner_${corner.key}`);
|
|
1194
|
+
if (stateIndex !== -1) {
|
|
1195
|
+
this._updateSpherePosition(stateIndex, corner.pos);
|
|
1035
1196
|
}
|
|
1036
1197
|
}
|
|
1037
|
-
|
|
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
|
-
});
|
|
1198
|
+
this._updateEdgeLines();
|
|
1048
1199
|
}
|
|
1049
1200
|
_unsubscribeToViewportNewVolumeSet(viewportsInfo) {
|
|
1050
1201
|
viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
|
|
@@ -1060,6 +1211,105 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
1060
1211
|
element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
|
|
1061
1212
|
});
|
|
1062
1213
|
}
|
|
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
|
+
}
|
|
1063
1313
|
}
|
|
1064
1314
|
VolumeCroppingTool.toolName = 'VolumeCropping';
|
|
1065
1315
|
export default VolumeCroppingTool;
|