@cornerstonejs/tools 4.18.4 → 5.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/tools/VolumeCroppingControlTool.d.ts +35 -10
  2. package/dist/esm/tools/VolumeCroppingControlTool.js +699 -179
  3. package/dist/esm/tools/VolumeCroppingTool.d.ts +32 -32
  4. package/dist/esm/tools/VolumeCroppingTool.js +525 -775
  5. package/dist/esm/utilities/index.d.ts +1 -2
  6. package/dist/esm/utilities/index.js +1 -2
  7. package/dist/esm/version.d.ts +1 -1
  8. package/dist/esm/version.js +1 -1
  9. package/package.json +3 -3
  10. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.d.ts +0 -7
  11. package/dist/esm/utilities/draw3D/addLine3DBetweenPoints.js +0 -34
  12. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.d.ts +0 -6
  13. package/dist/esm/utilities/draw3D/calculateAdaptiveSphereRadius.js +0 -7
  14. package/dist/esm/utilities/draw3D/index.d.ts +0 -2
  15. package/dist/esm/utilities/draw3D/index.js +0 -2
  16. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.d.ts +0 -6
  17. package/dist/esm/utilities/volumeCropping/computePlanePlaneIntersection.js +0 -37
  18. package/dist/esm/utilities/volumeCropping/constants.d.ts +0 -31
  19. package/dist/esm/utilities/volumeCropping/constants.js +0 -31
  20. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.d.ts +0 -2
  21. package/dist/esm/utilities/volumeCropping/copyClippingPlanes.js +0 -6
  22. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.d.ts +0 -9
  23. package/dist/esm/utilities/volumeCropping/extractVolumeDirectionVectors.js +0 -9
  24. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.d.ts +0 -5
  25. package/dist/esm/utilities/volumeCropping/findLineBoundsIntersection.js +0 -50
  26. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.d.ts +0 -1
  27. package/dist/esm/utilities/volumeCropping/getColorKeyForPlaneIndex.js +0 -13
  28. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.d.ts +0 -2
  29. package/dist/esm/utilities/volumeCropping/getOrientationFromNormal.js +0 -19
  30. package/dist/esm/utilities/volumeCropping/index.d.ts +0 -9
  31. package/dist/esm/utilities/volumeCropping/index.js +0 -9
  32. package/dist/esm/utilities/volumeCropping/parseCornerKey.d.ts +0 -8
  33. package/dist/esm/utilities/volumeCropping/parseCornerKey.js +0 -11
  34. package/dist/esm/utilities/volumeCropping/types.d.ts +0 -5
  35. package/dist/esm/utilities/volumeCropping/types.js +0 -0
@@ -1,18 +1,25 @@
1
- import { vec2 } from 'gl-matrix';
1
+ import { vec2, vec3 } from 'gl-matrix';
2
2
  import vtkMath from '@kitware/vtk.js/Common/Core/Math';
3
3
  import { AnnotationTool } from './base';
4
- import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, Enums, CONSTANTS, triggerEvent, eventTarget, convertColorArrayToRgbString, } from '@cornerstonejs/core';
5
- import { getToolGroup } from '../store/ToolGroupManager';
4
+ import { getRenderingEngine, getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, triggerEvent, eventTarget, } from '@cornerstonejs/core';
5
+ import { getToolGroup, getToolGroupForViewport, } from '../store/ToolGroupManager';
6
6
  import { addAnnotation, getAnnotations, removeAnnotation, } from '../stateManagement/annotation/annotationState';
7
7
  import { drawCircle as drawCircleSvg, drawLine as drawLineSvg, } from '../drawingSvg';
8
8
  import { state } from '../store/state';
9
9
  import { Events } from '../enums';
10
10
  import { getViewportIdsWithToolToRender } from '../utilities/viewportFilters';
11
11
  import { resetElementCursor, hideElementCursor, } from '../cursors/elementCursor';
12
+ import liangBarksyClip from '../utilities/math/vec2/liangBarksyClip';
12
13
  import * as lineSegment from '../utilities/math/line';
13
14
  import { isAnnotationLocked } from '../stateManagement/annotation/annotationLocking';
14
15
  import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
15
- import { NUM_CLIPPING_PLANES, LINE_INTERSECTION_TOLERANCE, POINT_PROXIMITY_THRESHOLD_PIXELS, copyClippingPlanes, getColorKeyForPlaneIndex, getOrientationFromNormal, computePlanePlaneIntersection, findLineBoundsIntersection, } from '../utilities/volumeCropping';
16
+ const { RENDERING_DEFAULTS } = CONSTANTS;
17
+ function defaultReferenceLineColor() {
18
+ return 'rgb(0, 200, 0)';
19
+ }
20
+ function defaultReferenceLineControllable() {
21
+ return true;
22
+ }
16
23
  const OPERATION = {
17
24
  DRAG: 1,
18
25
  ROTATE: 2,
@@ -45,7 +52,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
45
52
  },
46
53
  }) {
47
54
  super(toolProps, defaultToolProps);
48
- this.clippingPlanes = [];
55
+ this._virtualAnnotations = [];
56
+ this.sphereStates = [];
57
+ this.draggingSphereIndex = null;
58
+ this.toolCenter = [0, 0, 0];
59
+ this.toolCenterMin = [0, 0, 0];
60
+ this.toolCenterMax = [0, 0, 0];
49
61
  this.initializeViewport = ({ renderingEngineId, viewportId, }) => {
50
62
  if (!renderingEngineId || !viewportId) {
51
63
  console.warn('VolumeCroppingControlTool: Missing renderingEngineId or viewportId');
@@ -56,21 +68,15 @@ class VolumeCroppingControlTool extends AnnotationTool {
56
68
  return;
57
69
  }
58
70
  const { viewport } = enabledElement;
59
- const volumeActors = viewport.getActors();
60
- if (volumeActors && volumeActors.length > 0) {
61
- const imageData = volumeActors[0].actor.getMapper().getInputData();
62
- if (imageData) {
63
- this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
64
- }
65
- }
71
+ this._updateToolCentersFromViewport(viewport);
66
72
  const { element } = viewport;
67
- const { position, focalPoint } = viewport.getCamera();
73
+ const { position, focalPoint, viewPlaneNormal } = viewport.getCamera();
68
74
  let annotations = this._getAnnotations(enabledElement);
69
75
  annotations = this.filterInteractableAnnotationsForElement(element, annotations);
70
76
  if (annotations?.length) {
71
77
  removeAnnotation(annotations[0].annotationUID);
72
78
  }
73
- const orientation = getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
79
+ const orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
74
80
  const annotation = {
75
81
  highlighted: false,
76
82
  metadata: {
@@ -80,11 +86,11 @@ class VolumeCroppingControlTool extends AnnotationTool {
80
86
  },
81
87
  data: {
82
88
  handles: {
83
- activeOperation: null,
84
- clippingPlanes: this.clippingPlanes.length > 0
85
- ? copyClippingPlanes(this.clippingPlanes)
86
- : [],
89
+ toolCenter: this.toolCenter,
90
+ toolCenterMin: this.toolCenterMin,
91
+ toolCenterMax: this.toolCenterMax,
87
92
  },
93
+ activeOperation: null,
88
94
  activeViewportIds: [],
89
95
  viewportId,
90
96
  referenceLines: [],
@@ -92,6 +98,10 @@ class VolumeCroppingControlTool extends AnnotationTool {
92
98
  },
93
99
  };
94
100
  addAnnotation(annotation, element);
101
+ return {
102
+ normal: viewPlaneNormal,
103
+ point: viewport.canvasToWorld([100, 100]),
104
+ };
95
105
  };
96
106
  this._getViewportsInfo = () => {
97
107
  const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
@@ -124,22 +134,139 @@ class VolumeCroppingControlTool extends AnnotationTool {
124
134
  }
125
135
  viewport.render();
126
136
  }
127
- this._initializeViewports(viewportsInfo);
137
+ this._computeToolCenter(viewportsInfo);
138
+ };
139
+ this.computeToolCenter = () => {
140
+ const viewportsInfo = this._getViewportsInfo();
128
141
  };
129
- this._initializeViewports = (viewportsInfo) => {
130
- if (!viewportsInfo?.length || !viewportsInfo[0]) {
131
- console.warn('VolumeCroppingControlTool: No valid viewportsInfo for initialization.');
142
+ this._computeToolCenter = (viewportsInfo) => {
143
+ if (!viewportsInfo || !viewportsInfo[0]) {
144
+ console.warn(' _computeToolCenter : No valid viewportsInfo for computeToolCenter.');
132
145
  return;
133
146
  }
134
- viewportsInfo.forEach((vpInfo) => {
135
- this.initializeViewport(vpInfo);
147
+ const orientationIds = ['AXIAL', 'CORONAL', 'SAGITTAL'];
148
+ const presentOrientations = viewportsInfo
149
+ .map((vp) => {
150
+ if (vp.renderingEngineId) {
151
+ const renderingEngine = getRenderingEngine(vp.renderingEngineId);
152
+ const viewport = renderingEngine.getViewport(vp.viewportId);
153
+ if (viewport && viewport.getCamera) {
154
+ const orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
155
+ if (orientation) {
156
+ return orientation;
157
+ }
158
+ }
159
+ }
160
+ return null;
161
+ })
162
+ .filter(Boolean);
163
+ const missingOrientation = orientationIds.find((id) => !presentOrientations.includes(id));
164
+ const presentNormals = [];
165
+ const presentCenters = [];
166
+ const presentViewportInfos = viewportsInfo.filter((vp) => {
167
+ let orientation = null;
168
+ if (vp.renderingEngineId) {
169
+ const renderingEngine = getRenderingEngine(vp.renderingEngineId);
170
+ const viewport = renderingEngine.getViewport(vp.viewportId);
171
+ if (viewport && viewport.getCamera) {
172
+ orientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
173
+ }
174
+ }
175
+ return orientation && orientationIds.includes(orientation);
136
176
  });
137
- triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
177
+ presentViewportInfos.forEach((vpInfo) => {
178
+ const { normal, point } = this.initializeViewport(vpInfo);
179
+ presentNormals.push(normal);
180
+ presentCenters.push(point);
181
+ });
182
+ if (presentViewportInfos.length === 2 && missingOrientation) {
183
+ const virtualNormal = [0, 0, 0];
184
+ vec3.cross(virtualNormal, presentNormals[0], presentNormals[1]);
185
+ vec3.normalize(virtualNormal, virtualNormal);
186
+ const virtualCenter = [
187
+ (presentCenters[0][0] + presentCenters[1][0]) / 2,
188
+ (presentCenters[0][1] + presentCenters[1][1]) / 2,
189
+ (presentCenters[0][2] + presentCenters[1][2]) / 2,
190
+ ];
191
+ const orientation = null;
192
+ const virtualAnnotation = {
193
+ highlighted: false,
194
+ metadata: {
195
+ cameraPosition: [...virtualCenter],
196
+ cameraFocalPoint: [...virtualCenter],
197
+ toolName: this.getToolName(),
198
+ },
199
+ data: {
200
+ handles: {
201
+ activeOperation: null,
202
+ toolCenter: this.toolCenter,
203
+ toolCenterMin: this.toolCenterMin,
204
+ toolCenterMax: this.toolCenterMax,
205
+ },
206
+ activeViewportIds: [],
207
+ viewportId: missingOrientation,
208
+ referenceLines: [],
209
+ orientation,
210
+ },
211
+ isVirtual: true,
212
+ virtualNormal,
213
+ };
214
+ this._virtualAnnotations = [virtualAnnotation];
215
+ }
216
+ else if (presentViewportInfos.length === 1) {
217
+ let presentOrientation = null;
218
+ const vpInfo = presentViewportInfos[0];
219
+ if (vpInfo.renderingEngineId) {
220
+ const renderingEngine = getRenderingEngine(vpInfo.renderingEngineId);
221
+ const viewport = renderingEngine.getViewport(vpInfo.viewportId);
222
+ if (viewport && viewport.getCamera) {
223
+ presentOrientation = this._getOrientationFromNormal(viewport.getCamera().viewPlaneNormal);
224
+ }
225
+ }
226
+ const presentCenter = presentCenters[0];
227
+ const canonicalNormals = {
228
+ AXIAL: [0, 0, 1],
229
+ CORONAL: [0, 1, 0],
230
+ SAGITTAL: [1, 0, 0],
231
+ };
232
+ const missingIds = orientationIds.filter((id) => id !== presentOrientation);
233
+ const virtualAnnotations = missingIds.map((orientation) => {
234
+ const normal = canonicalNormals[orientation];
235
+ const virtualAnnotation = {
236
+ highlighted: false,
237
+ metadata: {
238
+ cameraPosition: [...presentCenter],
239
+ cameraFocalPoint: [...presentCenter],
240
+ toolName: this.getToolName(),
241
+ },
242
+ data: {
243
+ handles: {
244
+ activeOperation: null,
245
+ toolCenter: this.toolCenter,
246
+ toolCenterMin: this.toolCenterMin,
247
+ toolCenterMax: this.toolCenterMax,
248
+ },
249
+ activeViewportIds: [],
250
+ viewportId: orientation,
251
+ referenceLines: [],
252
+ orientation,
253
+ },
254
+ isVirtual: true,
255
+ virtualNormal: normal,
256
+ };
257
+ return virtualAnnotation;
258
+ });
259
+ this._virtualAnnotations = virtualAnnotations;
260
+ }
261
+ if (viewportsInfo && viewportsInfo.length) {
262
+ triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
263
+ }
138
264
  };
139
265
  this.cancel = () => {
266
+ console.log('Not implemented yet');
140
267
  };
141
268
  this.isPointNearTool = (element, annotation, canvasCoords, proximity) => {
142
- if (this._pointNearTool(element, annotation, canvasCoords, POINT_PROXIMITY_THRESHOLD_PIXELS)) {
269
+ if (this._pointNearTool(element, annotation, canvasCoords, 6)) {
143
270
  return true;
144
271
  }
145
272
  return false;
@@ -171,8 +298,13 @@ class VolumeCroppingControlTool extends AnnotationTool {
171
298
  if (!data.handles) {
172
299
  continue;
173
300
  }
301
+ const previousActiveOperation = data.handles.activeOperation;
302
+ const previousActiveViewportIds = data.activeViewportIds && data.activeViewportIds.length > 0
303
+ ? [...data.activeViewportIds]
304
+ : [];
174
305
  data.activeViewportIds = [];
175
- const near = this._pointNearTool(element, annotation, canvasCoords, POINT_PROXIMITY_THRESHOLD_PIXELS);
306
+ let near = false;
307
+ near = this._pointNearTool(element, annotation, canvasCoords, 6);
176
308
  const nearToolAndNotMarkedActive = near && !highlighted;
177
309
  const notNearToolAndMarkedActive = !near && highlighted;
178
310
  if (nearToolAndNotMarkedActive || notNearToolAndMarkedActive) {
@@ -189,9 +321,12 @@ class VolumeCroppingControlTool extends AnnotationTool {
189
321
  const enabledElement = getEnabledElement(element);
190
322
  let orientation = null;
191
323
  if (enabledElement.viewport && enabledElement.viewport.getCamera) {
192
- orientation = getOrientationFromNormal(enabledElement.viewport.getCamera().viewPlaneNormal);
324
+ orientation = this._getOrientationFromNormal(enabledElement.viewport.getCamera().viewPlaneNormal);
193
325
  }
194
326
  const filtered = annotations.filter((annotation) => {
327
+ if (annotation.isVirtual) {
328
+ return true;
329
+ }
195
330
  if (annotation.data.orientation &&
196
331
  orientation &&
197
332
  annotation.data.orientation === orientation) {
@@ -208,7 +343,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
208
343
  const s2_x = q2[0] - q1[0];
209
344
  const s2_y = q2[1] - q1[1];
210
345
  const denom = -s2_x * s1_y + s1_x * s2_y;
211
- if (Math.abs(denom) < LINE_INTERSECTION_TOLERANCE) {
346
+ if (Math.abs(denom) < 1e-8) {
212
347
  return null;
213
348
  }
214
349
  const s = (-s1_y * (p1[0] - q1[0]) + s1_x * (p1[1] - q1[1])) / denom;
@@ -225,7 +360,10 @@ class VolumeCroppingControlTool extends AnnotationTool {
225
360
  let renderStatus = false;
226
361
  const { viewport, renderingEngine } = enabledElement;
227
362
  const { element } = viewport;
228
- const annotations = this._getAnnotations(enabledElement);
363
+ let annotations = this._getAnnotations(enabledElement);
364
+ if (this._virtualAnnotations && this._virtualAnnotations.length) {
365
+ annotations = annotations.concat(this._virtualAnnotations);
366
+ }
229
367
  const camera = viewport.getCamera();
230
368
  const filteredToolAnnotations = this.filterInteractableAnnotationsForElement(element, annotations);
231
369
  const viewportAnnotation = filteredToolAnnotations[0];
@@ -236,65 +374,135 @@ class VolumeCroppingControlTool extends AnnotationTool {
236
374
  const { clientWidth, clientHeight } = viewport.canvas;
237
375
  const canvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
238
376
  const data = viewportAnnotation.data;
239
- let clippingPlanes = viewportAnnotation.data.handles.clippingPlanes;
240
- if (!clippingPlanes || clippingPlanes.length < NUM_CLIPPING_PLANES) {
241
- if (this.clippingPlanes &&
242
- this.clippingPlanes.length >= NUM_CLIPPING_PLANES) {
243
- clippingPlanes = this.clippingPlanes;
244
- data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
245
- }
246
- else {
247
- return false;
248
- }
249
- }
250
- const { viewPlaneNormal, focalPoint } = camera;
377
+ const otherViewportAnnotations = annotations;
378
+ const volumeCroppingCenterCanvasMin = viewport.worldToCanvas(this.toolCenterMin);
379
+ const volumeCroppingCenterCanvasMax = viewport.worldToCanvas(this.toolCenterMax);
251
380
  const referenceLines = [];
252
- const planeTypes = [
253
- 'min',
254
- 'max',
255
- 'min',
256
- 'max',
257
- 'min',
258
- 'max',
259
- ];
260
- for (let planeIndex = 0; planeIndex < NUM_CLIPPING_PLANES; planeIndex++) {
261
- const clippingPlane = clippingPlanes[planeIndex];
262
- const intersection = computePlanePlaneIntersection(clippingPlane, viewPlaneNormal, focalPoint);
263
- if (!intersection) {
264
- continue;
381
+ const canvasBox = [0, 0, clientWidth, clientHeight];
382
+ otherViewportAnnotations.forEach((annotation) => {
383
+ const data = annotation.data;
384
+ const isVirtual = 'isVirtual' in annotation &&
385
+ annotation.isVirtual === true;
386
+ data.handles.toolCenter = this.toolCenter;
387
+ let otherViewport, otherCamera, clientWidth, clientHeight, otherCanvasDiagonalLength, otherCanvasCenter, otherViewportCenterWorld;
388
+ if (isVirtual) {
389
+ const realViewports = viewportsInfo.filter((vp) => vp.viewportId !== data.viewportId);
390
+ if (realViewports.length === 2) {
391
+ const vp1 = renderingEngine.getViewport(realViewports[0].viewportId);
392
+ const vp2 = renderingEngine.getViewport(realViewports[1].viewportId);
393
+ const normal1 = vp1.getCamera().viewPlaneNormal;
394
+ const normal2 = vp2.getCamera().viewPlaneNormal;
395
+ const virtualNormal = vec3.create();
396
+ vec3.cross(virtualNormal, normal1, normal2);
397
+ vec3.normalize(virtualNormal, virtualNormal);
398
+ otherCamera = {
399
+ viewPlaneNormal: virtualNormal,
400
+ position: data.handles.toolCenter,
401
+ focalPoint: data.handles.toolCenter,
402
+ viewUp: [0, 1, 0],
403
+ };
404
+ clientWidth = viewport.canvas.clientWidth;
405
+ clientHeight = viewport.canvas.clientHeight;
406
+ otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
407
+ otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
408
+ otherViewportCenterWorld = data.handles.toolCenter;
409
+ otherViewport = {
410
+ id: data.viewportId,
411
+ canvas: viewport.canvas,
412
+ canvasToWorld: () => data.handles.toolCenter,
413
+ };
414
+ }
415
+ else {
416
+ const virtualNormal = annotation
417
+ .virtualNormal ?? [0, 0, 1];
418
+ otherCamera = {
419
+ viewPlaneNormal: virtualNormal,
420
+ position: data.handles.toolCenter,
421
+ focalPoint: data.handles.toolCenter,
422
+ viewUp: [0, 1, 0],
423
+ };
424
+ clientWidth = viewport.canvas.clientWidth;
425
+ clientHeight = viewport.canvas.clientHeight;
426
+ otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
427
+ otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
428
+ otherViewportCenterWorld = data.handles.toolCenter;
429
+ otherViewport = {
430
+ id: data.viewportId,
431
+ canvas: viewport.canvas,
432
+ canvasToWorld: () => data.handles.toolCenter,
433
+ };
434
+ }
265
435
  }
266
- const lineBounds = findLineBoundsIntersection(intersection.point, intersection.direction, viewport);
267
- if (!lineBounds) {
268
- continue;
436
+ else {
437
+ otherViewport = renderingEngine.getViewport(data.viewportId);
438
+ otherCamera = otherViewport.getCamera();
439
+ clientWidth = otherViewport.canvas.clientWidth;
440
+ clientHeight = otherViewport.canvas.clientHeight;
441
+ otherCanvasDiagonalLength = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight);
442
+ otherCanvasCenter = [clientWidth * 0.5, clientHeight * 0.5];
443
+ otherViewportCenterWorld =
444
+ otherViewport.canvasToWorld(otherCanvasCenter);
269
445
  }
446
+ const otherViewportControllable = this._getReferenceLineControllable(otherViewport.id);
447
+ const direction = [0, 0, 0];
448
+ vtkMath.cross(camera.viewPlaneNormal, otherCamera.viewPlaneNormal, direction);
449
+ vtkMath.normalize(direction);
450
+ vtkMath.multiplyScalar(direction, otherCanvasDiagonalLength);
451
+ const pointWorld0 = [0, 0, 0];
452
+ vtkMath.add(otherViewportCenterWorld, direction, pointWorld0);
453
+ const pointWorld1 = [0, 0, 0];
454
+ vtkMath.subtract(otherViewportCenterWorld, direction, pointWorld1);
455
+ const pointCanvas0 = viewport.worldToCanvas(pointWorld0);
456
+ const otherViewportCenterCanvas = viewport.worldToCanvas([
457
+ otherViewportCenterWorld[0] ?? 0,
458
+ otherViewportCenterWorld[1] ?? 0,
459
+ otherViewportCenterWorld[2] ?? 0,
460
+ ]);
461
+ const canvasUnitVectorFromCenter = vec2.create();
462
+ vec2.subtract(canvasUnitVectorFromCenter, pointCanvas0, otherViewportCenterCanvas);
463
+ vec2.normalize(canvasUnitVectorFromCenter, canvasUnitVectorFromCenter);
464
+ const canvasVectorFromCenterLong = vec2.create();
465
+ vec2.scale(canvasVectorFromCenterLong, canvasUnitVectorFromCenter, canvasDiagonalLength * 100);
466
+ const refLinesCenterMin = otherViewportControllable
467
+ ? vec2.clone(volumeCroppingCenterCanvasMin)
468
+ : vec2.clone(otherViewportCenterCanvas);
469
+ const refLinePointMinOne = vec2.create();
470
+ const refLinePointMinTwo = vec2.create();
471
+ vec2.add(refLinePointMinOne, refLinesCenterMin, canvasVectorFromCenterLong);
472
+ vec2.subtract(refLinePointMinTwo, refLinesCenterMin, canvasVectorFromCenterLong);
473
+ liangBarksyClip(refLinePointMinOne, refLinePointMinTwo, canvasBox);
270
474
  referenceLines.push([
271
- {
272
- id: viewport.id,
273
- canvas: viewport.canvas,
274
- },
275
- lineBounds.start,
276
- lineBounds.end,
277
- planeTypes[planeIndex],
278
- planeIndex,
475
+ otherViewport,
476
+ refLinePointMinOne,
477
+ refLinePointMinTwo,
478
+ 'min',
279
479
  ]);
280
- }
480
+ const refLinesCenterMax = otherViewportControllable
481
+ ? vec2.clone(volumeCroppingCenterCanvasMax)
482
+ : vec2.clone(otherViewportCenterCanvas);
483
+ const refLinePointMaxOne = vec2.create();
484
+ const refLinePointMaxTwo = vec2.create();
485
+ vec2.add(refLinePointMaxOne, refLinesCenterMax, canvasVectorFromCenterLong);
486
+ vec2.subtract(refLinePointMaxTwo, refLinesCenterMax, canvasVectorFromCenterLong);
487
+ liangBarksyClip(refLinePointMaxOne, refLinePointMaxTwo, canvasBox);
488
+ referenceLines.push([
489
+ otherViewport,
490
+ refLinePointMaxOne,
491
+ refLinePointMaxTwo,
492
+ 'max',
493
+ ]);
494
+ });
281
495
  data.referenceLines = referenceLines;
282
496
  const viewportColor = this._getReferenceLineColor(viewport.id);
283
- const defaultColor = viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
497
+ const color = viewportColor !== undefined ? viewportColor : 'rgb(200, 200, 200)';
284
498
  referenceLines.forEach((line, lineIndex) => {
285
- const [otherViewport, startPoint, endPoint, type, planeIndex] = line;
286
- if (planeIndex === undefined ||
287
- planeIndex < 0 ||
288
- planeIndex >= NUM_CLIPPING_PLANES) {
289
- return;
290
- }
291
499
  const intersections = [];
292
500
  for (let j = 0; j < referenceLines.length; ++j) {
293
501
  if (j === lineIndex) {
294
502
  continue;
295
503
  }
296
504
  const otherLine = referenceLines[j];
297
- const intersection = lineIntersection2D(startPoint, endPoint, otherLine[1], otherLine[2]);
505
+ const intersection = lineIntersection2D(line[1], line[2], otherLine[1], otherLine[2]);
298
506
  if (intersection) {
299
507
  intersections.push({
300
508
  with: otherLine[3],
@@ -302,12 +510,32 @@ class VolumeCroppingControlTool extends AnnotationTool {
302
510
  });
303
511
  }
304
512
  }
305
- const colorKey = getColorKeyForPlaneIndex(planeIndex);
513
+ const otherViewport = line[0];
514
+ let orientation = null;
515
+ if (otherViewport && otherViewport.id) {
516
+ const annotationForViewport = annotations.find((a) => a.data.viewportId === otherViewport.id);
517
+ if (annotationForViewport && annotationForViewport.data.orientation) {
518
+ orientation = String(annotationForViewport.data.orientation).toUpperCase();
519
+ }
520
+ else {
521
+ const idUpper = otherViewport.id.toUpperCase();
522
+ if (idUpper.includes('AXIAL')) {
523
+ orientation = 'AXIAL';
524
+ }
525
+ else if (idUpper.includes('CORONAL')) {
526
+ orientation = 'CORONAL';
527
+ }
528
+ else if (idUpper.includes('SAGITTAL')) {
529
+ orientation = 'SAGITTAL';
530
+ }
531
+ }
532
+ }
306
533
  const lineColors = this.configuration.lineColors || {};
307
- const colorArr = colorKey
308
- ? lineColors[colorKey] || lineColors.UNKNOWN || [1.0, 0.0, 0.0]
309
- : [1.0, 0.0, 0.0];
310
- const color = convertColorArrayToRgbString(colorArr);
534
+ const colorArr = lineColors[orientation] ||
535
+ lineColors.unknown || [1.0, 0.0, 0.0];
536
+ const color = Array.isArray(colorArr)
537
+ ? `rgb(${colorArr.map((v) => Math.round(v * 255)).join(',')})`
538
+ : colorArr;
311
539
  const viewportControllable = this._getReferenceLineControllable(otherViewport.id);
312
540
  const selectedViewportId = data.activeViewportIds.find((id) => id === otherViewport.id);
313
541
  let lineWidth = this.configuration.lineWidth ?? 1.5;
@@ -317,29 +545,28 @@ class VolumeCroppingControlTool extends AnnotationTool {
317
545
  if (lineActive) {
318
546
  lineWidth = this.configuration.activeLineWidth ?? 2.5;
319
547
  }
320
- const lineUID = `plane_${planeIndex}`;
321
- if (intersections.length === 2) {
322
- drawLineSvg(svgDrawingHelper, annotationUID, lineUID, intersections[0].point, intersections[1].point, {
323
- color,
324
- lineWidth,
325
- });
326
- }
327
- else {
328
- drawLineSvg(svgDrawingHelper, annotationUID, lineUID, startPoint, endPoint, {
329
- color,
330
- lineWidth,
331
- });
332
- }
333
- if (this.configuration.extendReferenceLines &&
334
- intersections.length === 2) {
335
- const sortedIntersections = intersections
336
- .map((intersection) => ({
337
- ...intersection,
338
- distance: vec2.distance(startPoint, intersection.point),
339
- }))
340
- .sort((a, b) => a.distance - b.distance);
341
- drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_before', startPoint, sortedIntersections[0].point, { color, lineWidth, lineDash: [4, 4] });
342
- drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_after', sortedIntersections[1].point, endPoint, { color, lineWidth, lineDash: [4, 4] });
548
+ const lineUID = `${lineIndex}`;
549
+ if (viewportControllable) {
550
+ if (intersections.length === 2) {
551
+ drawLineSvg(svgDrawingHelper, annotationUID, lineUID, intersections[0].point, intersections[1].point, {
552
+ color,
553
+ lineWidth,
554
+ });
555
+ }
556
+ if (this.configuration.extendReferenceLines &&
557
+ intersections.length === 2) {
558
+ if (this.configuration.extendReferenceLines &&
559
+ intersections.length === 2) {
560
+ const sortedIntersections = intersections
561
+ .map((intersection) => ({
562
+ ...intersection,
563
+ distance: vec2.distance(line[1], intersection.point),
564
+ }))
565
+ .sort((a, b) => a.distance - b.distance);
566
+ drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_before', line[1], sortedIntersections[0].point, { color, lineWidth, lineDash: [4, 4] });
567
+ drawLineSvg(svgDrawingHelper, annotationUID, lineUID + '_dashed_after', sortedIntersections[1].point, line[2], { color, lineWidth, lineDash: [4, 4] });
568
+ }
569
+ }
343
570
  }
344
571
  });
345
572
  renderStatus = true;
@@ -353,7 +580,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
353
580
  ];
354
581
  const circleRadius = viewportIndicatorsConfig?.circleRadius || canvasDiagonalLength * 0.01;
355
582
  const circleUID = '0';
356
- drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color: defaultColor, fill: defaultColor });
583
+ drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, referenceColorCoordinates, circleRadius, { color, fill: color });
357
584
  }
358
585
  return renderStatus;
359
586
  };
@@ -375,7 +602,40 @@ class VolumeCroppingControlTool extends AnnotationTool {
375
602
  if (evt.detail.seriesInstanceUID !== this.seriesInstanceUID) {
376
603
  return;
377
604
  }
378
- return;
605
+ const { draggingSphereIndex, toolCenter } = evt.detail;
606
+ const newMin = [...this.toolCenterMin];
607
+ const newMax = [...this.toolCenterMax];
608
+ if (draggingSphereIndex >= 0 && draggingSphereIndex <= 5) {
609
+ const axis = Math.floor(draggingSphereIndex / 2);
610
+ const isMin = draggingSphereIndex % 2 === 0;
611
+ (isMin ? newMin : newMax)[axis] = toolCenter[axis];
612
+ this.setToolCenter(newMin, 'min');
613
+ this.setToolCenter(newMax, 'max');
614
+ return;
615
+ }
616
+ if (draggingSphereIndex >= 6 && draggingSphereIndex <= 13) {
617
+ const idx = draggingSphereIndex;
618
+ if (idx < 10) {
619
+ newMin[0] = toolCenter[0];
620
+ }
621
+ else {
622
+ newMax[0] = toolCenter[0];
623
+ }
624
+ if ([6, 7, 10, 11].includes(idx)) {
625
+ newMin[1] = toolCenter[1];
626
+ }
627
+ else {
628
+ newMax[1] = toolCenter[1];
629
+ }
630
+ if (idx % 2 === 0) {
631
+ newMin[2] = toolCenter[2];
632
+ }
633
+ else {
634
+ newMax[2] = toolCenter[2];
635
+ }
636
+ this.setToolCenter(newMin, 'min');
637
+ this.setToolCenter(newMax, 'max');
638
+ }
379
639
  }
380
640
  };
381
641
  this._onNewVolume = () => {
@@ -383,28 +643,72 @@ class VolumeCroppingControlTool extends AnnotationTool {
383
643
  if (viewportsInfo && viewportsInfo.length > 0) {
384
644
  const { viewportId, renderingEngineId } = viewportsInfo[0];
385
645
  const renderingEngine = getRenderingEngine(renderingEngineId);
386
- if (!renderingEngine) {
387
- return;
388
- }
389
646
  const viewport = renderingEngine.getViewport(viewportId);
390
- if (!viewport) {
391
- return;
392
- }
393
647
  const volumeActors = viewport.getActors();
394
648
  if (volumeActors.length > 0) {
395
649
  const imageData = volumeActors[0].actor.getMapper().getInputData();
396
650
  if (imageData) {
397
651
  this.seriesInstanceUID = imageData.seriesInstanceUID;
652
+ this._updateToolCentersFromViewport(viewport);
653
+ const annotations = getAnnotations(this.getToolName(), viewportId) || [];
654
+ annotations.forEach((annotation) => {
655
+ if (annotation.data && annotation.data.handles) {
656
+ annotation.data.handles.toolCenter = [...this.toolCenter];
657
+ }
658
+ });
398
659
  }
399
660
  }
400
661
  }
401
- this._initializeViewports(viewportsInfo);
662
+ this._computeToolCenter(viewportsInfo);
402
663
  triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
403
664
  toolGroupId: this.toolGroupId,
404
665
  viewportsInfo: viewportsInfo,
405
666
  seriesInstanceUID: this.seriesInstanceUID,
406
667
  });
407
668
  };
669
+ this._getAnnotationsForViewportsWithDifferentCameras = (enabledElement, annotations) => {
670
+ const { viewportId, renderingEngine, viewport } = enabledElement;
671
+ const otherViewportAnnotations = annotations.filter((annotation) => annotation.data.viewportId !== viewportId);
672
+ if (!otherViewportAnnotations || !otherViewportAnnotations.length) {
673
+ return [];
674
+ }
675
+ const camera = viewport.getCamera();
676
+ const { viewPlaneNormal, position } = camera;
677
+ const viewportsWithDifferentCameras = otherViewportAnnotations.filter((annotation) => {
678
+ const { viewportId } = annotation.data;
679
+ const targetViewport = renderingEngine.getViewport(viewportId);
680
+ const cameraOfTarget = targetViewport.getCamera();
681
+ return !(csUtils.isEqual(cameraOfTarget.viewPlaneNormal, viewPlaneNormal, 1e-2) && csUtils.isEqual(cameraOfTarget.position, position, 1));
682
+ });
683
+ return viewportsWithDifferentCameras;
684
+ };
685
+ this._filterViewportWithSameOrientation = (enabledElement, referenceAnnotation, annotations) => {
686
+ const { renderingEngine } = enabledElement;
687
+ const { data } = referenceAnnotation;
688
+ const viewport = renderingEngine.getViewport(data.viewportId);
689
+ const linkedViewportAnnotations = annotations.filter((annotation) => {
690
+ const { data } = annotation;
691
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
692
+ const otherViewportControllable = this._getReferenceLineControllable(otherViewport.id);
693
+ return otherViewportControllable === true;
694
+ });
695
+ if (!linkedViewportAnnotations || !linkedViewportAnnotations.length) {
696
+ return [];
697
+ }
698
+ const camera = viewport.getCamera();
699
+ const viewPlaneNormal = camera.viewPlaneNormal;
700
+ vtkMath.normalize(viewPlaneNormal);
701
+ const otherViewportsAnnotationsWithSameCameraDirection = linkedViewportAnnotations.filter((annotation) => {
702
+ const { viewportId } = annotation.data;
703
+ const otherViewport = renderingEngine.getViewport(viewportId);
704
+ const otherCamera = otherViewport.getCamera();
705
+ const otherViewPlaneNormal = otherCamera.viewPlaneNormal;
706
+ vtkMath.normalize(otherViewPlaneNormal);
707
+ return (csUtils.isEqual(viewPlaneNormal, otherViewPlaneNormal, 1e-2) &&
708
+ csUtils.isEqual(camera.viewUp, otherCamera.viewUp, 1e-2));
709
+ });
710
+ return otherViewportsAnnotationsWithSameCameraDirection;
711
+ };
408
712
  this._activateModify = (element) => {
409
713
  state.isInteractingWithTool = !this.configuration.mobile?.enabled;
410
714
  element.addEventListener(Events.MOUSE_UP, this._endCallback);
@@ -456,71 +760,48 @@ class VolumeCroppingControlTool extends AnnotationTool {
456
760
  return;
457
761
  }
458
762
  const { handles } = viewportAnnotation.data;
459
- const clippingPlanes = handles.clippingPlanes;
460
- if (handles.activeOperation === OPERATION.DRAG &&
461
- clippingPlanes &&
462
- clippingPlanes.length >= NUM_CLIPPING_PLANES) {
463
- if (handles.activePlaneIndex !== undefined &&
464
- handles.activePlaneIndex >= 0 &&
465
- handles.activePlaneIndex < NUM_CLIPPING_PLANES) {
466
- const planeIndex = handles.activePlaneIndex;
467
- const plane = clippingPlanes[planeIndex];
468
- const normal = plane.normal;
469
- const dotProd = vtkMath.dot(delta, normal);
470
- const moveDistance = dotProd;
471
- plane.origin[0] += normal[0] * moveDistance;
472
- plane.origin[1] += normal[1] * moveDistance;
473
- plane.origin[2] += normal[2] * moveDistance;
474
- }
475
- else if (handles.activeType === 'min') {
476
- clippingPlanes[0].origin[0] += delta[0];
477
- clippingPlanes[2].origin[1] += delta[1];
478
- clippingPlanes[4].origin[2] += delta[2];
763
+ if (handles.activeOperation === OPERATION.DRAG) {
764
+ if (handles.activeType === 'min') {
765
+ this.toolCenterMin[0] += delta[0];
766
+ this.toolCenterMin[1] += delta[1];
767
+ this.toolCenterMin[2] += delta[2];
479
768
  }
480
769
  else if (handles.activeType === 'max') {
481
- clippingPlanes[1].origin[0] += delta[0];
482
- clippingPlanes[3].origin[1] += delta[1];
483
- clippingPlanes[5].origin[2] += delta[2];
770
+ this.toolCenterMax[0] += delta[0];
771
+ this.toolCenterMax[1] += delta[1];
772
+ this.toolCenterMax[2] += delta[2];
773
+ }
774
+ else {
775
+ this.toolCenter[0] += delta[0];
776
+ this.toolCenter[1] += delta[1];
777
+ this.toolCenter[2] += delta[2];
484
778
  }
485
- this.clippingPlanes = copyClippingPlanes(clippingPlanes);
486
779
  const viewportsInfo = this._getViewportsInfo();
487
- viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
488
- const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
489
- if (enabledElement) {
490
- const annotations = this._getAnnotations(enabledElement);
491
- annotations.forEach((annotation) => {
492
- if (annotation.data?.handles) {
493
- annotation.data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
494
- }
495
- });
496
- }
497
- });
498
780
  triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
499
781
  triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
500
782
  toolGroupId: this.toolGroupId,
501
- clippingPlanes: this.clippingPlanes,
783
+ toolCenter: this.toolCenter,
784
+ toolCenterMin: this.toolCenterMin,
785
+ toolCenterMax: this.toolCenterMax,
502
786
  handleType: handles.activeType,
787
+ viewportOrientation: [],
503
788
  seriesInstanceUID: this.seriesInstanceUID,
504
789
  });
505
790
  }
506
791
  };
507
792
  this._getReferenceLineColor =
508
793
  toolProps.configuration?.getReferenceLineColor ||
509
- (() => 'rgb(0, 200, 0)');
794
+ defaultReferenceLineColor;
510
795
  this._getReferenceLineControllable =
511
- toolProps.configuration?.getReferenceLineControllable || (() => true);
796
+ toolProps.configuration?.getReferenceLineControllable ||
797
+ defaultReferenceLineControllable;
512
798
  const viewportsInfo = getToolGroup(this.toolGroupId)?.viewportsInfo;
513
799
  eventTarget.addEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
514
800
  if (viewportsInfo && viewportsInfo.length > 0) {
515
801
  const { viewportId, renderingEngineId } = viewportsInfo[0];
802
+ const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
516
803
  const renderingEngine = getRenderingEngine(renderingEngineId);
517
- if (!renderingEngine) {
518
- return;
519
- }
520
804
  const viewport = renderingEngine.getViewport(viewportId);
521
- if (!viewport) {
522
- return;
523
- }
524
805
  const volumeActors = viewport.getActors();
525
806
  if (!volumeActors || !volumeActors.length) {
526
807
  console.warn(`VolumeCroppingControlTool: No volume actors found in viewport ${viewportId}.`);
@@ -528,11 +809,67 @@ class VolumeCroppingControlTool extends AnnotationTool {
528
809
  }
529
810
  const imageData = volumeActors[0].actor.getMapper().getInputData();
530
811
  if (imageData) {
812
+ const dimensions = imageData.getDimensions();
813
+ const spacing = imageData.getSpacing();
814
+ const origin = imageData.getOrigin();
531
815
  this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
816
+ const cropFactor = this.configuration.initialCropFactor ?? 0.2;
817
+ this.toolCenter = [
818
+ origin[0] + cropFactor * (dimensions[0] - 1) * spacing[0],
819
+ origin[1] + cropFactor * (dimensions[1] - 1) * spacing[1],
820
+ origin[2] + cropFactor * (dimensions[2] - 1) * spacing[2],
821
+ ];
822
+ const maxCropFactor = 1 - cropFactor;
823
+ this.toolCenterMin = [
824
+ origin[0] + cropFactor * (dimensions[0] - 1) * spacing[0],
825
+ origin[1] + cropFactor * (dimensions[1] - 1) * spacing[1],
826
+ origin[2] + cropFactor * (dimensions[2] - 1) * spacing[2],
827
+ ];
828
+ this.toolCenterMax = [
829
+ origin[0] + maxCropFactor * (dimensions[0] - 1) * spacing[0],
830
+ origin[1] + maxCropFactor * (dimensions[1] - 1) * spacing[1],
831
+ origin[2] + maxCropFactor * (dimensions[2] - 1) * spacing[2],
832
+ ];
532
833
  }
533
834
  }
534
835
  }
836
+ _updateToolCentersFromViewport(viewport) {
837
+ const volumeActors = viewport.getActors();
838
+ if (!volumeActors || !volumeActors.length) {
839
+ return;
840
+ }
841
+ const imageData = volumeActors[0].actor.getMapper().getInputData();
842
+ if (!imageData) {
843
+ return;
844
+ }
845
+ this.seriesInstanceUID = imageData.seriesInstanceUID || 'unknown';
846
+ const dimensions = imageData.getDimensions();
847
+ const spacing = imageData.getSpacing();
848
+ const origin = imageData.getOrigin();
849
+ const cropFactor = this.configuration.initialCropFactor ?? 0.2;
850
+ const cropStart = cropFactor / 2;
851
+ const cropEnd = 1 - cropFactor / 2;
852
+ this.toolCenter = [
853
+ origin[0] +
854
+ ((cropStart + cropEnd) / 2) * (dimensions[0] - 1) * spacing[0],
855
+ origin[1] +
856
+ ((cropStart + cropEnd) / 2) * (dimensions[1] - 1) * spacing[1],
857
+ origin[2] +
858
+ ((cropStart + cropEnd) / 2) * (dimensions[2] - 1) * spacing[2],
859
+ ];
860
+ this.toolCenterMin = [
861
+ origin[0] + cropStart * (dimensions[0] - 1) * spacing[0],
862
+ origin[1] + cropStart * (dimensions[1] - 1) * spacing[1],
863
+ origin[2] + cropStart * (dimensions[2] - 1) * spacing[2],
864
+ ];
865
+ this.toolCenterMax = [
866
+ origin[0] + cropEnd * (dimensions[0] - 1) * spacing[0],
867
+ origin[1] + cropEnd * (dimensions[1] - 1) * spacing[1],
868
+ origin[2] + cropEnd * (dimensions[2] - 1) * spacing[2],
869
+ ];
870
+ }
535
871
  onSetToolInactive() {
872
+ console.debug(`VolumeCroppingControlTool: onSetToolInactive called for tool ${this.getToolName()}`);
536
873
  }
537
874
  onSetToolActive() {
538
875
  const viewportsInfo = this._getViewportsInfo();
@@ -548,7 +885,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
548
885
  if (!anyAnnotationExists) {
549
886
  this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
550
887
  this._subscribeToViewportNewVolumeSet(viewportsInfo);
551
- this._initializeViewports(viewportsInfo);
888
+ this._computeToolCenter(viewportsInfo);
552
889
  triggerEvent(eventTarget, Events.VOLUMECROPPINGCONTROL_TOOL_CHANGED, {
553
890
  toolGroupId: this.toolGroupId,
554
891
  viewportsInfo: viewportsInfo,
@@ -572,12 +909,13 @@ class VolumeCroppingControlTool extends AnnotationTool {
572
909
  }
573
910
  }
574
911
  onSetToolEnabled() {
575
- eventTarget.addEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
912
+ console.debug(`VolumeCroppingControlTool: onSetToolEnabled called for tool ${this.getToolName()}`);
913
+ const viewportsInfo = this._getViewportsInfo();
576
914
  }
577
915
  onSetToolDisabled() {
916
+ console.debug(`VolumeCroppingControlTool: onSetToolDisabled called for tool ${this.getToolName()}`);
578
917
  const viewportsInfo = this._getViewportsInfo();
579
918
  this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
580
- eventTarget.removeEventListener(Events.VOLUMECROPPING_TOOL_CHANGED, this._onSphereMoved);
581
919
  viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
582
920
  const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
583
921
  if (!enabledElement) {
@@ -591,24 +929,176 @@ class VolumeCroppingControlTool extends AnnotationTool {
591
929
  }
592
930
  });
593
931
  }
594
- _syncWithVolumeCroppingTool(originalClippingPlanes) {
595
- if (!originalClippingPlanes ||
596
- originalClippingPlanes.length < NUM_CLIPPING_PLANES) {
597
- return;
932
+ _getOrientationFromNormal(normal) {
933
+ if (!normal) {
934
+ return null;
598
935
  }
599
- this.clippingPlanes = copyClippingPlanes(originalClippingPlanes);
600
- const viewportsInfo = this._getViewportsInfo();
601
- viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
602
- const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
603
- if (enabledElement) {
604
- const annotations = this._getAnnotations(enabledElement);
605
- annotations.forEach((annotation) => {
606
- if (annotation.data?.handles) {
607
- annotation.data.handles.clippingPlanes = copyClippingPlanes(this.clippingPlanes);
936
+ const canonical = {
937
+ AXIAL: [0, 0, 1],
938
+ CORONAL: [0, 1, 0],
939
+ SAGITTAL: [1, 0, 0],
940
+ };
941
+ const tol = 1e-2;
942
+ for (const [key, value] of Object.entries(canonical)) {
943
+ if (Math.abs(normal[0] - value[0]) < tol &&
944
+ Math.abs(normal[1] - value[1]) < tol &&
945
+ Math.abs(normal[2] - value[2]) < tol) {
946
+ return key;
947
+ }
948
+ if (Math.abs(normal[0] + value[0]) < tol &&
949
+ Math.abs(normal[1] + value[1]) < tol &&
950
+ Math.abs(normal[2] + value[2]) < tol) {
951
+ return key;
952
+ }
953
+ }
954
+ return null;
955
+ }
956
+ _syncWithVolumeCroppingTool(originalClippingPlanes) {
957
+ const planes = originalClippingPlanes;
958
+ if (planes.length >= 6) {
959
+ this.toolCenterMin = [
960
+ planes[0].origin[0],
961
+ planes[2].origin[1],
962
+ planes[4].origin[2],
963
+ ];
964
+ this.toolCenterMax = [
965
+ planes[1].origin[0],
966
+ planes[3].origin[1],
967
+ planes[5].origin[2],
968
+ ];
969
+ this.toolCenter = [
970
+ (this.toolCenterMin[0] + this.toolCenterMax[0]) / 2,
971
+ (this.toolCenterMin[1] + this.toolCenterMax[1]) / 2,
972
+ (this.toolCenterMin[2] + this.toolCenterMax[2]) / 2,
973
+ ];
974
+ const viewportsInfo = this._getViewportsInfo();
975
+ viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
976
+ const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
977
+ if (enabledElement) {
978
+ const annotations = this._getAnnotations(enabledElement);
979
+ annotations.forEach((annotation) => {
980
+ if (annotation.data &&
981
+ annotation.data.handles &&
982
+ annotation.data.orientation) {
983
+ const orientation = annotation.data.orientation;
984
+ if (orientation === 'AXIAL') {
985
+ annotation.data.handles.toolCenterMin = [
986
+ planes[0].origin[0],
987
+ planes[2].origin[1],
988
+ annotation.data.handles.toolCenterMin[2],
989
+ ];
990
+ annotation.data.handles.toolCenterMax = [
991
+ planes[1].origin[0],
992
+ planes[3].origin[1],
993
+ annotation.data.handles.toolCenterMax[2],
994
+ ];
995
+ }
996
+ else if (orientation === 'CORONAL') {
997
+ annotation.data.handles.toolCenterMin = [
998
+ planes[0].origin[0],
999
+ annotation.data.handles.toolCenterMin[1],
1000
+ planes[4].origin[2],
1001
+ ];
1002
+ annotation.data.handles.toolCenterMax = [
1003
+ planes[1].origin[0],
1004
+ annotation.data.handles.toolCenterMax[1],
1005
+ planes[5].origin[2],
1006
+ ];
1007
+ }
1008
+ else if (orientation === 'SAGITTAL') {
1009
+ annotation.data.handles.toolCenterMin = [
1010
+ annotation.data.handles.toolCenterMin[0],
1011
+ planes[2].origin[1],
1012
+ planes[4].origin[2],
1013
+ ];
1014
+ annotation.data.handles.toolCenterMax = [
1015
+ annotation.data.handles.toolCenterMax[0],
1016
+ planes[3].origin[1],
1017
+ planes[5].origin[2],
1018
+ ];
1019
+ }
1020
+ annotation.data.handles.toolCenter = [
1021
+ (annotation.data.handles.toolCenterMin[0] +
1022
+ annotation.data.handles.toolCenterMax[0]) /
1023
+ 2,
1024
+ (annotation.data.handles.toolCenterMin[1] +
1025
+ annotation.data.handles.toolCenterMax[1]) /
1026
+ 2,
1027
+ (annotation.data.handles.toolCenterMin[2] +
1028
+ annotation.data.handles.toolCenterMax[2]) /
1029
+ 2,
1030
+ ];
1031
+ }
1032
+ });
1033
+ }
1034
+ });
1035
+ if (this._virtualAnnotations && this._virtualAnnotations.length > 0) {
1036
+ this._virtualAnnotations.forEach((annotation) => {
1037
+ if (annotation.data &&
1038
+ annotation.data.handles &&
1039
+ annotation.data.orientation) {
1040
+ const orientation = annotation.data.orientation.toUpperCase();
1041
+ if (orientation === 'AXIAL') {
1042
+ annotation.data.handles.toolCenterMin = [
1043
+ planes[0].origin[0],
1044
+ planes[2].origin[1],
1045
+ annotation.data.handles.toolCenterMin[2],
1046
+ ];
1047
+ annotation.data.handles.toolCenterMax = [
1048
+ planes[1].origin[0],
1049
+ planes[3].origin[1],
1050
+ annotation.data.handles.toolCenterMax[2],
1051
+ ];
1052
+ }
1053
+ else if (orientation === 'CORONAL') {
1054
+ annotation.data.handles.toolCenterMin = [
1055
+ planes[0].origin[0],
1056
+ annotation.data.handles.toolCenterMin[1],
1057
+ planes[4].origin[2],
1058
+ ];
1059
+ annotation.data.handles.toolCenterMax = [
1060
+ planes[1].origin[0],
1061
+ annotation.data.handles.toolCenterMax[1],
1062
+ planes[5].origin[2],
1063
+ ];
1064
+ }
1065
+ else if (orientation === 'SAGITTAL') {
1066
+ annotation.data.handles.toolCenterMin = [
1067
+ annotation.data.handles.toolCenterMin[0],
1068
+ planes[2].origin[1],
1069
+ planes[4].origin[2],
1070
+ ];
1071
+ annotation.data.handles.toolCenterMax = [
1072
+ annotation.data.handles.toolCenterMax[0],
1073
+ planes[3].origin[1],
1074
+ planes[5].origin[2],
1075
+ ];
1076
+ }
1077
+ annotation.data.handles.toolCenter = [
1078
+ (annotation.data.handles.toolCenterMin[0] +
1079
+ annotation.data.handles.toolCenterMax[0]) /
1080
+ 2,
1081
+ (annotation.data.handles.toolCenterMin[1] +
1082
+ annotation.data.handles.toolCenterMax[1]) /
1083
+ 2,
1084
+ (annotation.data.handles.toolCenterMin[2] +
1085
+ annotation.data.handles.toolCenterMax[2]) /
1086
+ 2,
1087
+ ];
608
1088
  }
609
1089
  });
610
1090
  }
611
- });
1091
+ triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
1092
+ }
1093
+ }
1094
+ setToolCenter(toolCenter, handleType) {
1095
+ if (handleType === 'min') {
1096
+ this.toolCenterMin = [...toolCenter];
1097
+ }
1098
+ else if (handleType === 'max') {
1099
+ this.toolCenterMax = [...toolCenter];
1100
+ }
1101
+ const viewportsInfo = this._getViewportsInfo();
612
1102
  triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
613
1103
  }
614
1104
  addNewAnnotation(evt) {
@@ -633,6 +1123,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
633
1123
  continue;
634
1124
  }
635
1125
  viewportIdArray.push(otherViewport.id);
1126
+ i++;
636
1127
  }
637
1128
  data.activeViewportIds = [...viewportIdArray];
638
1129
  data.handles.activeOperation = OPERATION.DRAG;
@@ -658,22 +1149,51 @@ class VolumeCroppingControlTool extends AnnotationTool {
658
1149
  element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
659
1150
  });
660
1151
  }
1152
+ _applyDeltaShiftToSelectedViewportCameras(renderingEngine, viewportsAnnotationsToUpdate, delta) {
1153
+ viewportsAnnotationsToUpdate.forEach((annotation) => {
1154
+ this._applyDeltaShiftToViewportCamera(renderingEngine, annotation, delta);
1155
+ });
1156
+ }
1157
+ _applyDeltaShiftToViewportCamera(renderingEngine, annotation, delta) {
1158
+ const { data } = annotation;
1159
+ const viewport = renderingEngine.getViewport(data.viewportId);
1160
+ const camera = viewport.getCamera();
1161
+ const normal = camera.viewPlaneNormal;
1162
+ const dotProd = vtkMath.dot(delta, normal);
1163
+ const projectedDelta = [...normal];
1164
+ vtkMath.multiplyScalar(projectedDelta, dotProd);
1165
+ if (Math.abs(projectedDelta[0]) > 1e-3 ||
1166
+ Math.abs(projectedDelta[1]) > 1e-3 ||
1167
+ Math.abs(projectedDelta[2]) > 1e-3) {
1168
+ const newFocalPoint = [0, 0, 0];
1169
+ const newPosition = [0, 0, 0];
1170
+ vtkMath.add(camera.focalPoint, projectedDelta, newFocalPoint);
1171
+ vtkMath.add(camera.position, projectedDelta, newPosition);
1172
+ viewport.setCamera({
1173
+ focalPoint: newFocalPoint,
1174
+ position: newPosition,
1175
+ });
1176
+ viewport.render();
1177
+ }
1178
+ }
661
1179
  _pointNearTool(element, annotation, canvasCoords, proximity) {
662
1180
  const { data } = annotation;
663
1181
  const referenceLines = data.referenceLines;
664
1182
  const viewportIdArray = [];
665
1183
  if (referenceLines) {
666
1184
  for (let i = 0; i < referenceLines.length; ++i) {
667
- const [otherViewport, startPoint, endPoint, type, planeIndex] = referenceLines[i];
668
- const distance = lineSegment.distanceToPoint(startPoint, endPoint, [
1185
+ const otherViewport = referenceLines[i][0];
1186
+ const start1 = referenceLines[i][1];
1187
+ const end1 = referenceLines[i][2];
1188
+ const type = referenceLines[i][3];
1189
+ const distance1 = lineSegment.distanceToPoint(start1, end1, [
669
1190
  canvasCoords[0],
670
1191
  canvasCoords[1],
671
1192
  ]);
672
- if (distance <= proximity) {
1193
+ if (distance1 <= proximity) {
673
1194
  viewportIdArray.push(otherViewport.id);
674
- data.handles.activeOperation = OPERATION.DRAG;
1195
+ data.handles.activeOperation = 1;
675
1196
  data.handles.activeType = type;
676
- data.handles.activePlaneIndex = planeIndex;
677
1197
  }
678
1198
  }
679
1199
  }
@@ -681,7 +1201,7 @@ class VolumeCroppingControlTool extends AnnotationTool {
681
1201
  this.editData = {
682
1202
  annotation,
683
1203
  };
684
- return data.handles.activeOperation === OPERATION.DRAG ? true : false;
1204
+ return data.handles.activeOperation === 1 ? true : false;
685
1205
  }
686
1206
  }
687
1207
  VolumeCroppingControlTool.toolName = 'VolumeCroppingControl';