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