@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.
- package/dist/cjs/tools/annotation/LivewireContourTool.d.ts +5 -0
- package/dist/cjs/tools/annotation/LivewireContourTool.js +133 -44
- package/dist/cjs/tools/annotation/LivewireContourTool.js.map +1 -1
- package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +1 -1
- package/dist/cjs/utilities/contours/findHandlePolylineIndex.d.ts +2 -0
- package/dist/cjs/utilities/contours/findHandlePolylineIndex.js +35 -0
- package/dist/cjs/utilities/contours/findHandlePolylineIndex.js.map +1 -0
- package/dist/cjs/utilities/contours/index.d.ts +2 -1
- package/dist/cjs/utilities/contours/index.js +3 -1
- package/dist/cjs/utilities/contours/index.js.map +1 -1
- package/dist/cjs/utilities/contours/interpolation/interpolate.js +1 -1
- package/dist/cjs/utilities/contours/interpolation/interpolate.js.map +1 -1
- package/dist/cjs/utilities/contours/reverseIfAntiClockwise.d.ts +1 -1
- package/dist/cjs/utilities/contours/reverseIfAntiClockwise.js +5 -5
- package/dist/cjs/utilities/contours/reverseIfAntiClockwise.js.map +1 -1
- package/dist/cjs/utilities/livewire/LiveWirePath.d.ts +3 -0
- package/dist/cjs/utilities/livewire/LiveWirePath.js +12 -0
- package/dist/cjs/utilities/livewire/LiveWirePath.js.map +1 -1
- package/dist/cjs/utilities/livewire/LivewireScissors.d.ts +2 -1
- package/dist/cjs/utilities/livewire/LivewireScissors.js +26 -7
- package/dist/cjs/utilities/livewire/LivewireScissors.js.map +1 -1
- package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js +1 -0
- package/dist/cjs/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
- package/dist/esm/tools/annotation/LivewireContourTool.js +131 -44
- package/dist/esm/tools/annotation/LivewireContourTool.js.map +1 -1
- package/dist/esm/utilities/contours/findHandlePolylineIndex.js +32 -0
- package/dist/esm/utilities/contours/findHandlePolylineIndex.js.map +1 -0
- package/dist/esm/utilities/contours/index.js +2 -1
- package/dist/esm/utilities/contours/index.js.map +1 -1
- package/dist/esm/utilities/contours/interpolation/interpolate.js +1 -1
- package/dist/esm/utilities/contours/interpolation/interpolate.js.map +1 -1
- package/dist/esm/utilities/contours/reverseIfAntiClockwise.js +5 -5
- package/dist/esm/utilities/contours/reverseIfAntiClockwise.js.map +1 -1
- package/dist/esm/utilities/livewire/LiveWirePath.js +12 -0
- package/dist/esm/utilities/livewire/LiveWirePath.js.map +1 -1
- package/dist/esm/utilities/livewire/LivewireScissors.js +26 -7
- package/dist/esm/utilities/livewire/LivewireScissors.js.map +1 -1
- package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js +1 -0
- package/dist/esm/utilities/segmentation/InterpolationManager/InterpolationManager.js.map +1 -1
- package/dist/types/tools/annotation/LivewireContourTool.d.ts +5 -0
- package/dist/types/tools/annotation/LivewireContourTool.d.ts.map +1 -1
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +1 -1
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
- package/dist/types/utilities/contours/findHandlePolylineIndex.d.ts +3 -0
- package/dist/types/utilities/contours/findHandlePolylineIndex.d.ts.map +1 -0
- package/dist/types/utilities/contours/index.d.ts +2 -1
- package/dist/types/utilities/contours/index.d.ts.map +1 -1
- package/dist/types/utilities/contours/interpolation/interpolate.d.ts.map +1 -1
- package/dist/types/utilities/contours/reverseIfAntiClockwise.d.ts +1 -1
- package/dist/types/utilities/contours/reverseIfAntiClockwise.d.ts.map +1 -1
- package/dist/types/utilities/livewire/LiveWirePath.d.ts +3 -0
- package/dist/types/utilities/livewire/LiveWirePath.d.ts.map +1 -1
- package/dist/types/utilities/livewire/LivewireScissors.d.ts +2 -1
- package/dist/types/utilities/livewire/LivewireScissors.d.ts.map +1 -1
- package/dist/types/utilities/segmentation/InterpolationManager/InterpolationManager.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/tools/annotation/EllipticalROITool.ts +1 -1
- package/src/tools/annotation/LivewireContourTool.ts +220 -71
- package/src/types/ToolSpecificAnnotationTypes.ts +2 -0
- package/src/utilities/contours/findHandlePolylineIndex.ts +52 -0
- package/src/utilities/contours/index.ts +2 -0
- package/src/utilities/contours/interpolation/interpolate.ts +3 -2
- package/src/utilities/contours/reverseIfAntiClockwise.ts +11 -13
- package/src/utilities/livewire/LiveWirePath.ts +24 -0
- package/src/utilities/livewire/LivewireScissors.ts +52 -8
- 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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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
|
|
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
|
|
185
|
+
newAnnotation,
|
|
182
186
|
hasMoved: false,
|
|
183
|
-
lastCanvasPoint
|
|
184
|
-
confirmedPath
|
|
185
|
-
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(
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
this.editData.currentPath.
|
|
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
|
|
472
|
-
|
|
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
|
|
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
|
-
|
|
657
|
-
|
|
803
|
+
highlighted ||
|
|
804
|
+
(newAnnotation &&
|
|
805
|
+
annotation.annotationUID === this.editData?.annotation?.annotationUID)
|
|
658
806
|
) {
|
|
659
807
|
const handleGroupUID = '0';
|
|
660
|
-
const
|
|
808
|
+
const canvasHandles = handles.points.map(worldToCanvas);
|
|
661
809
|
|
|
662
810
|
drawHandlesSvg(
|
|
663
811
|
svgDrawingHelper,
|
|
664
812
|
annotationUID,
|
|
665
813
|
handleGroupUID,
|
|
666
|
-
|
|
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];
|
|
@@ -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
|
|
171
|
-
interpolationData.get(endIndex)[0].data.handles.points.length
|
|
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(
|
|
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
|
-
|
|
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
|
}
|