@cornerstonejs/tools 1.49.2 → 1.50.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 (68) hide show
  1. package/dist/cjs/tools/annotation/LivewireContourTool.d.ts +5 -0
  2. package/dist/cjs/tools/annotation/LivewireContourTool.js +133 -44
  3. package/dist/cjs/tools/annotation/LivewireContourTool.js.map +1 -1
  4. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +1 -1
  5. package/dist/cjs/utilities/contours/findHandlePolylineIndex.d.ts +2 -0
  6. package/dist/cjs/utilities/contours/findHandlePolylineIndex.js +35 -0
  7. package/dist/cjs/utilities/contours/findHandlePolylineIndex.js.map +1 -0
  8. package/dist/cjs/utilities/contours/index.d.ts +2 -1
  9. package/dist/cjs/utilities/contours/index.js +3 -1
  10. package/dist/cjs/utilities/contours/index.js.map +1 -1
  11. package/dist/cjs/utilities/contours/interpolation/interpolate.js +1 -1
  12. package/dist/cjs/utilities/contours/interpolation/interpolate.js.map +1 -1
  13. package/dist/cjs/utilities/contours/reverseIfAntiClockwise.d.ts +1 -1
  14. package/dist/cjs/utilities/contours/reverseIfAntiClockwise.js +5 -5
  15. package/dist/cjs/utilities/contours/reverseIfAntiClockwise.js.map +1 -1
  16. package/dist/cjs/utilities/livewire/LiveWirePath.d.ts +3 -0
  17. package/dist/cjs/utilities/livewire/LiveWirePath.js +12 -0
  18. package/dist/cjs/utilities/livewire/LiveWirePath.js.map +1 -1
  19. package/dist/cjs/utilities/livewire/LivewireScissors.d.ts +2 -1
  20. package/dist/cjs/utilities/livewire/LivewireScissors.js +26 -7
  21. package/dist/cjs/utilities/livewire/LivewireScissors.js.map +1 -1
  22. package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js +1 -0
  23. package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
  24. package/dist/esm/tools/annotation/LivewireContourTool.js +131 -44
  25. package/dist/esm/tools/annotation/LivewireContourTool.js.map +1 -1
  26. package/dist/esm/utilities/contours/findHandlePolylineIndex.js +32 -0
  27. package/dist/esm/utilities/contours/findHandlePolylineIndex.js.map +1 -0
  28. package/dist/esm/utilities/contours/index.js +2 -1
  29. package/dist/esm/utilities/contours/index.js.map +1 -1
  30. package/dist/esm/utilities/contours/interpolation/interpolate.js +1 -1
  31. package/dist/esm/utilities/contours/interpolation/interpolate.js.map +1 -1
  32. package/dist/esm/utilities/contours/reverseIfAntiClockwise.js +5 -5
  33. package/dist/esm/utilities/contours/reverseIfAntiClockwise.js.map +1 -1
  34. package/dist/esm/utilities/livewire/LiveWirePath.js +12 -0
  35. package/dist/esm/utilities/livewire/LiveWirePath.js.map +1 -1
  36. package/dist/esm/utilities/livewire/LivewireScissors.js +26 -7
  37. package/dist/esm/utilities/livewire/LivewireScissors.js.map +1 -1
  38. package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js +1 -0
  39. package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
  40. package/dist/types/tools/annotation/LivewireContourTool.d.ts +5 -0
  41. package/dist/types/tools/annotation/LivewireContourTool.d.ts.map +1 -1
  42. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +1 -1
  43. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
  44. package/dist/types/utilities/contours/findHandlePolylineIndex.d.ts +3 -0
  45. package/dist/types/utilities/contours/findHandlePolylineIndex.d.ts.map +1 -0
  46. package/dist/types/utilities/contours/index.d.ts +2 -1
  47. package/dist/types/utilities/contours/index.d.ts.map +1 -1
  48. package/dist/types/utilities/contours/interpolation/interpolate.d.ts.map +1 -1
  49. package/dist/types/utilities/contours/reverseIfAntiClockwise.d.ts +1 -1
  50. package/dist/types/utilities/contours/reverseIfAntiClockwise.d.ts.map +1 -1
  51. package/dist/types/utilities/livewire/LiveWirePath.d.ts +3 -0
  52. package/dist/types/utilities/livewire/LiveWirePath.d.ts.map +1 -1
  53. package/dist/types/utilities/livewire/LivewireScissors.d.ts +2 -1
  54. package/dist/types/utilities/livewire/LivewireScissors.d.ts.map +1 -1
  55. package/dist/types/utilities/segmentation/InterpolationManager/InterpolationManager.d.ts.map +1 -1
  56. package/dist/umd/index.js +1 -1
  57. package/dist/umd/index.js.map +1 -1
  58. package/package.json +3 -3
  59. package/src/tools/annotation/EllipticalROITool.ts +1 -1
  60. package/src/tools/annotation/LivewireContourTool.ts +220 -71
  61. package/src/types/ToolSpecificAnnotationTypes.ts +2 -0
  62. package/src/utilities/contours/findHandlePolylineIndex.ts +52 -0
  63. package/src/utilities/contours/index.ts +2 -0
  64. package/src/utilities/contours/interpolation/interpolate.ts +3 -2
  65. package/src/utilities/contours/reverseIfAntiClockwise.ts +11 -13
  66. package/src/utilities/livewire/LiveWirePath.ts +24 -0
  67. package/src/utilities/livewire/LivewireScissors.ts +52 -8
  68. package/src/utilities/segmentation/InterpolationManager/InterpolationManager.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.49.2",
3
+ "version": "1.50.0",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
30
30
  },
31
31
  "dependencies": {
32
- "@cornerstonejs/core": "^1.49.2",
32
+ "@cornerstonejs/core": "^1.50.0",
33
33
  "comlink": "^4.4.1",
34
34
  "lodash.clonedeep": "4.5.0",
35
35
  "lodash.get": "^4.4.2"
@@ -53,5 +53,5 @@
53
53
  "type": "individual",
54
54
  "url": "https://ohif.org/donate"
55
55
  },
56
- "gitHead": "c405ce8a57e5e3d67fab7f1b02e105a6a689ae93"
56
+ "gitHead": "20550ac144790828fae0b1358e9cbcfcf7af6eb6"
57
57
  }
@@ -1045,7 +1045,7 @@ class EllipticalROITool extends AnnotationTool {
1045
1045
 
1046
1046
  if (this._isInsideVolume(worldPos1Index, worldPos2Index, dimensions)) {
1047
1047
  this.isHandleOutsideImage = false;
1048
-
1048
+
1049
1049
  const iMin = Math.min(worldPos1Index[0], worldPos2Index[0]);
1050
1050
  const iMax = Math.max(worldPos1Index[0], worldPos2Index[0]);
1051
1051
 
@@ -22,22 +22,25 @@ import type {
22
22
  SVGDrawingHelper,
23
23
  } from '../../types';
24
24
  import { math, triggerAnnotationRenderForViewportIds } from '../../utilities';
25
+ import findHandlePolylineIndex from '../../utilities/contours/findHandlePolylineIndex';
25
26
  import { LivewireContourAnnotation } from '../../types/ToolSpecificAnnotationTypes';
26
- import {
27
- AnnotationCompletedEventDetail,
28
- AnnotationModifiedEventDetail,
29
- } from '../../types/EventTypes';
27
+ import { AnnotationModifiedEventDetail } from '../../types/EventTypes';
28
+ import reverseIfAntiClockwise from '../../utilities/contours/reverseIfAntiClockwise';
30
29
 
31
30
  import { LivewireScissors } from '../../utilities/livewire/LivewireScissors';
32
31
  import { LivewirePath } from '../../utilities/livewire/LiveWirePath';
33
32
  import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
34
33
  import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
35
34
 
35
+ const { isEqual } = csUtils;
36
+
36
37
  const CLICK_CLOSE_CURVE_SQR_DIST = 10 ** 2; // px
37
38
 
38
39
  class LivewireContourTool extends ContourSegmentationBaseTool {
39
40
  public static toolName: string;
40
41
  private scissors: LivewireScissors;
42
+ /** The scissors from the right handle, used for editing */
43
+ private scissorsRight: LivewireScissors;
41
44
 
42
45
  touchDragCallback: any;
43
46
  mouseDragCallback: any;
@@ -50,9 +53,12 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
50
53
  lastCanvasPoint?: Types.Point2;
51
54
  confirmedPath?: LivewirePath;
52
55
  currentPath?: LivewirePath;
56
+ /** The next path segment, on the other side of the handle */
57
+ confirmedPathRight?: LivewirePath;
53
58
  closed?: boolean;
54
59
  worldToSlice?: (point: Types.Point3) => Types.Point2;
55
60
  sliceToWorld?: (point: Types.Point2) => Types.Point3;
61
+ originalPath?: Types.Point3[];
56
62
  } | null;
57
63
  isDrawing: boolean;
58
64
  isHandleOutsideImage = false;
@@ -69,23 +75,9 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
69
75
  super(toolProps, defaultToolProps);
70
76
  }
71
77
 
72
- /**
73
- * Based on the current position of the mouse and the current imageId to create
74
- * a CircleROI Annotation and stores it in the annotationManager
75
- *
76
- * @param evt - EventTypes.NormalizedMouseEventType
77
- * @returns The annotation object.
78
- *
79
- */
80
- addNewAnnotation(
81
- evt: EventTypes.InteractionEventType
82
- ): LivewireContourAnnotation {
83
- const eventDetail = evt.detail;
84
- const { currentPoints, element } = eventDetail;
85
- const { world: worldPos, canvas: canvasPos } = currentPoints;
86
-
78
+ protected setupBaseEditData(worldPos, element, annotation, rightPos?) {
87
79
  const enabledElement = getEnabledElement(element);
88
- const { viewport, renderingEngine } = enabledElement;
80
+ const { viewport } = enabledElement;
89
81
 
90
82
  this.isDrawing = true;
91
83
 
@@ -157,40 +149,79 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
157
149
  height,
158
150
  voiRange
159
151
  );
152
+ if (rightPos) {
153
+ this.scissorsRight = LivewireScissors.createInstanceFromRawPixelData(
154
+ scalarData as Float32Array,
155
+ width,
156
+ height,
157
+ voiRange
158
+ );
159
+ this.scissorsRight.startSearch(worldToSlice(rightPos));
160
+ }
160
161
 
162
+ // Scissors always start at the startPos for both editing handles and
163
+ // for initial rendering
161
164
  this.scissors.startSearch(startPos);
162
165
 
166
+ const newAnnotation = !rightPos;
167
+
163
168
  const confirmedPath = new LivewirePath();
164
169
  const currentPath = new LivewirePath();
170
+ const currentPathNext = newAnnotation ? undefined : new LivewirePath();
165
171
 
166
172
  confirmedPath.addPoint(startPos);
167
173
  confirmedPath.addControlPoint(startPos);
168
174
 
169
- const annotation = this.createAnnotation(evt) as LivewireContourAnnotation;
170
-
171
- this.addAnnotation(annotation, element);
172
-
173
175
  const viewportIdsToRender = getViewportIdsWithToolToRender(
174
176
  element,
175
177
  this.getToolName()
176
178
  );
177
179
 
180
+ const lastCanvasPoint = viewport.worldToCanvas(worldPos);
181
+
178
182
  this.editData = {
179
183
  annotation,
180
184
  viewportIdsToRender,
181
- newAnnotation: true,
185
+ newAnnotation,
182
186
  hasMoved: false,
183
- lastCanvasPoint: canvasPos,
184
- confirmedPath: confirmedPath,
185
- currentPath: currentPath,
187
+ lastCanvasPoint,
188
+ confirmedPath,
189
+ currentPath,
190
+ confirmedPathRight: currentPathNext,
186
191
  closed: false,
192
+ handleIndex:
193
+ this.editData?.handleIndex ?? annotation.handles?.activeHandleIndex,
187
194
  worldToSlice,
188
195
  sliceToWorld,
189
196
  };
197
+ }
198
+
199
+ /**
200
+ * Based on the current position of the mouse and the current imageId to create
201
+ * a CircleROI Annotation and stores it in the annotationManager
202
+ *
203
+ * @param evt - EventTypes.NormalizedMouseEventType
204
+ * @returns The annotation object.
205
+ *
206
+ */
207
+ addNewAnnotation(
208
+ evt: EventTypes.InteractionEventType
209
+ ): LivewireContourAnnotation {
210
+ const eventDetail = evt.detail;
211
+ const { currentPoints, element } = eventDetail;
212
+ const { world: worldPos } = currentPoints;
213
+ const { renderingEngine } = getEnabledElement(element);
214
+ const annotation = this.createAnnotation(evt) as LivewireContourAnnotation;
215
+
216
+ this.setupBaseEditData(worldPos, element, annotation);
217
+ this.addAnnotation(annotation, element);
190
218
 
191
219
  this._activateDraw(element);
192
220
  evt.preventDefault();
193
- triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
221
+ triggerAnnotationRenderForViewportIds(
222
+ renderingEngine,
223
+ this.editData.viewportIdsToRender
224
+ );
194
225
 
195
226
  return annotation;
196
227
  }
@@ -325,20 +356,37 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
325
356
  removeAnnotation(annotation.annotationUID);
326
357
  }
327
358
 
359
+ // Reverse the points if needed, ensuring both the handles and the
360
+ // polyline is also reversed.
361
+ const { worldToSlice } = this.editData;
362
+ if (worldToSlice) {
363
+ reverseIfAntiClockwise(
364
+ data.handles.points.map(worldToSlice),
365
+ data.handles.points,
366
+ data.contour.polyline
367
+ );
368
+ }
369
+
328
370
  triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
329
371
 
330
- if (newAnnotation) {
331
- const eventType = Events.ANNOTATION_COMPLETED;
332
- const eventDetail: AnnotationCompletedEventDetail = {
333
- annotation,
334
- changeType: ChangeTypes.Completed,
335
- };
372
+ const eventType = newAnnotation
373
+ ? Events.ANNOTATION_COMPLETED
374
+ : Events.ANNOTATION_MODIFIED;
375
+ const { viewportId, renderingEngineId } = enabledElement;
376
+ const eventDetailModified: AnnotationModifiedEventDetail = {
377
+ annotation,
378
+ viewportId,
379
+ renderingEngineId,
380
+ changeType: newAnnotation
381
+ ? ChangeTypes.Completed
382
+ : ChangeTypes.HandlesUpdated,
383
+ };
336
384
 
337
- triggerEvent(eventTarget, eventType, eventDetail);
338
- }
385
+ triggerEvent(eventTarget, eventType, eventDetailModified);
339
386
 
340
387
  this.editData = null;
341
388
  this.scissors = null;
389
+ this.scissorsRight = null;
342
390
  this.isDrawing = false;
343
391
  };
344
392
 
@@ -354,7 +402,8 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
354
402
  const eventDetail = evt.detail;
355
403
  const { element } = eventDetail;
356
404
  const { currentPoints } = eventDetail;
357
- const { canvas: canvasPos, world: worldPos } = currentPoints;
405
+ const { canvas: canvasPos, world: worldPosOriginal } = currentPoints;
406
+ let worldPos = worldPosOriginal;
358
407
  const enabledElement = getEnabledElement(element);
359
408
  const { viewport, renderingEngine } = enabledElement;
360
409
  const controlPoints = this.editData.currentPath.getControlPoints();
@@ -395,10 +444,27 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
395
444
  this.editData.closed = this.editData.closed || closePath;
396
445
  this.editData.confirmedPath = this.editData.currentPath;
397
446
 
398
- // Add the current cursor position as a new control point after clicking
399
- this.editData.confirmedPath.addControlPoint(
400
- this.editData.currentPath.getLastPoint()
447
+ const smoothPathCount = this.scissors.smoothPathCount(
448
+ this.editData.confirmedPath.pointArray,
449
+ this.editData.currentPath.getLastControlPoint()
401
450
  );
451
+ if (smoothPathCount) {
452
+ this.editData.currentPath.removeLastPoints(smoothPathCount);
453
+ annotation.data.contour.polyline.splice(
454
+ annotation.data.contour.polyline.length - smoothPathCount,
455
+ smoothPathCount
456
+ );
457
+ worldPos =
458
+ annotation.data.contour.polyline[
459
+ annotation.data.contour.polyline.length - 1
460
+ ];
461
+ }
462
+
463
+ // Add the current cursor position as a new control point after clicking
464
+ const lastPoint = this.editData.currentPath.getLastPoint();
465
+
466
+ this.editData.confirmedPath.addControlPoint(lastPoint);
467
+ annotation.data.handles.points.push(sliceToWorld(lastPoint));
402
468
 
403
469
  // Start a new search starting at the last control point
404
470
  this.scissors.startSearch(worldToSlice(worldPos));
@@ -442,10 +508,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
442
508
 
443
509
  const pathPoints = this.scissors.findPathToPoint(slicePoint);
444
510
  const currentPath = new LivewirePath();
445
-
446
- for (let i = 0, len = pathPoints.length; i < len; i++) {
447
- currentPath.addPoint(pathPoints[i]);
448
- }
511
+ currentPath.addPoints(pathPoints);
449
512
 
450
513
  // Merge the "confirmed" path that goes from the first control point to the
451
514
  // last one with the current path that goes from the last control point to
@@ -459,38 +522,122 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
459
522
  evt.preventDefault();
460
523
  };
461
524
 
525
+ public editHandle(
526
+ worldPos: Types.Point3,
527
+ element,
528
+ annotation,
529
+ handleIndex: number
530
+ ) {
531
+ const { data } = annotation;
532
+ const { points: handlePoints } = data.handles;
533
+ const { length: numHandles } = handlePoints;
534
+ const previousHandle =
535
+ handlePoints[(handleIndex - 1 + numHandles) % numHandles];
536
+ const nextHandle = handlePoints[(handleIndex + 1) % numHandles];
537
+
538
+ if (!this.editData?.confirmedPathRight) {
539
+ this.setupBaseEditData(previousHandle, element, annotation, nextHandle);
540
+ const { polyline } = data.contour;
541
+ const confirmedPath = new LivewirePath();
542
+ const confirmedPathRight = new LivewirePath();
543
+ const { worldToSlice } = this.editData;
544
+ const previousIndex = findHandlePolylineIndex(
545
+ annotation,
546
+ handleIndex - 1
547
+ );
548
+ const nextIndex = findHandlePolylineIndex(annotation, handleIndex + 1);
549
+ if (nextIndex === -1 || previousIndex === -1) {
550
+ throw new Error(
551
+ `Can't find handle index ${nextIndex === -1 && nextHandle} ${
552
+ previousIndex === -1 && previousHandle
553
+ }`
554
+ );
555
+ }
556
+ if (handleIndex === 0) {
557
+ // For this case, the next/previous indices are swapped, and the
558
+ // path data gets inserted in between the newly generated data, so
559
+ // handle this case specially
560
+ confirmedPathRight.addPoints(
561
+ polyline.slice(nextIndex + 1, previousIndex).map(worldToSlice)
562
+ );
563
+ } else if (nextIndex < previousIndex) {
564
+ throw new Error(
565
+ `Expected right index after left index, but were: ${previousIndex} ${nextIndex}`
566
+ );
567
+ } else {
568
+ confirmedPath.addPoints(
569
+ polyline.slice(0, previousIndex + 1).map(worldToSlice)
570
+ );
571
+ confirmedPathRight.addPoints(
572
+ polyline.slice(nextIndex, polyline.length).map(worldToSlice)
573
+ );
574
+ }
575
+ this.editData.confirmedPath = confirmedPath;
576
+ this.editData.confirmedPathRight = confirmedPathRight;
577
+ }
578
+ const { editData, scissors } = this;
579
+ const { worldToSlice, sliceToWorld } = editData;
580
+
581
+ const { activeHandleIndex } = data.handles;
582
+ if (activeHandleIndex === null || activeHandleIndex === undefined) {
583
+ data.handle.activeHandleIndex = handleIndex;
584
+ } else if (activeHandleIndex !== handleIndex) {
585
+ throw new Error(
586
+ `Trying to edit a different handle than the one currently being edited ${handleIndex}!==${data.handles.activeHandleIndex}`
587
+ );
588
+ }
589
+ const slicePos = worldToSlice(worldPos);
590
+ if (
591
+ slicePos[0] < 0 ||
592
+ slicePos[0] >= scissors.width ||
593
+ slicePos[1] < 0 ||
594
+ slicePos[1] >= scissors.height
595
+ ) {
596
+ // Find path to point hangs if the position is outside the image data
597
+ return;
598
+ }
599
+ handlePoints[handleIndex] = sliceToWorld(slicePos);
600
+
601
+ const pathPointsLeft = scissors.findPathToPoint(slicePos);
602
+ const pathPointsRight = this.scissorsRight.findPathToPoint(slicePos);
603
+ const currentPath = new LivewirePath();
604
+
605
+ // Merge the "confirmed" path that goes from the first control point to the
606
+ // last one with the current path that goes from the last control point to
607
+ // the cursor point
608
+ currentPath.prependPath(editData.confirmedPath);
609
+ if (handleIndex !== 0) {
610
+ currentPath.addPoints(pathPointsLeft);
611
+ }
612
+ currentPath.addPoints(pathPointsRight.reverse());
613
+ currentPath.appendPath(editData.confirmedPathRight);
614
+ if (handleIndex === 0) {
615
+ currentPath.addPoints(pathPointsLeft);
616
+ }
617
+
618
+ // Store the new path
619
+ editData.currentPath = currentPath;
620
+
621
+ annotation.invalidated = true;
622
+ editData.hasMoved = true;
623
+ }
624
+
462
625
  private _dragCallback = (evt: EventTypes.InteractionEventType): void => {
463
626
  this.isDrawing = true;
464
627
  const eventDetail = evt.detail;
465
628
  const { element } = eventDetail;
466
629
 
467
630
  const { annotation, viewportIdsToRender, handleIndex } = this.editData;
468
- const { data } = annotation;
469
-
470
631
  if (handleIndex === undefined) {
471
- // Drag mode - moving handle
472
- const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
473
- const worldPosDelta = deltaPoints.world;
474
-
475
- const points = data.contour.polyline;
476
-
477
- points.forEach((point) => {
478
- point[0] += worldPosDelta[0];
479
- point[1] += worldPosDelta[1];
480
- point[2] += worldPosDelta[2];
481
- });
482
- annotation.invalidated = true;
632
+ // Drag mode - moving object
633
+ console.warn('No drag implemented for livewire');
483
634
  } else {
484
635
  // Move mode - after double click, and mouse move to draw
485
636
  const { currentPoints } = eventDetail;
486
637
  const worldPos = currentPoints.world;
487
-
488
- data.handles.points[handleIndex] = [...worldPos];
489
- annotation.invalidated = true;
638
+ this.editHandle(worldPos, element, annotation, handleIndex);
490
639
  }
491
640
 
492
- this.editData.hasMoved = true;
493
-
494
641
  const enabledElement = getEnabledElement(element);
495
642
  const { renderingEngine } = enabledElement;
496
643
 
@@ -644,26 +791,27 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
644
791
  const { viewport } = enabledElement;
645
792
  const { worldToCanvas } = viewport;
646
793
  const annotation = renderContext.annotation as LivewireContourAnnotation;
647
- const { annotationUID, data } = annotation;
794
+ const { annotationUID, data, highlighted } = annotation;
648
795
  const { handles } = data;
649
796
  const newAnnotation = this.editData?.newAnnotation;
650
797
  const { lineWidth, lineDash, color } = annotationStyle;
651
798
 
652
- // Render the first control point only when the annotaion is drawn for the
799
+ // Render the first control point only when the annotation is drawn for the
653
800
  // first time to make it easier to know where the user needs to click to
654
801
  // to close the ROI.
655
802
  if (
656
- newAnnotation &&
657
- annotation.annotationUID === this.editData?.annotation?.annotationUID
803
+ highlighted ||
804
+ (newAnnotation &&
805
+ annotation.annotationUID === this.editData?.annotation?.annotationUID)
658
806
  ) {
659
807
  const handleGroupUID = '0';
660
- const startPoint = worldToCanvas(handles.points[0]);
808
+ const canvasHandles = handles.points.map(worldToCanvas);
661
809
 
662
810
  drawHandlesSvg(
663
811
  svgDrawingHelper,
664
812
  annotationUID,
665
813
  handleGroupUID,
666
- [startPoint],
814
+ canvasHandles,
667
815
  {
668
816
  color,
669
817
  lineDash,
@@ -686,9 +834,10 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
686
834
  return;
687
835
  }
688
836
 
837
+ const { annotation, sliceToWorld } = this.editData;
838
+
689
839
  const { pointArray: imagePoints } = livewirePath;
690
840
  const worldPolylinePoints: Types.Point3[] = [];
691
- const { annotation, sliceToWorld } = this.editData;
692
841
 
693
842
  for (let i = 0, len = imagePoints.length; i < len; i++) {
694
843
  const imagePoint = imagePoints[i];
@@ -444,3 +444,5 @@ export interface VideoRedactionAnnotation extends Annotation {
444
444
  active: boolean;
445
445
  };
446
446
  }
447
+
448
+ export type { ContourAnnotation };
@@ -0,0 +1,52 @@
1
+ import { utilities as csUtils } from '@cornerstonejs/core';
2
+ import { vec3 } from 'gl-matrix';
3
+
4
+ import { ContourAnnotation } from '../../types/ToolSpecificAnnotationTypes';
5
+
6
+ const { isEqual } = csUtils;
7
+
8
+ /**
9
+ * Finds the index in the polyline of the specified handle. If the handle
10
+ * doesn't match a polyline point, then finds the closest polyline point.
11
+ *
12
+ * Assumes polyline is in the same orientation as the handles.
13
+ *
14
+ * @param annotation - to find the polyline and handles in
15
+ * @param handleIndex - the index of hte handle to look for.
16
+ * Negative values are treated relative to the end of the handle index.
17
+ * @returns Index in polyline of the closest handle
18
+ * * 0 for handleIndex 0
19
+ * * length for `handleIndex===handles length`
20
+ */
21
+ export default function findHandlePolylineIndex(
22
+ annotation: ContourAnnotation,
23
+ handleIndex: number
24
+ ): number {
25
+ const { polyline } = annotation.data.contour;
26
+ const { points } = annotation.data.handles;
27
+ const { length } = points;
28
+ if (handleIndex === length) {
29
+ return polyline.length;
30
+ }
31
+ if (handleIndex < 0) {
32
+ handleIndex = (handleIndex + length) % length;
33
+ }
34
+ if (handleIndex === 0) {
35
+ return 0;
36
+ }
37
+ const handle = points[handleIndex];
38
+ const index = polyline.findIndex((point) => isEqual(handle, point));
39
+ if (index !== -1) {
40
+ return index;
41
+ }
42
+ // Need to find nearest
43
+ let closestDistance = Infinity;
44
+ return polyline.reduce((closestIndex, point, testIndex) => {
45
+ const distance = vec3.squaredDistance(point, handle);
46
+ if (distance < closestDistance) {
47
+ closestDistance = distance;
48
+ return testIndex;
49
+ }
50
+ return closestIndex;
51
+ }, -1);
52
+ }
@@ -5,6 +5,7 @@ import { generateContourSetsFromLabelmap } from './generateContourSetsFromLabelm
5
5
  import AnnotationToPointData from './AnnotationToPointData';
6
6
  import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
7
7
  import * as interpolation from './interpolation';
8
+ import findHandlePolylineIndex from './findHandlePolylineIndex';
8
9
 
9
10
  export {
10
11
  contourFinder,
@@ -14,4 +15,5 @@ export {
14
15
  AnnotationToPointData,
15
16
  interpolation,
16
17
  acceptAutogeneratedInterpolations,
18
+ findHandlePolylineIndex,
17
19
  };
@@ -167,8 +167,9 @@ function _linearlyInterpolateContour(
167
167
  const handleCount = Math.round(
168
168
  Math.max(
169
169
  8,
170
- interpolationData.get(startIndex)[0].data.handles.points.length * 2,
171
- interpolationData.get(endIndex)[0].data.handles.points.length * 2
170
+ interpolationData.get(startIndex)[0].data.handles.points.length,
171
+ interpolationData.get(endIndex)[0].data.handles.points.length,
172
+ interpolated3DPoints.x.length / 50
172
173
  )
173
174
  );
174
175
  const handlePoints = _subselect(interpolated3DPoints, handleCount);
@@ -1,24 +1,18 @@
1
1
  import { Types } from '@cornerstonejs/core';
2
2
 
3
- /**
4
- * getSumReducer - A reducer function that calculates the sum of an array.
5
- *
6
- * @param total - The running total.
7
- * @param num - The numerical value of the array element.
8
- * @returns The updated running total.
9
- */
10
- function getSumReducer(total: number, num: number): number {
11
- return total + num;
12
- }
13
-
14
3
  /**
15
4
  * _reverseIfAntiClockwise - If the contour's nodes run anti-clockwise,
16
5
  * reverse them.
17
6
  *
18
7
  * @param points - The points array.
8
+ * @param otherListsToReverse - any number of additional lists to also reverse
9
+ * when the primary list is anti-clockwise.
19
10
  * @returns The contour, corrected to be clockwise if appropriate.
20
11
  */
21
- export default function reverseIfAntiClockwise(points: Types.Point2[]) {
12
+ export default function reverseIfAntiClockwise(
13
+ points: Types.Point2[],
14
+ ...otherListsToReverse: unknown[][]
15
+ ) {
22
16
  const length = points.length;
23
17
  if (!length) {
24
18
  return points;
@@ -43,7 +37,11 @@ export default function reverseIfAntiClockwise(points: Types.Point2[]) {
43
37
  }
44
38
  }
45
39
 
46
- if (checkSum > 0) {
40
+ // Checksum will be less than zero for anti-clockwise
41
+ if (checkSum < 0) {
42
+ if (otherListsToReverse) {
43
+ otherListsToReverse.forEach((list) => list.reverse());
44
+ }
47
45
  return points.slice().reverse();
48
46
  }
49
47
  return points;
@@ -101,6 +101,18 @@ export class LivewirePath {
101
101
  }
102
102
  }
103
103
 
104
+ public getLastControlPoint(): Types.Point2 {
105
+ if (this._controlPointIndexes.length) {
106
+ return this.pointArray[
107
+ this._controlPointIndexes[this._controlPointIndexes.length - 1]
108
+ ];
109
+ }
110
+ }
111
+
112
+ public removeLastPoints(count: number) {
113
+ this.pointArray.splice(this.pointArray.length - count, count);
114
+ }
115
+
104
116
  /**
105
117
  * Add points to the path.
106
118
  *
@@ -128,4 +140,16 @@ export class LivewirePath {
128
140
  this._controlPointIndexes =
129
141
  other._controlPointIndexes.concat(shiftedIndexArray);
130
142
  }
143
+
144
+ /**
145
+ * Append a path to this one.
146
+ *
147
+ * @param other - The path to append.
148
+ */
149
+ public appendPath(other: LivewirePath): void {
150
+ this.addPoints(other.pointArray);
151
+ other._controlPointIndexes.forEach((point) =>
152
+ this._controlPointIndexes.push(point)
153
+ );
154
+ }
131
155
  }