@cornerstonejs/tools 3.24.0 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/esm/stateManagement/segmentation/utilities/convertContourHoles.d.ts +1 -0
  2. package/dist/esm/stateManagement/segmentation/utilities/convertContourHoles.js +66 -0
  3. package/dist/esm/stateManagement/segmentation/utilities/getAnnotationsUIDMapFromSegmentation.d.ts +1 -0
  4. package/dist/esm/stateManagement/segmentation/utilities/getAnnotationsUIDMapFromSegmentation.js +17 -0
  5. package/dist/esm/stateManagement/segmentation/utilities/index.d.ts +2 -0
  6. package/dist/esm/stateManagement/segmentation/utilities/index.js +3 -0
  7. package/dist/esm/stateManagement/segmentation/utilities/removeContourHoles.js +0 -1
  8. package/dist/esm/tools/annotation/CircleROITool.js +93 -53
  9. package/dist/esm/tools/annotation/SplineROITool.d.ts +2 -0
  10. package/dist/esm/tools/annotation/SplineROITool.js +18 -0
  11. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +1 -1
  12. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +62 -46
  13. package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +8 -2
  14. package/dist/esm/types/index.d.ts +2 -1
  15. package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.d.ts +2 -1
  16. package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.js +3 -4
  17. package/dist/esm/utilities/contourSegmentation/areViewReferencesEqual.d.ts +2 -0
  18. package/dist/esm/utilities/contourSegmentation/areViewReferencesEqual.js +23 -0
  19. package/dist/esm/utilities/contourSegmentation/copyAnnotation.d.ts +3 -0
  20. package/dist/esm/utilities/contourSegmentation/copyAnnotation.js +86 -0
  21. package/dist/esm/utilities/contourSegmentation/getViewReferenceFromAnnotation.d.ts +3 -0
  22. package/dist/esm/utilities/contourSegmentation/getViewReferenceFromAnnotation.js +20 -0
  23. package/dist/esm/utilities/contourSegmentation/index.d.ts +7 -3
  24. package/dist/esm/utilities/contourSegmentation/index.js +7 -3
  25. package/dist/esm/utilities/contourSegmentation/logicalOperators.d.ts +21 -12
  26. package/dist/esm/utilities/contourSegmentation/logicalOperators.js +148 -43
  27. package/dist/esm/utilities/contourSegmentation/polylineInfoTypes.d.ts +9 -0
  28. package/dist/esm/utilities/contourSegmentation/polylineInfoTypes.js +0 -0
  29. package/dist/esm/utilities/contourSegmentation/polylineIntersect.d.ts +2 -0
  30. package/dist/esm/utilities/contourSegmentation/polylineIntersect.js +34 -0
  31. package/dist/esm/utilities/contourSegmentation/polylineSubtract.d.ts +6 -0
  32. package/dist/esm/utilities/contourSegmentation/polylineSubtract.js +67 -0
  33. package/dist/esm/utilities/contourSegmentation/polylineUnify.d.ts +6 -0
  34. package/dist/esm/utilities/contourSegmentation/polylineUnify.js +79 -0
  35. package/dist/esm/utilities/contourSegmentation/polylineXor.d.ts +2 -0
  36. package/dist/esm/utilities/contourSegmentation/polylineXor.js +41 -0
  37. package/dist/esm/utilities/contourSegmentation/sharedOperations.d.ts +2 -0
  38. package/dist/esm/utilities/contourSegmentation/sharedOperations.js +44 -0
  39. package/dist/esm/utilities/math/polyline/arePolylinesIdentical.d.ts +2 -0
  40. package/dist/esm/utilities/math/polyline/arePolylinesIdentical.js +53 -0
  41. package/dist/esm/utilities/math/polyline/combinePolyline.d.ts +1 -2
  42. package/dist/esm/utilities/math/polyline/combinePolyline.js +17 -47
  43. package/dist/esm/utilities/math/polyline/index.d.ts +5 -2
  44. package/dist/esm/utilities/math/polyline/index.js +5 -2
  45. package/dist/esm/utilities/math/polyline/intersectPolylines.d.ts +2 -0
  46. package/dist/esm/utilities/math/polyline/intersectPolylines.js +271 -0
  47. package/dist/esm/utilities/math/polyline/robustSegmentIntersection.d.ts +37 -0
  48. package/dist/esm/utilities/math/polyline/robustSegmentIntersection.js +78 -0
  49. package/dist/esm/utilities/math/polyline/subtractPolylines.d.ts +2 -0
  50. package/dist/esm/utilities/math/polyline/subtractPolylines.js +210 -0
  51. package/dist/esm/version.d.ts +1 -1
  52. package/dist/esm/version.js +1 -1
  53. package/package.json +3 -3
  54. package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.d.ts +0 -31
  55. package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.js +0 -110
@@ -0,0 +1 @@
1
+ export default function convertContourHoles(segmentationId: string, segmentIndex: number, targetSegmentationId?: string, targetSegmentationIndex?: number): void;
@@ -0,0 +1,66 @@
1
+ import { findContourHoles } from '../../../utilities/contours';
2
+ import { getAnnotation, clearParentAnnotation, } from '../../annotation/annotationState';
3
+ import { getSegmentation } from '../getSegmentation';
4
+ import { extractSegmentPolylines } from './extractSegmentPolylines';
5
+ export default function convertContourHoles(segmentationId, segmentIndex, targetSegmentationId, targetSegmentationIndex) {
6
+ const segmentation = getSegmentation(segmentationId);
7
+ if (!segmentation) {
8
+ console.warn(`Invalid segmentation given ${segmentationId}`);
9
+ return;
10
+ }
11
+ if (!segmentation.representationData.Contour) {
12
+ console.warn(`No contour representation found for segmentation ${segmentationId}`);
13
+ return;
14
+ }
15
+ const { annotationUIDsMap } = segmentation?.representationData.Contour || {};
16
+ if (!annotationUIDsMap) {
17
+ console.warn(`No annotation map found for segmentation ${segmentationId}`);
18
+ return;
19
+ }
20
+ const annotationsUIDsSet = annotationUIDsMap?.get(segmentIndex);
21
+ if (!annotationsUIDsSet) {
22
+ console.warn(`Segmentation index ${segmentIndex} has no annotations in segmentation ${segmentationId}`);
23
+ return;
24
+ }
25
+ let targetUIDsSet;
26
+ if (targetSegmentationId && typeof targetSegmentationIndex === 'number') {
27
+ const targetSegmentation = getSegmentation(targetSegmentationId);
28
+ if (!targetSegmentation) {
29
+ console.warn(`Target segmentation ${targetSegmentationId} does not exist.`);
30
+ return;
31
+ }
32
+ if (!targetSegmentation.representationData.Contour) {
33
+ console.warn(`No contour representation found for target segmentation ${targetSegmentationId}`);
34
+ return;
35
+ }
36
+ targetUIDsSet =
37
+ targetSegmentation.representationData.Contour.annotationUIDsMap.get(targetSegmentationIndex);
38
+ if (!targetUIDsSet) {
39
+ targetUIDsSet = new Set();
40
+ targetSegmentation.representationData.Contour.annotationUIDsMap.set(targetSegmentationIndex, targetUIDsSet);
41
+ }
42
+ }
43
+ const polylinesCanvasMap = extractSegmentPolylines(segmentationId, segmentIndex);
44
+ if (!polylinesCanvasMap) {
45
+ console.warn(`Error extracting contour data from segment ${segmentIndex} in segmentation ${segmentationId}`);
46
+ return;
47
+ }
48
+ const keys = Array.from(polylinesCanvasMap?.keys());
49
+ const polylines = keys.map((key) => polylinesCanvasMap.get(key));
50
+ const holeDetectionResults = findContourHoles(polylines);
51
+ if (holeDetectionResults?.length > 0) {
52
+ holeDetectionResults.forEach((hole) => {
53
+ hole.holeIndexes.forEach((index) => {
54
+ const annotation = getAnnotation(keys[index]);
55
+ clearParentAnnotation(annotation);
56
+ if (targetSegmentationId &&
57
+ typeof targetSegmentationIndex === 'number') {
58
+ targetUIDsSet.add(annotation.annotationUID);
59
+ }
60
+ else {
61
+ annotationsUIDsSet.add(annotation.annotationUID);
62
+ }
63
+ });
64
+ });
65
+ }
66
+ }
@@ -0,0 +1 @@
1
+ export declare function getAnnotationsUIDMapFromSegmentation(segmentationId: string): Map<number, Set<string>>;
@@ -0,0 +1,17 @@
1
+ import { getSegmentation } from '../getSegmentation';
2
+ export function getAnnotationsUIDMapFromSegmentation(segmentationId) {
3
+ const segmentation = getSegmentation(segmentationId);
4
+ if (!segmentation) {
5
+ return;
6
+ }
7
+ const contourRepresentationData = segmentation.representationData
8
+ ?.Contour;
9
+ if (!contourRepresentationData) {
10
+ return;
11
+ }
12
+ const { annotationUIDsMap } = contourRepresentationData;
13
+ if (!annotationUIDsMap) {
14
+ return;
15
+ }
16
+ return annotationUIDsMap;
17
+ }
@@ -1,4 +1,5 @@
1
1
  export { getViewportAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
2
+ export { getAnnotationsUIDMapFromSegmentation } from './getAnnotationsUIDMapFromSegmentation';
2
3
  export { getAnnotationMapFromSegmentation } from './getAnnotationMapFromSegmentation';
3
4
  export { default as decimateContours } from './decimateContours';
4
5
  export { extractSegmentPolylines } from './extractSegmentPolylines';
@@ -6,3 +7,4 @@ export { removeCompleteContourAnnotation } from './removeCompleteContourAnnotati
6
7
  export { default as removeContourHoles } from './removeContourHoles';
7
8
  export { default as removeContourIslands } from './removeContourIslands';
8
9
  export { default as smoothContours } from './smoothContours';
10
+ export { default as convertContourHoles } from './convertContourHoles';
@@ -1,4 +1,6 @@
1
+ import { getAnnotationsUIDMapFromSegmentation } from './getAnnotationsUIDMapFromSegmentation';
1
2
  export { getViewportAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
3
+ export { getAnnotationsUIDMapFromSegmentation } from './getAnnotationsUIDMapFromSegmentation';
2
4
  export { getAnnotationMapFromSegmentation } from './getAnnotationMapFromSegmentation';
3
5
  export { default as decimateContours } from './decimateContours';
4
6
  export { extractSegmentPolylines } from './extractSegmentPolylines';
@@ -6,3 +8,4 @@ export { removeCompleteContourAnnotation } from './removeCompleteContourAnnotati
6
8
  export { default as removeContourHoles } from './removeContourHoles';
7
9
  export { default as removeContourIslands } from './removeContourIslands';
8
10
  export { default as smoothContours } from './smoothContours';
11
+ export { default as convertContourHoles } from './convertContourHoles';
@@ -1,6 +1,5 @@
1
1
  import { findContourHoles } from '../../../utilities/contours';
2
2
  import { getAnnotation } from '../../annotation/annotationState';
3
- import { triggerAnnotationRemoved } from '../../annotation/helpers/state';
4
3
  import { getSegmentation } from '../getSegmentation';
5
4
  import { extractSegmentPolylines } from './extractSegmentPolylines';
6
5
  import { removeCompleteContourAnnotation } from './removeCompleteContourAnnotation';
@@ -19,6 +19,7 @@ import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScale
19
19
  import { getCanvasCircleCorners, getCanvasCircleRadius, } from '../../utilities/math/circle';
20
20
  import { pointInEllipse } from '../../utilities/math/ellipse';
21
21
  import { BasicStatsCalculator } from '../../utilities/math/basic';
22
+ import { vec2, vec3 } from 'gl-matrix';
22
23
  const { transformWorldToIndex } = csUtils;
23
24
  class CircleROITool extends AnnotationTool {
24
25
  static { this.toolName = 'CircleROI'; }
@@ -32,6 +33,7 @@ class CircleROITool extends AnnotationTool {
32
33
  calculateStats: true,
33
34
  getTextLines: defaultGetTextLines,
34
35
  statsCalculator: BasicStatsCalculator,
36
+ simplified: true,
35
37
  },
36
38
  }) {
37
39
  super(toolProps, defaultToolProps);
@@ -41,12 +43,25 @@ class CircleROITool extends AnnotationTool {
41
43
  const { currentPoints, element } = eventDetail;
42
44
  const worldPos = currentPoints.world;
43
45
  const enabledElement = getEnabledElement(element);
44
- const { viewport, renderingEngine } = enabledElement;
46
+ const { viewport } = enabledElement;
45
47
  this.isDrawing = true;
46
48
  const camera = viewport.getCamera();
47
49
  const { viewPlaneNormal, viewUp } = camera;
48
50
  const referencedImageId = this.getReferencedImageId(viewport, worldPos, viewPlaneNormal, viewUp);
49
51
  const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
52
+ let points;
53
+ if (this.configuration.simplified) {
54
+ points = [[...worldPos], [...worldPos]];
55
+ }
56
+ else {
57
+ points = [
58
+ [...worldPos],
59
+ [...worldPos],
60
+ [...worldPos],
61
+ [...worldPos],
62
+ [...worldPos],
63
+ ];
64
+ }
50
65
  const annotation = {
51
66
  highlighted: true,
52
67
  invalidated: true,
@@ -71,7 +86,7 @@ class CircleROITool extends AnnotationTool {
71
86
  bottomRight: [0, 0, 0],
72
87
  },
73
88
  },
74
- points: [[...worldPos], [...worldPos]],
89
+ points,
75
90
  activeHandleIndex: null,
76
91
  },
77
92
  cachedStats: {},
@@ -94,18 +109,12 @@ class CircleROITool extends AnnotationTool {
94
109
  this.isPointNearTool = (element, annotation, canvasCoords, proximity) => {
95
110
  const enabledElement = getEnabledElement(element);
96
111
  const { viewport } = enabledElement;
97
- const { data } = annotation;
98
- const { points } = data.handles;
99
- const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
100
- const radius = getCanvasCircleRadius(canvasCoordinates);
101
- const radiusPoint = getCanvasCircleRadius([
102
- canvasCoordinates[0],
103
- canvasCoords,
104
- ]);
105
- if (Math.abs(radiusPoint - radius) < proximity / 2) {
106
- return true;
107
- }
108
- return false;
112
+ const { points } = annotation.data.handles;
113
+ const canvasHandles = points.map((p) => viewport.worldToCanvas(p));
114
+ const canvasCenter = canvasHandles[0];
115
+ const radius = getCanvasCircleRadius([canvasCenter, canvasHandles[1]]);
116
+ const radiusPoint = getCanvasCircleRadius([canvasCenter, canvasCoords]);
117
+ return Math.abs(radiusPoint - radius) < proximity / 2;
109
118
  };
110
119
  this.toolSelectedCallback = (evt, annotation) => {
111
120
  const eventDetail = evt.detail;
@@ -119,8 +128,6 @@ class CircleROITool extends AnnotationTool {
119
128
  };
120
129
  hideElementCursor(element);
121
130
  this._activateModify(element);
122
- const enabledElement = getEnabledElement(element);
123
- const { renderingEngine } = enabledElement;
124
131
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
125
132
  evt.preventDefault();
126
133
  };
@@ -147,8 +154,6 @@ class CircleROITool extends AnnotationTool {
147
154
  };
148
155
  this._activateModify(element);
149
156
  hideElementCursor(element);
150
- const enabledElement = getEnabledElement(element);
151
- const { renderingEngine } = enabledElement;
152
157
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
153
158
  evt.preventDefault();
154
159
  };
@@ -166,7 +171,6 @@ class CircleROITool extends AnnotationTool {
166
171
  this._deactivateModify(element);
167
172
  this._deactivateDraw(element);
168
173
  resetElementCursor(element);
169
- const { renderingEngine } = getEnabledElement(element);
170
174
  this.editData = null;
171
175
  this.isDrawing = false;
172
176
  if (this.isHandleOutsideImage &&
@@ -181,19 +185,39 @@ class CircleROITool extends AnnotationTool {
181
185
  this._dragDrawCallback = (evt) => {
182
186
  this.isDrawing = true;
183
187
  const eventDetail = evt.detail;
184
- const { element } = eventDetail;
185
- const { currentPoints } = eventDetail;
186
- const currentCanvasPoints = currentPoints.canvas;
188
+ const { element, currentPoints } = eventDetail;
189
+ const { world: worldPos, canvas: currentCanvasPoints } = currentPoints;
187
190
  const enabledElement = getEnabledElement(element);
188
191
  const { viewport } = enabledElement;
189
192
  const { canvasToWorld } = viewport;
190
193
  const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
191
194
  this.createMemo(element, annotation, { newAnnotation });
192
195
  const { data } = annotation;
193
- data.handles.points = [
194
- data.handles.points[0],
195
- canvasToWorld(currentCanvasPoints),
196
- ];
196
+ const centerWorld = data.handles.points[0];
197
+ const centerCanvas = viewport.worldToCanvas(centerWorld);
198
+ if (this.configuration.simplified) {
199
+ data.handles.points[1] = worldPos;
200
+ }
201
+ else {
202
+ const radiusCanvas = vec2.distance(centerCanvas, currentCanvasPoints);
203
+ data.handles.points[0] = [...centerWorld];
204
+ data.handles.points[1] = canvasToWorld([
205
+ centerCanvas[0],
206
+ centerCanvas[1] - radiusCanvas,
207
+ ]);
208
+ data.handles.points[2] = canvasToWorld([
209
+ centerCanvas[0],
210
+ centerCanvas[1] + radiusCanvas,
211
+ ]);
212
+ data.handles.points[3] = canvasToWorld([
213
+ centerCanvas[0] - radiusCanvas,
214
+ centerCanvas[1],
215
+ ]);
216
+ data.handles.points[4] = canvasToWorld([
217
+ centerCanvas[0] + radiusCanvas,
218
+ centerCanvas[1],
219
+ ]);
220
+ }
197
221
  annotation.invalidated = true;
198
222
  this.editData.hasMoved = true;
199
223
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
@@ -231,8 +255,6 @@ class CircleROITool extends AnnotationTool {
231
255
  this._dragHandle(evt);
232
256
  annotation.invalidated = true;
233
257
  }
234
- const enabledElement = getEnabledElement(element);
235
- const { renderingEngine } = enabledElement;
236
258
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
237
259
  if (annotation.invalidated) {
238
260
  triggerAnnotationModified(annotation, element, ChangeTypes.HandlesUpdated);
@@ -246,23 +268,36 @@ class CircleROITool extends AnnotationTool {
246
268
  const { annotation, handleIndex } = this.editData;
247
269
  const { data } = annotation;
248
270
  const { points } = data.handles;
249
- const canvasCoordinates = points.map((p) => worldToCanvas(p));
250
- const { currentPoints } = eventDetail;
251
- const currentCanvasPoints = currentPoints.canvas;
271
+ const { currentPoints, deltaPoints } = eventDetail;
252
272
  if (handleIndex === 0) {
253
- const dXCanvas = currentCanvasPoints[0] - canvasCoordinates[0][0];
254
- const dYCanvas = currentCanvasPoints[1] - canvasCoordinates[0][1];
255
- const canvasCenter = currentCanvasPoints;
256
- const canvasEnd = [
257
- canvasCoordinates[1][0] + dXCanvas,
258
- canvasCoordinates[1][1] + dYCanvas,
259
- ];
260
- points[0] = canvasToWorld(canvasCenter);
261
- points[1] = canvasToWorld(canvasEnd);
273
+ const worldPosDelta = deltaPoints.world;
274
+ points.forEach((point) => {
275
+ vec3.add(point, point, worldPosDelta);
276
+ });
262
277
  }
263
278
  else {
264
- points[1] = canvasToWorld(currentCanvasPoints);
279
+ const centerWorld = points[0];
280
+ const centerCanvas = worldToCanvas(centerWorld);
281
+ const currentCanvasPoint = currentPoints.canvas;
282
+ const newRadiusCanvas = vec2.distance(centerCanvas, currentCanvasPoint);
283
+ points[1] = canvasToWorld([
284
+ centerCanvas[0],
285
+ centerCanvas[1] - newRadiusCanvas,
286
+ ]);
287
+ points[2] = canvasToWorld([
288
+ centerCanvas[0],
289
+ centerCanvas[1] + newRadiusCanvas,
290
+ ]);
291
+ points[3] = canvasToWorld([
292
+ centerCanvas[0] - newRadiusCanvas,
293
+ centerCanvas[1],
294
+ ]);
295
+ points[4] = canvasToWorld([
296
+ centerCanvas[0] + newRadiusCanvas,
297
+ centerCanvas[1],
298
+ ]);
265
299
  }
300
+ annotation.invalidated = true;
266
301
  };
267
302
  this.cancel = (element) => {
268
303
  if (this.isDrawing) {
@@ -271,9 +306,8 @@ class CircleROITool extends AnnotationTool {
271
306
  this._deactivateModify(element);
272
307
  resetElementCursor(element);
273
308
  const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
274
- const { data } = annotation;
275
309
  annotation.highlighted = false;
276
- data.handles.activeHandleIndex = null;
310
+ annotation.data.handles.activeHandleIndex = null;
277
311
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
278
312
  if (newAnnotation) {
279
313
  triggerAnnotationCompleted(annotation);
@@ -351,8 +385,11 @@ class CircleROITool extends AnnotationTool {
351
385
  });
352
386
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
353
387
  const center = canvasCoordinates[0];
354
- const radius = getCanvasCircleRadius(canvasCoordinates);
355
- const canvasCorners = getCanvasCircleCorners(canvasCoordinates);
388
+ const radius = getCanvasCircleRadius([center, canvasCoordinates[1]]);
389
+ const canvasCorners = getCanvasCircleCorners([
390
+ center,
391
+ canvasCoordinates[1],
392
+ ]);
356
393
  const { centerPointRadius } = this.configuration;
357
394
  if (!data.cachedStats[targetId] ||
358
395
  data.cachedStats[targetId].areaUnit == null) {
@@ -400,7 +437,12 @@ class CircleROITool extends AnnotationTool {
400
437
  if (!isAnnotationLocked(annotationUID) &&
401
438
  !this.editData &&
402
439
  activeHandleIndex !== null) {
403
- activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
440
+ if (this.configuration.simplified) {
441
+ activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
442
+ }
443
+ else {
444
+ activeHandleCanvasCoords = canvasCoordinates;
445
+ }
404
446
  }
405
447
  if (activeHandleCanvasCoords) {
406
448
  const handleGroupUID = '0';
@@ -451,7 +493,7 @@ class CircleROITool extends AnnotationTool {
451
493
  }
452
494
  const textBoxPosition = viewport.worldToCanvas(data.handles.textBox.worldPosition);
453
495
  const textBoxUID = '1';
454
- const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, options);
496
+ const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, [center, canvasCoordinates[1]], {}, options);
455
497
  const { x: left, y: top, width, height } = boundingBox;
456
498
  data.handles.textBox.worldBoundingBox = {
457
499
  topLeft: viewport.canvasToWorld([left, top]),
@@ -471,8 +513,10 @@ class CircleROITool extends AnnotationTool {
471
513
  const wasInvalidated = annotation.invalidated;
472
514
  const { points } = data.handles;
473
515
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
516
+ const canvasCenter = canvasCoordinates[0];
517
+ const canvasTop = canvasCoordinates[1];
474
518
  const { viewPlaneNormal, viewUp } = viewport.getCamera();
475
- const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners(canvasCoordinates));
519
+ const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners([canvasCenter, canvasTop]));
476
520
  const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
477
521
  const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
478
522
  const { cachedStats } = data;
@@ -506,11 +550,7 @@ class CircleROITool extends AnnotationTool {
506
550
  [jMin, jMax],
507
551
  [kMin, kMax],
508
552
  ];
509
- const center = [
510
- (topLeftWorld[0] + bottomRightWorld[0]) / 2,
511
- (topLeftWorld[1] + bottomRightWorld[1]) / 2,
512
- (topLeftWorld[2] + bottomRightWorld[2]) / 2,
513
- ];
553
+ const center = points[0];
514
554
  const xRadius = Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2;
515
555
  const yRadius = Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2;
516
556
  const zRadius = Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2;
@@ -64,6 +64,8 @@ declare class SplineROITool extends ContourSegmentationBaseTool {
64
64
  protected isContourSegmentationTool(): boolean;
65
65
  protected renderAnnotationInstance(renderContext: AnnotationRenderContext): boolean;
66
66
  protected createInterpolatedSplineControl(annotation: any): void;
67
+ isSplineAnnotation(annotation: ContourAnnotation): boolean;
68
+ createSplineObjectFromType(annotation: ContourAnnotation, splineType: string): void;
67
69
  protected createAnnotation(evt: EventTypes.InteractionEventType): ContourAnnotation;
68
70
  private _renderStats;
69
71
  addControlPointCallback: (evt: EventTypes.InteractionEventType, annotation: SplineROIAnnotation) => void;
@@ -41,6 +41,12 @@ var SplineToolActions;
41
41
  SplineToolActions["AddControlPoint"] = "addControlPoint";
42
42
  SplineToolActions["DeleteControlPoint"] = "deleteControlPoint";
43
43
  })(SplineToolActions || (SplineToolActions = {}));
44
+ const splineToolNames = [
45
+ 'CatmullRomSplineROI',
46
+ 'LinearSplineROI',
47
+ 'BSplineROI',
48
+ 'CardinalSplineROI',
49
+ ];
44
50
  class SplineROITool extends ContourSegmentationBaseTool {
45
51
  static { this.toolName = 'SplineROI'; }
46
52
  static { this.SplineTypes = SplineTypesEnum; }
@@ -684,6 +690,18 @@ class SplineROITool extends ContourSegmentationBaseTool {
684
690
  }
685
691
  points.push(polyline[polyline.length - 1]);
686
692
  }
693
+ isSplineAnnotation(annotation) {
694
+ return splineToolNames.includes(annotation?.metadata?.toolName);
695
+ }
696
+ createSplineObjectFromType(annotation, splineType) {
697
+ const splineConfig = this._getSplineConfig(splineType);
698
+ const spline = new splineConfig.Class();
699
+ annotation.data.spline = {
700
+ type: splineConfig.type,
701
+ instance: spline,
702
+ resolution: splineConfig.resolution,
703
+ };
704
+ }
687
705
  createAnnotation(evt) {
688
706
  const contourAnnotation = super.createAnnotation(evt);
689
707
  const { world: worldPos } = evt.detail.currentPoints;
@@ -44,7 +44,7 @@ declare class CircleROIStartEndThresholdTool extends CircleROITool {
44
44
  bottomRight: Types.Point3;
45
45
  };
46
46
  };
47
- points: [Types.Point3, Types.Point3];
47
+ points: any;
48
48
  activeHandleIndex: any;
49
49
  };
50
50
  cachedStats: {
@@ -25,6 +25,7 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
25
25
  constructor(toolProps = {}, defaultToolProps = {
26
26
  supportedInteractionTypes: ['Mouse', 'Touch'],
27
27
  configuration: {
28
+ simplified: true,
28
29
  storePointData: false,
29
30
  numSlicesToPropagate: 10,
30
31
  calculatePointsInsideVolume: true,
@@ -59,6 +60,19 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
59
60
  const startCoord = this._getStartCoordinate(worldPos, spacingInNormal, viewPlaneNormal);
60
61
  const endCoord = this._getEndCoordinate(worldPos, spacingInNormal, viewPlaneNormal);
61
62
  const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
63
+ let points;
64
+ if (this.configuration.simplified) {
65
+ points = [[...worldPos], [...worldPos]];
66
+ }
67
+ else {
68
+ points = [
69
+ [...worldPos],
70
+ [...worldPos],
71
+ [...worldPos],
72
+ [...worldPos],
73
+ [...worldPos],
74
+ ];
75
+ }
62
76
  const annotation = {
63
77
  highlighted: true,
64
78
  invalidated: true,
@@ -87,7 +101,7 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
87
101
  bottomRight: [0, 0, 0],
88
102
  },
89
103
  },
90
- points: [[...worldPos], [...worldPos]],
104
+ points,
91
105
  activeHandleIndex: null,
92
106
  },
93
107
  cachedStats: {
@@ -168,9 +182,15 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
168
182
  const color = this.getStyle('color', styleSpecifier, annotation);
169
183
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
170
184
  const center = canvasCoordinates[0];
171
- const radius = getCanvasCircleRadius(canvasCoordinates);
185
+ const radius = getCanvasCircleRadius([
186
+ canvasCoordinates[0],
187
+ canvasCoordinates[1],
188
+ ]);
172
189
  const { centerPointRadius } = this.configuration;
173
- const canvasCorners = getCanvasCircleCorners(canvasCoordinates);
190
+ const canvasCorners = getCanvasCircleCorners([
191
+ canvasCoordinates[0],
192
+ canvasCoordinates[1],
193
+ ]);
174
194
  const focalPoint = viewport.getCamera().focalPoint;
175
195
  const viewplaneNormal = viewport.getCamera().viewPlaneNormal;
176
196
  let tempStartCoordinate = startCoordinate;
@@ -214,7 +234,12 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
214
234
  !this.editData &&
215
235
  activeHandleIndex !== null &&
216
236
  isMiddleSlice) {
217
- activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
237
+ if (this.configuration.simplified) {
238
+ activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
239
+ }
240
+ else {
241
+ activeHandleCanvasCoords = canvasCoordinates;
242
+ }
218
243
  }
219
244
  if (activeHandleCanvasCoords) {
220
245
  const handleGroupUID = '0';
@@ -274,7 +299,7 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
274
299
  }
275
300
  const textBoxPosition = viewport.worldToCanvas(data.handles.textBox.worldPosition);
276
301
  const textBoxUID = '1';
277
- const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, options);
302
+ const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, [canvasCoordinates[0], canvasCoordinates[1]], {}, options);
278
303
  const { x: left, y: top, width, height } = boundingBox;
279
304
  data.handles.textBox.worldBoundingBox = {
280
305
  topLeft: viewport.canvasToWorld([left, top]),
@@ -296,45 +321,26 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
296
321
  _computeProjectionPoints(annotation, imageVolume) {
297
322
  const { data, metadata } = annotation;
298
323
  const { viewPlaneNormal, spacingInNormal } = metadata;
299
- const { imageData } = imageVolume;
300
324
  const { startCoordinate, endCoordinate } = data;
301
325
  const { points } = data.handles;
302
- const startIJK = transformWorldToIndex(imageData, points[0]);
303
- const endIJK = transformWorldToIndex(imageData, points[0]);
304
326
  const handlesToStart = csUtils.deepClone(points);
305
- const startWorld = vec3.create();
306
- imageData.indexToWorldVec3(startIJK, startWorld);
307
- const endWorld = vec3.create();
308
- imageData.indexToWorldVec3(endIJK, endWorld);
309
- const projectionAxisIndex = this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
310
- if (projectionAxisIndex == 2) {
311
- startWorld[2] = startCoordinate;
312
- endWorld[2] = endCoordinate;
313
- handlesToStart[0][2] = startCoordinate;
314
- handlesToStart[1][2] = startCoordinate;
315
- }
316
- else if (projectionAxisIndex == 0) {
317
- startWorld[0] = startCoordinate;
318
- endWorld[0] = endCoordinate;
319
- handlesToStart[0][0] = startCoordinate;
320
- handlesToStart[1][0] = startCoordinate;
321
- }
322
- else if (projectionAxisIndex == 1) {
323
- startWorld[1] = startCoordinate;
324
- endWorld[1] = endCoordinate;
325
- handlesToStart[0][1] = startCoordinate;
326
- handlesToStart[1][1] = startCoordinate;
327
- }
328
- const direction = vec3.create();
329
- vec3.subtract(direction, endWorld, startWorld);
330
- const distance = vec3.length(direction);
331
- vec3.normalize(direction, direction);
327
+ const startWorld = vec3.clone(points[0]);
328
+ const endWorld = vec3.clone(points[0]);
329
+ const indexOfNormal = this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
330
+ startWorld[indexOfNormal] = startCoordinate;
331
+ endWorld[indexOfNormal] = endCoordinate;
332
+ handlesToStart.forEach((handlePoint) => {
333
+ handlePoint[indexOfNormal] = startCoordinate;
334
+ });
335
+ const distance = vec3.distance(startWorld, endWorld);
332
336
  const newProjectionPoints = [];
333
- const basePoints = points;
334
- for (let dist = 0; dist < distance; dist += spacingInNormal) {
335
- newProjectionPoints.push(basePoints.map((point) => {
337
+ if (distance >= 0) {
338
+ newProjectionPoints.push(handlesToStart.map((p) => Array.from(p)));
339
+ }
340
+ for (let dist = spacingInNormal; dist <= distance; dist += spacingInNormal) {
341
+ newProjectionPoints.push(handlesToStart.map((point) => {
336
342
  const newPoint = vec3.create();
337
- vec3.scaleAndAdd(newPoint, point, direction, dist);
343
+ vec3.scaleAndAdd(newPoint, point, viewPlaneNormal, dist);
338
344
  return Array.from(newPoint);
339
345
  }));
340
346
  }
@@ -348,11 +354,18 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
348
354
  const pointsInsideVolume = [[]];
349
355
  const image = this.getTargetImageData(targetId);
350
356
  const canvasCoordinates = data.handles.points.map((p) => viewport.worldToCanvas(p));
351
- const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners(canvasCoordinates));
352
- const pos1 = viewport.canvasToWorld(topLeftCanvas);
353
- const pos2 = viewport.canvasToWorld(bottomRightCanvas);
354
- const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(viewPlaneNormal, viewUp, pos1, pos2);
355
- const measureInfo = getCalibratedLengthUnitsAndScale(image, data.handles);
357
+ const baseTopLeftCanvas = getCanvasCircleCorners([
358
+ canvasCoordinates[0],
359
+ canvasCoordinates[1],
360
+ ])[0];
361
+ const baseBottomRightCanvas = getCanvasCircleCorners([
362
+ canvasCoordinates[0],
363
+ canvasCoordinates[1],
364
+ ])[1];
365
+ const basePos1 = viewport.canvasToWorld(baseTopLeftCanvas);
366
+ const basePos2 = viewport.canvasToWorld(baseBottomRightCanvas);
367
+ const { worldWidth, worldHeight } = getWorldWidthAndHeightFromTwoPoints(viewPlaneNormal, viewUp, basePos1, basePos2);
368
+ const measureInfo = getCalibratedLengthUnitsAndScale(image, data.handles.points);
356
369
  const aspect = getCalibratedAspect(image);
357
370
  const area = Math.abs(Math.PI *
358
371
  (worldWidth / measureInfo.scale / 2) *
@@ -367,8 +380,11 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
367
380
  continue;
368
381
  }
369
382
  const centerWorld = projectionPoints[i][0];
370
- const canvasCoordinates = projectionPoints[i].map((p) => viewport.worldToCanvas(p));
371
- const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners(canvasCoordinates));
383
+ const currentCanvasCoordinates = projectionPoints[i].map((p) => viewport.worldToCanvas(p));
384
+ const [topLeftCanvas, bottomRightCanvas] = (getCanvasCircleCorners([
385
+ currentCanvasCoordinates[0],
386
+ currentCanvasCoordinates[1],
387
+ ]));
372
388
  const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
373
389
  const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
374
390
  const worldPos1 = topLeftWorld;
@@ -109,7 +109,13 @@ export interface AdvancedMagnifyAnnotation extends Annotation {
109
109
  export interface CircleROIAnnotation extends Annotation {
110
110
  data: {
111
111
  handles: {
112
- points: [Types.Point3, Types.Point3];
112
+ points: [
113
+ Types.Point3,
114
+ Types.Point3,
115
+ Types.Point3,
116
+ Types.Point3,
117
+ Types.Point3
118
+ ];
113
119
  activeHandleIndex: number | null;
114
120
  textBox?: {
115
121
  hasMoved: boolean;
@@ -287,7 +293,7 @@ export interface CircleROIStartEndThresholdAnnotation extends Annotation {
287
293
  statistics?: ROICachedStats;
288
294
  };
289
295
  handles: {
290
- points: [Types.Point3, Types.Point3];
296
+ points: Types.Point3[];
291
297
  activeHandleIndex: number | null;
292
298
  textBox?: {
293
299
  hasMoved: boolean;