@cornerstonejs/tools 3.19.1 → 3.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.d.ts +0 -3
- package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js +17 -178
- package/dist/esm/tools/segmentation/LabelmapEditWithContour.js +3 -0
- package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.d.ts +2 -0
- package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.js +40 -0
- package/dist/esm/utilities/contourSegmentation/contourSegmentationOperation.d.ts +3 -0
- package/dist/esm/utilities/contourSegmentation/contourSegmentationOperation.js +41 -0
- package/dist/esm/utilities/contourSegmentation/getIntersectingAnnotations.d.ts +8 -0
- package/dist/esm/utilities/contourSegmentation/getIntersectingAnnotations.js +34 -0
- package/dist/esm/utilities/contourSegmentation/index.d.ts +8 -1
- package/dist/esm/utilities/contourSegmentation/index.js +8 -1
- package/dist/esm/utilities/contourSegmentation/logicalOperators.d.ts +12 -0
- package/dist/esm/utilities/contourSegmentation/logicalOperators.js +75 -0
- package/dist/esm/utilities/contourSegmentation/mergeMultipleAnnotations.d.ts +8 -0
- package/dist/esm/utilities/contourSegmentation/mergeMultipleAnnotations.js +175 -0
- package/dist/esm/utilities/contourSegmentation/sharedOperations.d.ts +16 -0
- package/dist/esm/utilities/contourSegmentation/sharedOperations.js +183 -0
- package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.d.ts +31 -0
- package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.js +110 -0
- package/dist/esm/utilities/contours/interpolation/interpolate.js +1 -1
- package/dist/esm/utilities/math/polyline/combinePolyline.js +31 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import type { Types } from '@cornerstonejs/core';
|
|
2
|
-
import type { ContourSegmentationAnnotation } from '../../../types/ContourSegmentationAnnotation';
|
|
3
1
|
import type { AnnotationCompletedEventType } from '../../../types/EventTypes';
|
|
4
2
|
export default function contourSegmentationCompletedListener(evt: AnnotationCompletedEventType): Promise<void>;
|
|
5
|
-
export declare function createPolylineHole(viewport: Types.IViewport, targetAnnotation: ContourSegmentationAnnotation, holeAnnotation: ContourSegmentationAnnotation): void;
|
package/dist/esm/eventListeners/annotations/contourSegmentation/contourSegmentationCompleted.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import { getEnabledElement, utilities as csUtils } from '@cornerstonejs/core';
|
|
2
1
|
import getViewportsForAnnotation from '../../../utilities/getViewportsForAnnotation';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { getViewportIdsWithToolToRender } from '../../../utilities/viewportFilters';
|
|
6
|
-
import { addAnnotation, removeAnnotation, getAllAnnotations, getChildAnnotations, addChildAnnotation, clearParentAnnotation, } from '../../../stateManagement/annotation/annotationState';
|
|
7
|
-
import { ContourWindingDirection } from '../../../types/ContourAnnotation';
|
|
8
|
-
import { triggerAnnotationModified } from '../../../stateManagement/annotation/helpers/state';
|
|
9
|
-
import updateContourPolyline from '../../../utilities/contours/updateContourPolyline';
|
|
10
|
-
import { addContourSegmentationAnnotation, areSameSegment, isContourSegmentationAnnotation, removeContourSegmentationAnnotation, } from '../../../utilities/contourSegmentation';
|
|
2
|
+
import { getAllAnnotations } from '../../../stateManagement/annotation/annotationState';
|
|
3
|
+
import { areSameSegment, isContourSegmentationAnnotation, } from '../../../utilities/contourSegmentation';
|
|
11
4
|
import { getToolGroupForViewport } from '../../../store/ToolGroupManager';
|
|
12
|
-
import {
|
|
5
|
+
import { findAllIntersectingContours } from '../../../utilities/contourSegmentation/getIntersectingAnnotations';
|
|
6
|
+
import { processMultipleIntersections } from '../../../utilities/contourSegmentation/mergeMultipleAnnotations';
|
|
7
|
+
import { convertContourPolylineToCanvasSpace, createPolylineHole, combinePolylines, } from '../../../utilities/contourSegmentation/sharedOperations';
|
|
13
8
|
const DEFAULT_CONTOUR_SEG_TOOL_NAME = 'PlanarFreehandContourSegmentationTool';
|
|
14
9
|
export default async function contourSegmentationCompletedListener(evt) {
|
|
15
10
|
const sourceAnnotation = evt.detail
|
|
@@ -23,11 +18,15 @@ export default async function contourSegmentationCompletedListener(evt) {
|
|
|
23
18
|
return;
|
|
24
19
|
}
|
|
25
20
|
const sourcePolyline = convertContourPolylineToCanvasSpace(sourceAnnotation.data.contour.polyline, viewport);
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
21
|
+
const intersectingContours = findAllIntersectingContours(viewport, sourcePolyline, contourSegmentationAnnotations);
|
|
22
|
+
if (!intersectingContours.length) {
|
|
28
23
|
return;
|
|
29
24
|
}
|
|
30
|
-
|
|
25
|
+
if (intersectingContours.length > 1) {
|
|
26
|
+
processMultipleIntersections(viewport, sourceAnnotation, sourcePolyline, intersectingContours);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const { targetAnnotation, targetPolyline, isContourHole } = intersectingContours[0];
|
|
31
30
|
if (isContourHole) {
|
|
32
31
|
const { contourHoleProcessingEnabled = false } = evt.detail;
|
|
33
32
|
if (!contourHoleProcessingEnabled) {
|
|
@@ -43,11 +42,14 @@ function isFreehandContourSegToolRegisteredForViewport(viewport, silent = false)
|
|
|
43
42
|
const toolName = 'PlanarFreehandContourSegmentationTool';
|
|
44
43
|
const toolGroup = getToolGroupForViewport(viewport.id, viewport.renderingEngineId);
|
|
45
44
|
let errorMessage;
|
|
46
|
-
if (!toolGroup
|
|
45
|
+
if (!toolGroup) {
|
|
46
|
+
errorMessage = `ToolGroup not found for viewport ${viewport.id}`;
|
|
47
|
+
}
|
|
48
|
+
else if (!toolGroup.hasTool(toolName)) {
|
|
47
49
|
errorMessage = `Tool ${toolName} not added to ${toolGroup.id} toolGroup`;
|
|
48
50
|
}
|
|
49
51
|
else if (!toolGroup.getToolOptions(toolName)) {
|
|
50
|
-
errorMessage = `Tool ${toolName} must be in active/passive state`;
|
|
52
|
+
errorMessage = `Tool ${toolName} must be in active/passive state in ${toolGroup.id} toolGroup`;
|
|
51
53
|
}
|
|
52
54
|
if (errorMessage && !silent) {
|
|
53
55
|
console.warn(errorMessage);
|
|
@@ -59,14 +61,6 @@ function getViewport(annotation) {
|
|
|
59
61
|
const viewportWithToolRegistered = viewports.find((viewport) => isFreehandContourSegToolRegisteredForViewport(viewport, true));
|
|
60
62
|
return viewportWithToolRegistered ?? viewports[0];
|
|
61
63
|
}
|
|
62
|
-
function convertContourPolylineToCanvasSpace(polyline, viewport) {
|
|
63
|
-
const numPoints = polyline.length;
|
|
64
|
-
const projectedPolyline = new Array(numPoints);
|
|
65
|
-
for (let i = 0; i < numPoints; i++) {
|
|
66
|
-
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
67
|
-
}
|
|
68
|
-
return projectedPolyline;
|
|
69
|
-
}
|
|
70
64
|
function getValidContourSegmentationAnnotations(viewport, sourceAnnotation) {
|
|
71
65
|
const { annotationUID: sourceAnnotationUID } = sourceAnnotation;
|
|
72
66
|
const allAnnotations = getAllAnnotations();
|
|
@@ -76,158 +70,3 @@ function getValidContourSegmentationAnnotations(viewport, sourceAnnotation) {
|
|
|
76
70
|
areSameSegment(targetAnnotation, sourceAnnotation) &&
|
|
77
71
|
viewport.isReferenceViewable(targetAnnotation.metadata));
|
|
78
72
|
}
|
|
79
|
-
function findIntersectingContour(viewport, sourcePolyline, contourSegmentationAnnotations) {
|
|
80
|
-
const sourceAABB = math.polyline.getAABB(sourcePolyline);
|
|
81
|
-
for (let i = 0; i < contourSegmentationAnnotations.length; i++) {
|
|
82
|
-
const targetAnnotation = contourSegmentationAnnotations[i];
|
|
83
|
-
const targetPolyline = convertContourPolylineToCanvasSpace(targetAnnotation.data.contour.polyline, viewport);
|
|
84
|
-
const targetAABB = math.polyline.getAABB(targetPolyline);
|
|
85
|
-
const aabbIntersect = math.aabb.intersectAABB(sourceAABB, targetAABB);
|
|
86
|
-
const lineSegmentsIntersect = aabbIntersect &&
|
|
87
|
-
math.polyline.intersectPolyline(sourcePolyline, targetPolyline);
|
|
88
|
-
const isContourHole = aabbIntersect &&
|
|
89
|
-
!lineSegmentsIntersect &&
|
|
90
|
-
math.polyline.containsPoints(targetPolyline, sourcePolyline);
|
|
91
|
-
if (lineSegmentsIntersect || isContourHole) {
|
|
92
|
-
return { targetAnnotation, targetPolyline, isContourHole };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
export function createPolylineHole(viewport, targetAnnotation, holeAnnotation) {
|
|
97
|
-
const { windingDirection: targetWindingDirection } = targetAnnotation.data.contour;
|
|
98
|
-
const { windingDirection: holeWindingDirection } = holeAnnotation.data.contour;
|
|
99
|
-
addChildAnnotation(targetAnnotation, holeAnnotation);
|
|
100
|
-
removeContourSegmentationAnnotation(holeAnnotation);
|
|
101
|
-
const { contour: holeContour } = holeAnnotation.data;
|
|
102
|
-
const holePolyline = convertContourPolylineToCanvasSpace(holeContour.polyline, viewport);
|
|
103
|
-
updateContourPolyline(holeAnnotation, {
|
|
104
|
-
points: holePolyline,
|
|
105
|
-
closed: holeContour.closed,
|
|
106
|
-
}, viewport);
|
|
107
|
-
const { element } = viewport;
|
|
108
|
-
const updatedToolNames = new Set([
|
|
109
|
-
DEFAULT_CONTOUR_SEG_TOOL_NAME,
|
|
110
|
-
targetAnnotation.metadata.toolName,
|
|
111
|
-
holeAnnotation.metadata.toolName,
|
|
112
|
-
]);
|
|
113
|
-
for (const toolName of updatedToolNames.values()) {
|
|
114
|
-
const viewportIdsToRender = getViewportIdsWithToolToRender(element, toolName);
|
|
115
|
-
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function getContourHolesData(viewport, annotation) {
|
|
119
|
-
return getChildAnnotations(annotation).map((holeAnnotation) => {
|
|
120
|
-
const polyline = convertContourPolylineToCanvasSpace(holeAnnotation.data.contour.polyline, viewport);
|
|
121
|
-
return { annotation: holeAnnotation, polyline };
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function combinePolylines(viewport, targetAnnotation, targetPolyline, sourceAnnotation, sourcePolyline) {
|
|
125
|
-
if (!hasToolByName(DEFAULT_CONTOUR_SEG_TOOL_NAME)) {
|
|
126
|
-
console.warn(`${DEFAULT_CONTOUR_SEG_TOOL_NAME} is not registered in cornerstone`);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (!isFreehandContourSegToolRegisteredForViewport(viewport)) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const sourceStartPoint = sourcePolyline[0];
|
|
133
|
-
const mergePolylines = math.polyline.containsPoint(targetPolyline, sourceStartPoint);
|
|
134
|
-
const contourHolesData = getContourHolesData(viewport, targetAnnotation);
|
|
135
|
-
const unassignedContourHolesSet = new Set(contourHolesData);
|
|
136
|
-
const reassignedContourHolesMap = new Map();
|
|
137
|
-
const assignHoleToPolyline = (parentPolyline, holeData) => {
|
|
138
|
-
let holes = reassignedContourHolesMap.get(parentPolyline);
|
|
139
|
-
if (!holes) {
|
|
140
|
-
holes = [];
|
|
141
|
-
reassignedContourHolesMap.set(parentPolyline, holes);
|
|
142
|
-
}
|
|
143
|
-
holes.push(holeData);
|
|
144
|
-
unassignedContourHolesSet.delete(holeData);
|
|
145
|
-
};
|
|
146
|
-
const newPolylines = [];
|
|
147
|
-
if (mergePolylines) {
|
|
148
|
-
const mergedPolyline = math.polyline.mergePolylines(targetPolyline, sourcePolyline);
|
|
149
|
-
newPolylines.push(mergedPolyline);
|
|
150
|
-
Array.from(unassignedContourHolesSet.keys()).forEach((holeData) => assignHoleToPolyline(mergedPolyline, holeData));
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
const subtractedPolylines = math.polyline.subtractPolylines(targetPolyline, sourcePolyline);
|
|
154
|
-
subtractedPolylines.forEach((newPolyline) => {
|
|
155
|
-
newPolylines.push(newPolyline);
|
|
156
|
-
Array.from(unassignedContourHolesSet.keys()).forEach((holeData) => {
|
|
157
|
-
const containsHole = math.polyline.containsPoints(newPolyline, holeData.polyline);
|
|
158
|
-
if (containsHole) {
|
|
159
|
-
assignHoleToPolyline(newPolyline, holeData);
|
|
160
|
-
unassignedContourHolesSet.delete(holeData);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
Array.from(reassignedContourHolesMap.values()).forEach((contourHolesDataArray) => contourHolesDataArray.forEach((contourHoleData) => clearParentAnnotation(contourHoleData.annotation)));
|
|
166
|
-
const { element } = viewport;
|
|
167
|
-
const enabledElement = getEnabledElement(element);
|
|
168
|
-
const { metadata, data } = targetAnnotation;
|
|
169
|
-
const { handles, segmentation } = data;
|
|
170
|
-
const { textBox } = handles;
|
|
171
|
-
removeAnnotation(sourceAnnotation.annotationUID);
|
|
172
|
-
removeAnnotation(targetAnnotation.annotationUID);
|
|
173
|
-
for (let i = 0; i < newPolylines.length; i++) {
|
|
174
|
-
const polyline = newPolylines[i];
|
|
175
|
-
const startPoint = viewport.canvasToWorld(polyline[0]);
|
|
176
|
-
const endPoint = viewport.canvasToWorld(polyline[polyline.length - 1]);
|
|
177
|
-
const newAnnotation = {
|
|
178
|
-
metadata: {
|
|
179
|
-
...metadata,
|
|
180
|
-
toolName: DEFAULT_CONTOUR_SEG_TOOL_NAME,
|
|
181
|
-
originalToolName: metadata.originalToolName || metadata.toolName,
|
|
182
|
-
},
|
|
183
|
-
data: {
|
|
184
|
-
cachedStats: {},
|
|
185
|
-
handles: {
|
|
186
|
-
points: [startPoint, endPoint],
|
|
187
|
-
textBox: textBox ? { ...textBox } : undefined,
|
|
188
|
-
},
|
|
189
|
-
contour: {
|
|
190
|
-
polyline: [],
|
|
191
|
-
closed: true,
|
|
192
|
-
},
|
|
193
|
-
spline: targetAnnotation.data.spline,
|
|
194
|
-
segmentation: {
|
|
195
|
-
...segmentation,
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
annotationUID: csUtils.uuidv4(),
|
|
199
|
-
highlighted: true,
|
|
200
|
-
invalidated: true,
|
|
201
|
-
isLocked: false,
|
|
202
|
-
isVisible: undefined,
|
|
203
|
-
interpolationUID: targetAnnotation.interpolationUID,
|
|
204
|
-
interpolationCompleted: targetAnnotation.interpolationCompleted,
|
|
205
|
-
};
|
|
206
|
-
updateContourPolyline(newAnnotation, {
|
|
207
|
-
points: polyline,
|
|
208
|
-
closed: true,
|
|
209
|
-
targetWindingDirection: ContourWindingDirection.Clockwise,
|
|
210
|
-
}, viewport);
|
|
211
|
-
addAnnotation(newAnnotation, element);
|
|
212
|
-
addContourSegmentationAnnotation(newAnnotation);
|
|
213
|
-
triggerAnnotationModified(newAnnotation, viewport.element);
|
|
214
|
-
reassignedContourHolesMap
|
|
215
|
-
.get(polyline)
|
|
216
|
-
?.forEach((holeData) => addChildAnnotation(newAnnotation, holeData.annotation));
|
|
217
|
-
}
|
|
218
|
-
updateViewports(enabledElement, targetAnnotation, sourceAnnotation);
|
|
219
|
-
}
|
|
220
|
-
function updateViewports(enabledElement, targetAnnotation, sourceAnnotation) {
|
|
221
|
-
const { viewport } = enabledElement;
|
|
222
|
-
const { element } = viewport;
|
|
223
|
-
const updatedTtoolNames = new Set([
|
|
224
|
-
DEFAULT_CONTOUR_SEG_TOOL_NAME,
|
|
225
|
-
targetAnnotation.metadata.toolName,
|
|
226
|
-
sourceAnnotation.metadata.toolName,
|
|
227
|
-
]);
|
|
228
|
-
for (const toolName of updatedTtoolNames.values()) {
|
|
229
|
-
const viewportIdsToRender = getViewportIdsWithToolToRender(element, toolName);
|
|
230
|
-
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
231
|
-
}
|
|
232
|
-
return new Promise((resolve) => window.requestAnimationFrame(resolve));
|
|
233
|
-
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { utilities } from '@cornerstonejs/core';
|
|
2
|
+
import { addAnnotation } from '../../stateManagement';
|
|
3
|
+
const DEFAULT_CONTOUR_SEG_TOOLNAME = 'PlanarFreehandContourSegmentationTool';
|
|
4
|
+
export default function addPolylinesToSegmentation(viewport, segmentationId, polylines, segmentIndex) {
|
|
5
|
+
const annotationUIDsMap = new Map();
|
|
6
|
+
polylines.forEach((polyline) => {
|
|
7
|
+
if (polyline.length < 3) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const contourSegmentationAnnotation = {
|
|
11
|
+
annotationUID: utilities.uuidv4(),
|
|
12
|
+
data: {
|
|
13
|
+
contour: {
|
|
14
|
+
closed: true,
|
|
15
|
+
polyline,
|
|
16
|
+
},
|
|
17
|
+
segmentation: {
|
|
18
|
+
segmentationId,
|
|
19
|
+
segmentIndex,
|
|
20
|
+
},
|
|
21
|
+
handles: {},
|
|
22
|
+
},
|
|
23
|
+
handles: {},
|
|
24
|
+
highlighted: false,
|
|
25
|
+
autoGenerated: false,
|
|
26
|
+
invalidated: false,
|
|
27
|
+
isLocked: false,
|
|
28
|
+
isVisible: true,
|
|
29
|
+
metadata: {
|
|
30
|
+
toolName: DEFAULT_CONTOUR_SEG_TOOLNAME,
|
|
31
|
+
...viewport.getViewReference(),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
addAnnotation(contourSegmentationAnnotation, viewport.element);
|
|
35
|
+
const currentSet = annotationUIDsMap?.get(segmentIndex) || new Set();
|
|
36
|
+
currentSet.add(contourSegmentationAnnotation.annotationUID);
|
|
37
|
+
annotationUIDsMap.set(segmentIndex, currentSet);
|
|
38
|
+
});
|
|
39
|
+
return annotationUIDsMap;
|
|
40
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { ContourSegmentationAnnotation } from '../../types/ContourSegmentationAnnotation';
|
|
3
|
+
export declare function contourSegmentationOperation(sourceAnnotationOrUID: ContourSegmentationAnnotation | string, targetAnnotationOrUID: ContourSegmentationAnnotation | string, viewport?: Types.IViewport, contourHoleProcessingEnabled?: boolean): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getAnnotation } from '../../stateManagement/annotation/annotationState';
|
|
2
|
+
import getViewportsForAnnotation from '../getViewportsForAnnotation';
|
|
3
|
+
import { convertContourPolylineToCanvasSpace, checkIntersection, createPolylineHole, combinePolylines, } from './sharedOperations';
|
|
4
|
+
export async function contourSegmentationOperation(sourceAnnotationOrUID, targetAnnotationOrUID, viewport, contourHoleProcessingEnabled = true) {
|
|
5
|
+
const sourceAnnotation = typeof sourceAnnotationOrUID === 'string'
|
|
6
|
+
? getAnnotation(sourceAnnotationOrUID)
|
|
7
|
+
: sourceAnnotationOrUID;
|
|
8
|
+
const targetAnnotation = typeof targetAnnotationOrUID === 'string'
|
|
9
|
+
? getAnnotation(targetAnnotationOrUID)
|
|
10
|
+
: targetAnnotationOrUID;
|
|
11
|
+
if (!sourceAnnotation || !targetAnnotation) {
|
|
12
|
+
throw new Error('Both source and target annotations must be valid');
|
|
13
|
+
}
|
|
14
|
+
if (!viewport) {
|
|
15
|
+
viewport = getViewportFromAnnotation(sourceAnnotation);
|
|
16
|
+
}
|
|
17
|
+
const sourcePolyline = convertContourPolylineToCanvasSpace(sourceAnnotation.data.contour.polyline, viewport);
|
|
18
|
+
const targetPolyline = convertContourPolylineToCanvasSpace(targetAnnotation.data.contour.polyline, viewport);
|
|
19
|
+
const intersectionInfo = checkIntersection(sourcePolyline, targetPolyline);
|
|
20
|
+
if (!intersectionInfo.hasIntersection) {
|
|
21
|
+
console.warn('No intersection found between the two annotations');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (intersectionInfo.isContourHole) {
|
|
25
|
+
if (!contourHoleProcessingEnabled) {
|
|
26
|
+
console.warn('Hole processing is disabled');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
createPolylineHole(viewport, targetAnnotation, sourceAnnotation);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
combinePolylines(viewport, targetAnnotation, targetPolyline, sourceAnnotation, sourcePolyline);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function getViewportFromAnnotation(annotation) {
|
|
36
|
+
const viewports = getViewportsForAnnotation(annotation);
|
|
37
|
+
if (!viewports.length) {
|
|
38
|
+
throw new Error('No viewport found for the annotation');
|
|
39
|
+
}
|
|
40
|
+
return viewports[0];
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { ContourSegmentationAnnotation } from '../../types/ContourSegmentationAnnotation';
|
|
3
|
+
declare function findAllIntersectingContours(viewport: Types.IViewport, sourcePolyline: Types.Point2[], contourSegmentationAnnotations: ContourSegmentationAnnotation[]): Array<{
|
|
4
|
+
targetAnnotation: ContourSegmentationAnnotation;
|
|
5
|
+
targetPolyline: Types.Point2[];
|
|
6
|
+
isContourHole: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export { findAllIntersectingContours };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as math from '../math';
|
|
2
|
+
function findAllIntersectingContours(viewport, sourcePolyline, contourSegmentationAnnotations) {
|
|
3
|
+
const intersectingContours = [];
|
|
4
|
+
const sourceAABB = math.polyline.getAABB(sourcePolyline);
|
|
5
|
+
for (let i = 0; i < contourSegmentationAnnotations.length; i++) {
|
|
6
|
+
const targetAnnotation = contourSegmentationAnnotations[i];
|
|
7
|
+
const targetPolyline = convertContourPolylineToCanvasSpace(targetAnnotation.data.contour.polyline, viewport);
|
|
8
|
+
const targetAABB = math.polyline.getAABB(targetPolyline);
|
|
9
|
+
const aabbIntersect = math.aabb.intersectAABB(sourceAABB, targetAABB);
|
|
10
|
+
if (!aabbIntersect) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const lineSegmentsIntersect = math.polyline.intersectPolyline(sourcePolyline, targetPolyline);
|
|
14
|
+
const isContourHole = !lineSegmentsIntersect &&
|
|
15
|
+
math.polyline.containsPoints(targetPolyline, sourcePolyline);
|
|
16
|
+
if (lineSegmentsIntersect || isContourHole) {
|
|
17
|
+
intersectingContours.push({
|
|
18
|
+
targetAnnotation,
|
|
19
|
+
targetPolyline,
|
|
20
|
+
isContourHole,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return intersectingContours;
|
|
25
|
+
}
|
|
26
|
+
function convertContourPolylineToCanvasSpace(polyline, viewport) {
|
|
27
|
+
const numPoints = polyline.length;
|
|
28
|
+
const projectedPolyline = new Array(numPoints);
|
|
29
|
+
for (let i = 0; i < numPoints; i++) {
|
|
30
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
31
|
+
}
|
|
32
|
+
return projectedPolyline;
|
|
33
|
+
}
|
|
34
|
+
export { findAllIntersectingContours };
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import areSameSegment from './areSameSegment';
|
|
2
|
+
import { addition, subtraction } from './logicalOperators';
|
|
2
3
|
export { default as isContourSegmentationAnnotation } from './isContourSegmentationAnnotation';
|
|
3
4
|
export { addContourSegmentationAnnotation } from './addContourSegmentationAnnotation';
|
|
4
5
|
export { removeContourSegmentationAnnotation } from './removeContourSegmentationAnnotation';
|
|
6
|
+
export { findAllIntersectingContours } from './getIntersectingAnnotations';
|
|
7
|
+
export { processMultipleIntersections } from './mergeMultipleAnnotations';
|
|
8
|
+
export { contourSegmentationOperation } from './contourSegmentationOperation';
|
|
9
|
+
export * from './sharedOperations';
|
|
10
|
+
export { addition, subtraction, areSameSegment };
|
|
11
|
+
export { unifyPolylineSets, unifyMultiplePolylineSets, unifyAnnotationPolylines, subtractPolylineSets, subtractMultiplePolylineSets, subtractAnnotationPolylines, } from './unifyPolylineSets';
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import areSameSegment from './areSameSegment';
|
|
2
|
+
import { addition, subtraction } from './logicalOperators';
|
|
2
3
|
export { default as isContourSegmentationAnnotation } from './isContourSegmentationAnnotation';
|
|
3
4
|
export { addContourSegmentationAnnotation } from './addContourSegmentationAnnotation';
|
|
4
5
|
export { removeContourSegmentationAnnotation } from './removeContourSegmentationAnnotation';
|
|
6
|
+
export { findAllIntersectingContours } from './getIntersectingAnnotations';
|
|
7
|
+
export { processMultipleIntersections } from './mergeMultipleAnnotations';
|
|
8
|
+
export { contourSegmentationOperation } from './contourSegmentationOperation';
|
|
9
|
+
export * from './sharedOperations';
|
|
10
|
+
export { addition, subtraction, areSameSegment };
|
|
11
|
+
export { unifyPolylineSets, unifyMultiplePolylineSets, unifyAnnotationPolylines, subtractPolylineSets, subtractMultiplePolylineSets, subtractAnnotationPolylines, } from './unifyPolylineSets';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { Segmentation } from '../../types';
|
|
3
|
+
export declare function addition(viewport: Types.IViewport, segmentation: Segmentation, segmentIndex1: number, segmentIndex2: number, { name, segmentIndex, color }: {
|
|
4
|
+
name: any;
|
|
5
|
+
segmentIndex: any;
|
|
6
|
+
color: any;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function subtraction(viewport: Types.IViewport, segmentation: Segmentation, segmentIndex1: number, segmentIndex2: number, { name, segmentIndex, color }: {
|
|
9
|
+
name: any;
|
|
10
|
+
segmentIndex: any;
|
|
11
|
+
color: any;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getAnnotation } from '../../stateManagement';
|
|
2
|
+
import { convertContourPolylineToCanvasSpace, convertContourPolylineToWorld, } from './sharedOperations';
|
|
3
|
+
import { subtractPolylineSets, unifyPolylineSets } from './unifyPolylineSets';
|
|
4
|
+
import addPolylinesToSegmentation from './addPolylinesToSegmentation';
|
|
5
|
+
function getPolylines(contourRepresentationData, segmentIndex) {
|
|
6
|
+
const polylines = [];
|
|
7
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
8
|
+
const annotationUIDs = annotationUIDsMap.get(segmentIndex);
|
|
9
|
+
for (const annotationUID of annotationUIDs) {
|
|
10
|
+
const annotation = getAnnotation(annotationUID);
|
|
11
|
+
const { polyline } = annotation.data
|
|
12
|
+
.contour;
|
|
13
|
+
polylines.push(polyline);
|
|
14
|
+
}
|
|
15
|
+
return polylines;
|
|
16
|
+
}
|
|
17
|
+
function extractPolylines(viewport, segmentation, segmentIndex1, segmentIndex2) {
|
|
18
|
+
if (!segmentation) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!segmentation.representationData.Contour) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const contourRepresentationData = segmentation.representationData
|
|
25
|
+
.Contour;
|
|
26
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
27
|
+
if (!annotationUIDsMap) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!annotationUIDsMap.get(segmentIndex1)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!annotationUIDsMap.get(segmentIndex2)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const polyLines1 = getPolylines(contourRepresentationData, segmentIndex1);
|
|
37
|
+
const polyLines2 = getPolylines(contourRepresentationData, segmentIndex2);
|
|
38
|
+
const polyLinesCanvas1 = polyLines1.map((polyline) => convertContourPolylineToCanvasSpace(polyline, viewport));
|
|
39
|
+
const polyLinesCanvas2 = polyLines2.map((polyline) => convertContourPolylineToCanvasSpace(polyline, viewport));
|
|
40
|
+
return { polyLinesCanvas1, polyLinesCanvas2 };
|
|
41
|
+
}
|
|
42
|
+
export function addition(viewport, segmentation, segmentIndex1, segmentIndex2, { name, segmentIndex, color }) {
|
|
43
|
+
const { polyLinesCanvas1, polyLinesCanvas2 } = extractPolylines(viewport, segmentation, segmentIndex1, segmentIndex2) ||
|
|
44
|
+
{};
|
|
45
|
+
if (!polyLinesCanvas1 || polyLinesCanvas2) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const polylinesMerged = unifyPolylineSets(polyLinesCanvas1, polyLinesCanvas2);
|
|
49
|
+
const polyLinesWorld = polylinesMerged.map((polyline) => convertContourPolylineToWorld(polyline, viewport));
|
|
50
|
+
const annotationUIDsMapNew = addPolylinesToSegmentation(viewport, segmentation.segmentationId, polyLinesWorld, segmentIndex);
|
|
51
|
+
const contourRepresentationData = segmentation.representationData
|
|
52
|
+
.Contour;
|
|
53
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
54
|
+
if (!annotationUIDsMap) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
annotationUIDsMap.set(segmentIndex, annotationUIDsMapNew.get(segmentIndex));
|
|
58
|
+
}
|
|
59
|
+
export function subtraction(viewport, segmentation, segmentIndex1, segmentIndex2, { name, segmentIndex, color }) {
|
|
60
|
+
const { polyLinesCanvas1, polyLinesCanvas2 } = extractPolylines(viewport, segmentation, segmentIndex1, segmentIndex2) ||
|
|
61
|
+
{};
|
|
62
|
+
if (!polyLinesCanvas1 || polyLinesCanvas2) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const polylinesMerged = subtractPolylineSets(polyLinesCanvas1, polyLinesCanvas2);
|
|
66
|
+
const polyLinesWorld = polylinesMerged.map((polyline) => convertContourPolylineToWorld(polyline, viewport));
|
|
67
|
+
const annotationUIDsMapNew = addPolylinesToSegmentation(viewport, segmentation.segmentationId, polyLinesWorld, segmentIndex);
|
|
68
|
+
const contourRepresentationData = segmentation.representationData
|
|
69
|
+
.Contour;
|
|
70
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
71
|
+
if (!annotationUIDsMap) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
annotationUIDsMap.set(segmentIndex, annotationUIDsMapNew.get(segmentIndex));
|
|
75
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { ContourSegmentationAnnotation } from '../../types/ContourSegmentationAnnotation';
|
|
3
|
+
declare function processMultipleIntersections(viewport: Types.IViewport, sourceAnnotation: ContourSegmentationAnnotation, sourcePolyline: Types.Point2[], intersectingContours: Array<{
|
|
4
|
+
targetAnnotation: ContourSegmentationAnnotation;
|
|
5
|
+
targetPolyline: Types.Point2[];
|
|
6
|
+
isContourHole: boolean;
|
|
7
|
+
}>): void;
|
|
8
|
+
export { processMultipleIntersections };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { utilities as csUtils, getEnabledElement } from '@cornerstonejs/core';
|
|
2
|
+
import { ContourWindingDirection } from '../../types/ContourAnnotation';
|
|
3
|
+
import * as math from '../math';
|
|
4
|
+
import updateContourPolyline from '../contours/updateContourPolyline';
|
|
5
|
+
import { addAnnotation, removeAnnotation, getChildAnnotations, addChildAnnotation, clearParentAnnotation, } from '../../stateManagement/annotation/annotationState';
|
|
6
|
+
import { addContourSegmentationAnnotation } from './addContourSegmentationAnnotation';
|
|
7
|
+
import { removeContourSegmentationAnnotation } from './removeContourSegmentationAnnotation';
|
|
8
|
+
import { triggerAnnotationModified } from '../../stateManagement/annotation/helpers/state';
|
|
9
|
+
import triggerAnnotationRenderForViewportIds from '../triggerAnnotationRenderForViewportIds';
|
|
10
|
+
import { getViewportIdsWithToolToRender } from '../viewportFilters';
|
|
11
|
+
import { hasToolByName, hasTool } from '../../store/addTool';
|
|
12
|
+
const DEFAULT_CONTOUR_SEG_TOOL_NAME = 'PlanarFreehandContourSegmentationTool';
|
|
13
|
+
function processMultipleIntersections(viewport, sourceAnnotation, sourcePolyline, intersectingContours) {
|
|
14
|
+
const holeOperations = intersectingContours.filter((item) => item.isContourHole);
|
|
15
|
+
const mergeOperations = intersectingContours.filter((item) => !item.isContourHole);
|
|
16
|
+
if (holeOperations.length > 0) {
|
|
17
|
+
const primaryHoleTarget = holeOperations[0];
|
|
18
|
+
createPolylineHole(viewport, primaryHoleTarget.targetAnnotation, sourceAnnotation);
|
|
19
|
+
updateViewportsForAnnotations(viewport, [
|
|
20
|
+
sourceAnnotation,
|
|
21
|
+
primaryHoleTarget.targetAnnotation,
|
|
22
|
+
]);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (mergeOperations.length === 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!hasToolByName(DEFAULT_CONTOUR_SEG_TOOL_NAME)) {
|
|
29
|
+
console.warn(`${DEFAULT_CONTOUR_SEG_TOOL_NAME} is not registered in cornerstone. Cannot process multiple intersections.`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
processSequentialIntersections(viewport, sourceAnnotation, sourcePolyline, mergeOperations);
|
|
33
|
+
}
|
|
34
|
+
function processSequentialIntersections(viewport, sourceAnnotation, sourcePolyline, mergeOperations) {
|
|
35
|
+
const { element } = viewport;
|
|
36
|
+
const allAnnotationsToRemove = [sourceAnnotation];
|
|
37
|
+
const allResultPolylines = [];
|
|
38
|
+
const allHoles = [];
|
|
39
|
+
mergeOperations.forEach(({ targetAnnotation }) => {
|
|
40
|
+
const holes = getContourHolesData(viewport, targetAnnotation);
|
|
41
|
+
allHoles.push(...holes);
|
|
42
|
+
allAnnotationsToRemove.push(targetAnnotation);
|
|
43
|
+
});
|
|
44
|
+
const sourceStartPoint = sourcePolyline[0];
|
|
45
|
+
const shouldMerge = mergeOperations.some(({ targetPolyline }) => math.polyline.containsPoint(targetPolyline, sourceStartPoint));
|
|
46
|
+
if (shouldMerge) {
|
|
47
|
+
let resultPolyline = sourcePolyline;
|
|
48
|
+
mergeOperations.forEach(({ targetPolyline }) => {
|
|
49
|
+
resultPolyline = math.polyline.mergePolylines(resultPolyline, targetPolyline);
|
|
50
|
+
});
|
|
51
|
+
allResultPolylines.push(resultPolyline);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
mergeOperations.forEach(({ targetPolyline }) => {
|
|
55
|
+
const subtractedPolylines = math.polyline.subtractPolylines(targetPolyline, sourcePolyline);
|
|
56
|
+
allResultPolylines.push(...subtractedPolylines);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
allAnnotationsToRemove.forEach((annotation) => {
|
|
60
|
+
removeAnnotation(annotation.annotationUID);
|
|
61
|
+
removeContourSegmentationAnnotation(annotation);
|
|
62
|
+
});
|
|
63
|
+
allHoles.forEach((holeData) => clearParentAnnotation(holeData.annotation));
|
|
64
|
+
const baseAnnotation = mergeOperations[0].targetAnnotation;
|
|
65
|
+
const newAnnotations = [];
|
|
66
|
+
allResultPolylines.forEach((polyline) => {
|
|
67
|
+
if (!polyline || polyline.length < 3) {
|
|
68
|
+
console.warn('Skipping creation of new annotation due to invalid polyline:', polyline);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const newAnnotation = createNewAnnotationFromPolyline(viewport, baseAnnotation, polyline);
|
|
72
|
+
addAnnotation(newAnnotation, element);
|
|
73
|
+
addContourSegmentationAnnotation(newAnnotation);
|
|
74
|
+
triggerAnnotationModified(newAnnotation, viewport.element);
|
|
75
|
+
newAnnotations.push(newAnnotation);
|
|
76
|
+
});
|
|
77
|
+
reassignHolesToNewAnnotations(viewport, allHoles, newAnnotations);
|
|
78
|
+
updateViewportsForAnnotations(viewport, allAnnotationsToRemove);
|
|
79
|
+
}
|
|
80
|
+
function createNewAnnotationFromPolyline(viewport, baseAnnotation, polyline) {
|
|
81
|
+
const startPointWorld = viewport.canvasToWorld(polyline[0]);
|
|
82
|
+
const endPointWorld = viewport.canvasToWorld(polyline[polyline.length - 1]);
|
|
83
|
+
const newAnnotation = {
|
|
84
|
+
metadata: {
|
|
85
|
+
...baseAnnotation.metadata,
|
|
86
|
+
toolName: DEFAULT_CONTOUR_SEG_TOOL_NAME,
|
|
87
|
+
originalToolName: baseAnnotation.metadata.originalToolName ||
|
|
88
|
+
baseAnnotation.metadata.toolName,
|
|
89
|
+
},
|
|
90
|
+
data: {
|
|
91
|
+
cachedStats: {},
|
|
92
|
+
handles: {
|
|
93
|
+
points: [startPointWorld, endPointWorld],
|
|
94
|
+
textBox: baseAnnotation.data.handles.textBox
|
|
95
|
+
? { ...baseAnnotation.data.handles.textBox }
|
|
96
|
+
: undefined,
|
|
97
|
+
},
|
|
98
|
+
contour: {
|
|
99
|
+
polyline: [],
|
|
100
|
+
closed: true,
|
|
101
|
+
},
|
|
102
|
+
spline: baseAnnotation.data.spline,
|
|
103
|
+
segmentation: {
|
|
104
|
+
...baseAnnotation.data.segmentation,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
annotationUID: csUtils.uuidv4(),
|
|
108
|
+
highlighted: true,
|
|
109
|
+
invalidated: true,
|
|
110
|
+
isLocked: false,
|
|
111
|
+
isVisible: undefined,
|
|
112
|
+
interpolationUID: baseAnnotation.interpolationUID,
|
|
113
|
+
interpolationCompleted: baseAnnotation.interpolationCompleted,
|
|
114
|
+
};
|
|
115
|
+
updateContourPolyline(newAnnotation, {
|
|
116
|
+
points: polyline,
|
|
117
|
+
closed: true,
|
|
118
|
+
targetWindingDirection: ContourWindingDirection.Clockwise,
|
|
119
|
+
}, viewport);
|
|
120
|
+
return newAnnotation;
|
|
121
|
+
}
|
|
122
|
+
function reassignHolesToNewAnnotations(viewport, holes, newAnnotations) {
|
|
123
|
+
holes.forEach((holeData) => {
|
|
124
|
+
const parentAnnotation = newAnnotations.find((annotation) => {
|
|
125
|
+
const parentPolyline = convertContourPolylineToCanvasSpace(annotation.data.contour.polyline, viewport);
|
|
126
|
+
return math.polyline.containsPoints(parentPolyline, holeData.polyline);
|
|
127
|
+
});
|
|
128
|
+
if (parentAnnotation) {
|
|
129
|
+
addChildAnnotation(parentAnnotation, holeData.annotation);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function getContourHolesData(viewport, annotation) {
|
|
134
|
+
return getChildAnnotations(annotation).map((holeAnnotation) => {
|
|
135
|
+
const contourHoleAnnotation = holeAnnotation;
|
|
136
|
+
const polyline = convertContourPolylineToCanvasSpace(contourHoleAnnotation.data.contour.polyline, viewport);
|
|
137
|
+
return { annotation: contourHoleAnnotation, polyline };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function createPolylineHole(viewport, targetAnnotation, holeAnnotation) {
|
|
141
|
+
addChildAnnotation(targetAnnotation, holeAnnotation);
|
|
142
|
+
removeContourSegmentationAnnotation(holeAnnotation);
|
|
143
|
+
const { contour: holeContour } = holeAnnotation.data;
|
|
144
|
+
const holePolylineCanvas = convertContourPolylineToCanvasSpace(holeContour.polyline, viewport);
|
|
145
|
+
updateContourPolyline(holeAnnotation, {
|
|
146
|
+
points: holePolylineCanvas,
|
|
147
|
+
closed: holeContour.closed,
|
|
148
|
+
targetWindingDirection: targetAnnotation.data.contour.windingDirection ===
|
|
149
|
+
ContourWindingDirection.Clockwise
|
|
150
|
+
? ContourWindingDirection.CounterClockwise
|
|
151
|
+
: ContourWindingDirection.Clockwise,
|
|
152
|
+
}, viewport);
|
|
153
|
+
}
|
|
154
|
+
function convertContourPolylineToCanvasSpace(polyline, viewport) {
|
|
155
|
+
const numPoints = polyline.length;
|
|
156
|
+
const projectedPolyline = new Array(numPoints);
|
|
157
|
+
for (let i = 0; i < numPoints; i++) {
|
|
158
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
159
|
+
}
|
|
160
|
+
return projectedPolyline;
|
|
161
|
+
}
|
|
162
|
+
function updateViewportsForAnnotations(viewport, annotations) {
|
|
163
|
+
const { element } = viewport;
|
|
164
|
+
const updatedToolNames = new Set([DEFAULT_CONTOUR_SEG_TOOL_NAME]);
|
|
165
|
+
annotations.forEach((annotation) => {
|
|
166
|
+
updatedToolNames.add(annotation.metadata.toolName);
|
|
167
|
+
});
|
|
168
|
+
for (const toolName of updatedToolNames.values()) {
|
|
169
|
+
if (hasToolByName(toolName)) {
|
|
170
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(element, toolName);
|
|
171
|
+
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export { processMultipleIntersections };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { ContourSegmentationAnnotation } from '../../types/ContourSegmentationAnnotation';
|
|
3
|
+
export declare function convertContourPolylineToCanvasSpace(polyline: Types.Point3[], viewport: Types.IViewport): Types.Point2[];
|
|
4
|
+
export declare function convertContourPolylineToWorld(polyline: Types.Point2[], viewport: Types.IViewport): Types.Point3[];
|
|
5
|
+
export declare function checkIntersection(sourcePolyline: Types.Point2[], targetPolyline: Types.Point2[]): {
|
|
6
|
+
hasIntersection: boolean;
|
|
7
|
+
isContourHole: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function getContourHolesData(viewport: Types.IViewport, annotation: ContourSegmentationAnnotation): Array<{
|
|
10
|
+
annotation: ContourSegmentationAnnotation;
|
|
11
|
+
polyline: Types.Point2[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare function createPolylineHole(viewport: Types.IViewport, targetAnnotation: ContourSegmentationAnnotation, holeAnnotation: ContourSegmentationAnnotation): void;
|
|
14
|
+
export declare function combinePolylines(viewport: Types.IViewport, targetAnnotation: ContourSegmentationAnnotation, targetPolyline: Types.Point2[], sourceAnnotation: ContourSegmentationAnnotation, sourcePolyline: Types.Point2[]): void;
|
|
15
|
+
export declare function createNewAnnotationFromPolyline(viewport: Types.IViewport, templateAnnotation: ContourSegmentationAnnotation, polyline: Types.Point2[]): ContourSegmentationAnnotation;
|
|
16
|
+
export declare function updateViewportsForAnnotations(viewport: Types.IViewport, annotations: ContourSegmentationAnnotation[]): void;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { getEnabledElement, utilities as csUtils } from '@cornerstonejs/core';
|
|
2
|
+
import { ContourWindingDirection } from '../../types/ContourAnnotation';
|
|
3
|
+
import * as math from '../math';
|
|
4
|
+
import updateContourPolyline from '../contours/updateContourPolyline';
|
|
5
|
+
import { addAnnotation, removeAnnotation, getChildAnnotations, addChildAnnotation, clearParentAnnotation, } from '../../stateManagement/annotation/annotationState';
|
|
6
|
+
import { addContourSegmentationAnnotation } from './addContourSegmentationAnnotation';
|
|
7
|
+
import { removeContourSegmentationAnnotation } from './removeContourSegmentationAnnotation';
|
|
8
|
+
import { triggerAnnotationModified } from '../../stateManagement/annotation/helpers/state';
|
|
9
|
+
import triggerAnnotationRenderForViewportIds from '../triggerAnnotationRenderForViewportIds';
|
|
10
|
+
import { getViewportIdsWithToolToRender } from '../viewportFilters';
|
|
11
|
+
import { hasToolByName } from '../../store/addTool';
|
|
12
|
+
const DEFAULT_CONTOUR_SEG_TOOL_NAME = 'PlanarFreehandContourSegmentationTool';
|
|
13
|
+
export function convertContourPolylineToCanvasSpace(polyline, viewport) {
|
|
14
|
+
const numPoints = polyline.length;
|
|
15
|
+
const projectedPolyline = new Array(numPoints);
|
|
16
|
+
for (let i = 0; i < numPoints; i++) {
|
|
17
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
18
|
+
}
|
|
19
|
+
return projectedPolyline;
|
|
20
|
+
}
|
|
21
|
+
export function convertContourPolylineToWorld(polyline, viewport) {
|
|
22
|
+
const numPoints = polyline.length;
|
|
23
|
+
const projectedPolyline = new Array(numPoints);
|
|
24
|
+
for (let i = 0; i < numPoints; i++) {
|
|
25
|
+
projectedPolyline[i] = viewport.canvasToWorld(polyline[i]);
|
|
26
|
+
}
|
|
27
|
+
return projectedPolyline;
|
|
28
|
+
}
|
|
29
|
+
export function checkIntersection(sourcePolyline, targetPolyline) {
|
|
30
|
+
const sourceAABB = math.polyline.getAABB(sourcePolyline);
|
|
31
|
+
const targetAABB = math.polyline.getAABB(targetPolyline);
|
|
32
|
+
const aabbIntersect = math.aabb.intersectAABB(sourceAABB, targetAABB);
|
|
33
|
+
if (!aabbIntersect) {
|
|
34
|
+
return { hasIntersection: false, isContourHole: false };
|
|
35
|
+
}
|
|
36
|
+
const lineSegmentsIntersect = math.polyline.intersectPolyline(sourcePolyline, targetPolyline);
|
|
37
|
+
const isContourHole = !lineSegmentsIntersect &&
|
|
38
|
+
math.polyline.containsPoints(targetPolyline, sourcePolyline);
|
|
39
|
+
const hasIntersection = lineSegmentsIntersect || isContourHole;
|
|
40
|
+
return { hasIntersection, isContourHole };
|
|
41
|
+
}
|
|
42
|
+
export function getContourHolesData(viewport, annotation) {
|
|
43
|
+
return getChildAnnotations(annotation).map((holeAnnotation) => {
|
|
44
|
+
const contourHoleAnnotation = holeAnnotation;
|
|
45
|
+
const polyline = convertContourPolylineToCanvasSpace(contourHoleAnnotation.data.contour.polyline, viewport);
|
|
46
|
+
return { annotation: contourHoleAnnotation, polyline };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function createPolylineHole(viewport, targetAnnotation, holeAnnotation) {
|
|
50
|
+
addChildAnnotation(targetAnnotation, holeAnnotation);
|
|
51
|
+
removeContourSegmentationAnnotation(holeAnnotation);
|
|
52
|
+
const { contour: holeContour } = holeAnnotation.data;
|
|
53
|
+
const holePolylineCanvas = convertContourPolylineToCanvasSpace(holeContour.polyline, viewport);
|
|
54
|
+
updateContourPolyline(holeAnnotation, {
|
|
55
|
+
points: holePolylineCanvas,
|
|
56
|
+
closed: holeContour.closed,
|
|
57
|
+
targetWindingDirection: targetAnnotation.data.contour.windingDirection ===
|
|
58
|
+
ContourWindingDirection.Clockwise
|
|
59
|
+
? ContourWindingDirection.CounterClockwise
|
|
60
|
+
: ContourWindingDirection.Clockwise,
|
|
61
|
+
}, viewport);
|
|
62
|
+
const { element } = viewport;
|
|
63
|
+
updateViewportsForAnnotations(viewport, [targetAnnotation, holeAnnotation]);
|
|
64
|
+
}
|
|
65
|
+
export function combinePolylines(viewport, targetAnnotation, targetPolyline, sourceAnnotation, sourcePolyline) {
|
|
66
|
+
if (!hasToolByName(DEFAULT_CONTOUR_SEG_TOOL_NAME)) {
|
|
67
|
+
console.warn(`${DEFAULT_CONTOUR_SEG_TOOL_NAME} is not registered in cornerstone. Cannot combine polylines.`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const sourceStartPoint = sourcePolyline[0];
|
|
71
|
+
const mergePolylines = math.polyline.containsPoint(targetPolyline, sourceStartPoint);
|
|
72
|
+
const contourHolesData = getContourHolesData(viewport, targetAnnotation);
|
|
73
|
+
const unassignedContourHolesSet = new Set(contourHolesData);
|
|
74
|
+
const reassignedContourHolesMap = new Map();
|
|
75
|
+
const assignHoleToPolyline = (parentPolyline, holeData) => {
|
|
76
|
+
let holes = reassignedContourHolesMap.get(parentPolyline);
|
|
77
|
+
if (!holes) {
|
|
78
|
+
holes = [];
|
|
79
|
+
reassignedContourHolesMap.set(parentPolyline, holes);
|
|
80
|
+
}
|
|
81
|
+
holes.push(holeData);
|
|
82
|
+
unassignedContourHolesSet.delete(holeData);
|
|
83
|
+
};
|
|
84
|
+
const newPolylines = [];
|
|
85
|
+
if (mergePolylines) {
|
|
86
|
+
const mergedPolyline = math.polyline.mergePolylines(targetPolyline, sourcePolyline);
|
|
87
|
+
newPolylines.push(mergedPolyline);
|
|
88
|
+
Array.from(unassignedContourHolesSet.keys()).forEach((holeData) => assignHoleToPolyline(mergedPolyline, holeData));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const subtractedPolylines = math.polyline.subtractPolylines(targetPolyline, sourcePolyline);
|
|
92
|
+
subtractedPolylines.forEach((newPolyline) => {
|
|
93
|
+
newPolylines.push(newPolyline);
|
|
94
|
+
Array.from(unassignedContourHolesSet.keys()).forEach((holeData) => {
|
|
95
|
+
const containsHole = math.polyline.containsPoints(newPolyline, holeData.polyline);
|
|
96
|
+
if (containsHole) {
|
|
97
|
+
assignHoleToPolyline(newPolyline, holeData);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
Array.from(reassignedContourHolesMap.values()).forEach((contourHolesDataArray) => contourHolesDataArray.forEach((contourHoleData) => clearParentAnnotation(contourHoleData.annotation)));
|
|
103
|
+
const { element } = viewport;
|
|
104
|
+
const { metadata, data } = targetAnnotation;
|
|
105
|
+
const { handles, segmentation } = data;
|
|
106
|
+
const { textBox } = handles;
|
|
107
|
+
removeAnnotation(sourceAnnotation.annotationUID);
|
|
108
|
+
removeAnnotation(targetAnnotation.annotationUID);
|
|
109
|
+
removeContourSegmentationAnnotation(sourceAnnotation);
|
|
110
|
+
removeContourSegmentationAnnotation(targetAnnotation);
|
|
111
|
+
const newAnnotations = [];
|
|
112
|
+
for (let i = 0; i < newPolylines.length; i++) {
|
|
113
|
+
const polyline = newPolylines[i];
|
|
114
|
+
if (!polyline || polyline.length < 3) {
|
|
115
|
+
console.warn('Skipping creation of new annotation due to invalid polyline:', polyline);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const newAnnotation = createNewAnnotationFromPolyline(viewport, targetAnnotation, polyline);
|
|
119
|
+
addAnnotation(newAnnotation, element);
|
|
120
|
+
addContourSegmentationAnnotation(newAnnotation);
|
|
121
|
+
triggerAnnotationModified(newAnnotation, viewport.element);
|
|
122
|
+
newAnnotations.push(newAnnotation);
|
|
123
|
+
reassignedContourHolesMap
|
|
124
|
+
.get(polyline)
|
|
125
|
+
?.forEach((holeData) => addChildAnnotation(newAnnotation, holeData.annotation));
|
|
126
|
+
}
|
|
127
|
+
updateViewportsForAnnotations(viewport, [targetAnnotation, sourceAnnotation]);
|
|
128
|
+
}
|
|
129
|
+
export function createNewAnnotationFromPolyline(viewport, templateAnnotation, polyline) {
|
|
130
|
+
const startPointWorld = viewport.canvasToWorld(polyline[0]);
|
|
131
|
+
const endPointWorld = viewport.canvasToWorld(polyline[polyline.length - 1]);
|
|
132
|
+
const newAnnotation = {
|
|
133
|
+
metadata: {
|
|
134
|
+
...templateAnnotation.metadata,
|
|
135
|
+
toolName: DEFAULT_CONTOUR_SEG_TOOL_NAME,
|
|
136
|
+
originalToolName: templateAnnotation.metadata.originalToolName ||
|
|
137
|
+
templateAnnotation.metadata.toolName,
|
|
138
|
+
},
|
|
139
|
+
data: {
|
|
140
|
+
cachedStats: {},
|
|
141
|
+
handles: {
|
|
142
|
+
points: [startPointWorld, endPointWorld],
|
|
143
|
+
textBox: templateAnnotation.data.handles.textBox
|
|
144
|
+
? { ...templateAnnotation.data.handles.textBox }
|
|
145
|
+
: undefined,
|
|
146
|
+
},
|
|
147
|
+
contour: {
|
|
148
|
+
polyline: [],
|
|
149
|
+
closed: true,
|
|
150
|
+
},
|
|
151
|
+
spline: templateAnnotation.data.spline,
|
|
152
|
+
segmentation: {
|
|
153
|
+
...templateAnnotation.data.segmentation,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
annotationUID: csUtils.uuidv4(),
|
|
157
|
+
highlighted: true,
|
|
158
|
+
invalidated: true,
|
|
159
|
+
isLocked: false,
|
|
160
|
+
isVisible: undefined,
|
|
161
|
+
interpolationUID: templateAnnotation.interpolationUID,
|
|
162
|
+
interpolationCompleted: templateAnnotation.interpolationCompleted,
|
|
163
|
+
};
|
|
164
|
+
updateContourPolyline(newAnnotation, {
|
|
165
|
+
points: polyline,
|
|
166
|
+
closed: true,
|
|
167
|
+
targetWindingDirection: ContourWindingDirection.Clockwise,
|
|
168
|
+
}, viewport);
|
|
169
|
+
return newAnnotation;
|
|
170
|
+
}
|
|
171
|
+
export function updateViewportsForAnnotations(viewport, annotations) {
|
|
172
|
+
const { element } = viewport;
|
|
173
|
+
const updatedToolNames = new Set([DEFAULT_CONTOUR_SEG_TOOL_NAME]);
|
|
174
|
+
annotations.forEach((annotation) => {
|
|
175
|
+
updatedToolNames.add(annotation.metadata.toolName);
|
|
176
|
+
});
|
|
177
|
+
for (const toolName of updatedToolNames.values()) {
|
|
178
|
+
if (hasToolByName(toolName)) {
|
|
179
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(element, toolName);
|
|
180
|
+
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export declare function unifyPolylineSets(polylinesSetA: Types.Point2[][], polylinesSetB: Types.Point2[][]): Types.Point2[][];
|
|
3
|
+
export declare function unifyMultiplePolylineSets(polylineSets: Types.Point2[][][]): Types.Point2[][];
|
|
4
|
+
export declare function unifyAnnotationPolylines(annotationsSetA: Array<{
|
|
5
|
+
data: {
|
|
6
|
+
contour: {
|
|
7
|
+
polyline: Types.Point3[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
}>, annotationsSetB: Array<{
|
|
11
|
+
data: {
|
|
12
|
+
contour: {
|
|
13
|
+
polyline: Types.Point3[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}>, viewport: Types.IViewport): Types.Point2[][];
|
|
17
|
+
export declare function subtractPolylineSets(polylinesSetA: Types.Point2[][], polylinesSetB: Types.Point2[][]): Types.Point2[][];
|
|
18
|
+
export declare function subtractMultiplePolylineSets(basePolylineSet: Types.Point2[][], subtractorSets: Types.Point2[][][]): Types.Point2[][];
|
|
19
|
+
export declare function subtractAnnotationPolylines(baseAnnotations: Array<{
|
|
20
|
+
data: {
|
|
21
|
+
contour: {
|
|
22
|
+
polyline: Types.Point3[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}>, subtractorAnnotations: Array<{
|
|
26
|
+
data: {
|
|
27
|
+
contour: {
|
|
28
|
+
polyline: Types.Point3[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}>, viewport: Types.IViewport): Types.Point2[][];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as math from '../math';
|
|
2
|
+
import { checkIntersection } from './sharedOperations';
|
|
3
|
+
export function unifyPolylineSets(polylinesSetA, polylinesSetB) {
|
|
4
|
+
const result = [];
|
|
5
|
+
const processedFromA = new Set();
|
|
6
|
+
const processedFromB = new Set();
|
|
7
|
+
for (let i = 0; i < polylinesSetA.length; i++) {
|
|
8
|
+
if (processedFromA.has(i)) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const polylineA = polylinesSetA[i];
|
|
12
|
+
let merged = false;
|
|
13
|
+
for (let j = 0; j < polylinesSetB.length; j++) {
|
|
14
|
+
if (processedFromB.has(j)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const polylineB = polylinesSetB[j];
|
|
18
|
+
const intersection = checkIntersection(polylineA, polylineB);
|
|
19
|
+
if (intersection.hasIntersection && !intersection.isContourHole) {
|
|
20
|
+
const mergedPolyline = math.polyline.mergePolylines(polylineA, polylineB);
|
|
21
|
+
result.push(mergedPolyline);
|
|
22
|
+
processedFromA.add(i);
|
|
23
|
+
processedFromB.add(j);
|
|
24
|
+
merged = true;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!merged) {
|
|
29
|
+
result.push([...polylineA]);
|
|
30
|
+
processedFromA.add(i);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (let j = 0; j < polylinesSetB.length; j++) {
|
|
34
|
+
if (!processedFromB.has(j)) {
|
|
35
|
+
result.push([...polylinesSetB[j]]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
export function unifyMultiplePolylineSets(polylineSets) {
|
|
41
|
+
if (polylineSets.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
if (polylineSets.length === 1) {
|
|
45
|
+
return polylineSets[0].map((polyline) => [...polyline]);
|
|
46
|
+
}
|
|
47
|
+
let result = polylineSets[0].map((polyline) => [...polyline]);
|
|
48
|
+
for (let i = 1; i < polylineSets.length; i++) {
|
|
49
|
+
result = unifyPolylineSets(result, polylineSets[i]);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
export function unifyAnnotationPolylines(annotationsSetA, annotationsSetB, viewport) {
|
|
54
|
+
const polylinesSetA = annotationsSetA.map((annotation) => convertPolylineToCanvasSpace(annotation.data.contour.polyline, viewport));
|
|
55
|
+
const polylinesSetB = annotationsSetB.map((annotation) => convertPolylineToCanvasSpace(annotation.data.contour.polyline, viewport));
|
|
56
|
+
return unifyPolylineSets(polylinesSetA, polylinesSetB);
|
|
57
|
+
}
|
|
58
|
+
function convertPolylineToCanvasSpace(polyline, viewport) {
|
|
59
|
+
const numPoints = polyline.length;
|
|
60
|
+
const projectedPolyline = new Array(numPoints);
|
|
61
|
+
for (let i = 0; i < numPoints; i++) {
|
|
62
|
+
projectedPolyline[i] = viewport.worldToCanvas(polyline[i]);
|
|
63
|
+
}
|
|
64
|
+
return projectedPolyline;
|
|
65
|
+
}
|
|
66
|
+
export function subtractPolylineSets(polylinesSetA, polylinesSetB) {
|
|
67
|
+
const result = [];
|
|
68
|
+
const processedFromA = new Set();
|
|
69
|
+
for (let i = 0; i < polylinesSetA.length; i++) {
|
|
70
|
+
if (processedFromA.has(i)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let currentPolylines = [polylinesSetA[i]];
|
|
74
|
+
let wasSubtracted = false;
|
|
75
|
+
for (let j = 0; j < polylinesSetB.length; j++) {
|
|
76
|
+
const polylineB = polylinesSetB[j];
|
|
77
|
+
const newPolylines = [];
|
|
78
|
+
for (const currentPolyline of currentPolylines) {
|
|
79
|
+
const intersection = checkIntersection(currentPolyline, polylineB);
|
|
80
|
+
if (intersection.hasIntersection && !intersection.isContourHole) {
|
|
81
|
+
const subtractedPolylines = math.polyline.subtractPolylines(currentPolyline, polylineB);
|
|
82
|
+
newPolylines.push(...subtractedPolylines);
|
|
83
|
+
wasSubtracted = true;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
newPolylines.push(currentPolyline);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
currentPolylines = newPolylines;
|
|
90
|
+
}
|
|
91
|
+
result.push(...currentPolylines);
|
|
92
|
+
processedFromA.add(i);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
export function subtractMultiplePolylineSets(basePolylineSet, subtractorSets) {
|
|
97
|
+
if (subtractorSets.length === 0) {
|
|
98
|
+
return basePolylineSet.map((polyline) => [...polyline]);
|
|
99
|
+
}
|
|
100
|
+
let result = basePolylineSet.map((polyline) => [...polyline]);
|
|
101
|
+
for (let i = 0; i < subtractorSets.length; i++) {
|
|
102
|
+
result = subtractPolylineSets(result, subtractorSets[i]);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
export function subtractAnnotationPolylines(baseAnnotations, subtractorAnnotations, viewport) {
|
|
107
|
+
const basePolylines = baseAnnotations.map((annotation) => convertPolylineToCanvasSpace(annotation.data.contour.polyline, viewport));
|
|
108
|
+
const subtractorPolylines = subtractorAnnotations.map((annotation) => convertPolylineToCanvasSpace(annotation.data.contour.polyline, viewport));
|
|
109
|
+
return subtractPolylineSets(basePolylines, subtractorPolylines);
|
|
110
|
+
}
|
|
@@ -6,7 +6,7 @@ import EventTypes from '../../../enums/Events';
|
|
|
6
6
|
import * as annotationState from '../../../stateManagement/annotation';
|
|
7
7
|
import selectHandles from './selectHandles';
|
|
8
8
|
import updateChildInterpolationUID from './updateChildInterpolationUID';
|
|
9
|
-
import { createPolylineHole } from '
|
|
9
|
+
import { createPolylineHole } from '../../contourSegmentation';
|
|
10
10
|
const { PointsManager } = utilities;
|
|
11
11
|
const dP = 0.2;
|
|
12
12
|
function interpolate(viewportData) {
|
|
@@ -144,7 +144,10 @@ function mergePolylines(targetPolyline, sourcePolyline) {
|
|
|
144
144
|
}
|
|
145
145
|
const mergedPolyline = [startPoint.coordinates];
|
|
146
146
|
let currentPoint = startPoint.next;
|
|
147
|
-
|
|
147
|
+
let iterationCount = 0;
|
|
148
|
+
const maxIterations = targetPolyline.length + sourcePolyline.length + 1000;
|
|
149
|
+
while (currentPoint !== startPoint && iterationCount < maxIterations) {
|
|
150
|
+
iterationCount++;
|
|
148
151
|
if (currentPoint.type === PolylinePointType.Intersection &&
|
|
149
152
|
currentPoint.cloned) {
|
|
150
153
|
currentPoint = currentPoint.next;
|
|
@@ -152,6 +155,13 @@ function mergePolylines(targetPolyline, sourcePolyline) {
|
|
|
152
155
|
}
|
|
153
156
|
mergedPolyline.push(currentPoint.coordinates);
|
|
154
157
|
currentPoint = currentPoint.next;
|
|
158
|
+
if (!currentPoint) {
|
|
159
|
+
console.warn('Broken linked list detected in mergePolylines, breaking loop');
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (iterationCount >= maxIterations) {
|
|
164
|
+
console.warn('Maximum iterations reached in mergePolylines, possible infinite loop detected');
|
|
155
165
|
}
|
|
156
166
|
return mergedPolyline;
|
|
157
167
|
}
|
|
@@ -165,11 +175,19 @@ function subtractPolylines(targetPolyline, sourcePolyline) {
|
|
|
165
175
|
const { targetPolylinePoints } = getSourceAndTargetPointsList(targetPolyline, sourcePolyline);
|
|
166
176
|
let startPoint = null;
|
|
167
177
|
const subtractedPolylines = [];
|
|
168
|
-
|
|
178
|
+
let outerIterationCount = 0;
|
|
179
|
+
const maxOuterIterations = targetPolyline.length * 2;
|
|
180
|
+
while ((startPoint = getUnvisitedOutsidePoint(targetPolylinePoints)) &&
|
|
181
|
+
outerIterationCount < maxOuterIterations) {
|
|
182
|
+
outerIterationCount++;
|
|
169
183
|
const subtractedPolyline = [startPoint.coordinates];
|
|
170
184
|
let currentPoint = startPoint.next;
|
|
185
|
+
let innerIterationCount = 0;
|
|
186
|
+
const maxInnerIterations = targetPolyline.length + sourcePolyline.length + 1000;
|
|
171
187
|
startPoint.visited = true;
|
|
172
|
-
while (currentPoint !== startPoint
|
|
188
|
+
while (currentPoint !== startPoint &&
|
|
189
|
+
innerIterationCount < maxInnerIterations) {
|
|
190
|
+
innerIterationCount++;
|
|
173
191
|
currentPoint.visited = true;
|
|
174
192
|
if (currentPoint.type === PolylinePointType.Intersection &&
|
|
175
193
|
currentPoint.cloned) {
|
|
@@ -178,9 +196,19 @@ function subtractPolylines(targetPolyline, sourcePolyline) {
|
|
|
178
196
|
}
|
|
179
197
|
subtractedPolyline.push(currentPoint.coordinates);
|
|
180
198
|
currentPoint = currentPoint.next;
|
|
199
|
+
if (!currentPoint) {
|
|
200
|
+
console.warn('Broken linked list detected in subtractPolylines, breaking inner loop');
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (innerIterationCount >= maxInnerIterations) {
|
|
205
|
+
console.warn('Maximum inner iterations reached in subtractPolylines, possible infinite loop detected');
|
|
181
206
|
}
|
|
182
207
|
subtractedPolylines.push(subtractedPolyline);
|
|
183
208
|
}
|
|
209
|
+
if (outerIterationCount >= maxOuterIterations) {
|
|
210
|
+
console.warn('Maximum outer iterations reached in subtractPolylines, possible infinite loop detected');
|
|
211
|
+
}
|
|
184
212
|
return subtractedPolylines;
|
|
185
213
|
}
|
|
186
214
|
export { mergePolylines, subtractPolylines };
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.19.
|
|
1
|
+
export declare const version = "3.19.3";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.19.
|
|
1
|
+
export const version = '3.19.3';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "3.19.
|
|
3
|
+
"version": "3.19.3",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"canvas": "^3.1.0"
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|
|
111
|
-
"@cornerstonejs/core": "^3.19.
|
|
111
|
+
"@cornerstonejs/core": "^3.19.3",
|
|
112
112
|
"@kitware/vtk.js": "32.12.1",
|
|
113
113
|
"@types/d3-array": "^3.0.4",
|
|
114
114
|
"@types/d3-interpolate": "^3.0.1",
|
|
@@ -127,5 +127,5 @@
|
|
|
127
127
|
"type": "individual",
|
|
128
128
|
"url": "https://ohif.org/donate"
|
|
129
129
|
},
|
|
130
|
-
"gitHead": "
|
|
130
|
+
"gitHead": "dd1f16a4bfbced2de7ef786a3d3b3205aa661543"
|
|
131
131
|
}
|