@cornerstonejs/tools 3.24.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/esm/stateManagement/segmentation/utilities/convertContourHoles.d.ts +1 -0
  2. package/dist/esm/stateManagement/segmentation/utilities/convertContourHoles.js +66 -0
  3. package/dist/esm/stateManagement/segmentation/utilities/getAnnotationsUIDMapFromSegmentation.d.ts +1 -0
  4. package/dist/esm/stateManagement/segmentation/utilities/getAnnotationsUIDMapFromSegmentation.js +17 -0
  5. package/dist/esm/stateManagement/segmentation/utilities/index.d.ts +2 -0
  6. package/dist/esm/stateManagement/segmentation/utilities/index.js +3 -0
  7. package/dist/esm/stateManagement/segmentation/utilities/removeContourHoles.js +0 -1
  8. package/dist/esm/tools/annotation/SplineROITool.d.ts +2 -0
  9. package/dist/esm/tools/annotation/SplineROITool.js +18 -0
  10. package/dist/esm/types/index.d.ts +2 -1
  11. package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.d.ts +2 -1
  12. package/dist/esm/utilities/contourSegmentation/addPolylinesToSegmentation.js +3 -4
  13. package/dist/esm/utilities/contourSegmentation/areViewReferencesEqual.d.ts +2 -0
  14. package/dist/esm/utilities/contourSegmentation/areViewReferencesEqual.js +23 -0
  15. package/dist/esm/utilities/contourSegmentation/copyAnnotation.d.ts +3 -0
  16. package/dist/esm/utilities/contourSegmentation/copyAnnotation.js +86 -0
  17. package/dist/esm/utilities/contourSegmentation/getViewReferenceFromAnnotation.d.ts +3 -0
  18. package/dist/esm/utilities/contourSegmentation/getViewReferenceFromAnnotation.js +20 -0
  19. package/dist/esm/utilities/contourSegmentation/index.d.ts +7 -3
  20. package/dist/esm/utilities/contourSegmentation/index.js +7 -3
  21. package/dist/esm/utilities/contourSegmentation/logicalOperators.d.ts +21 -12
  22. package/dist/esm/utilities/contourSegmentation/logicalOperators.js +148 -43
  23. package/dist/esm/utilities/contourSegmentation/polylineInfoTypes.d.ts +9 -0
  24. package/dist/esm/utilities/contourSegmentation/polylineInfoTypes.js +0 -0
  25. package/dist/esm/utilities/contourSegmentation/polylineIntersect.d.ts +2 -0
  26. package/dist/esm/utilities/contourSegmentation/polylineIntersect.js +34 -0
  27. package/dist/esm/utilities/contourSegmentation/polylineSubtract.d.ts +6 -0
  28. package/dist/esm/utilities/contourSegmentation/polylineSubtract.js +67 -0
  29. package/dist/esm/utilities/contourSegmentation/polylineUnify.d.ts +6 -0
  30. package/dist/esm/utilities/contourSegmentation/polylineUnify.js +79 -0
  31. package/dist/esm/utilities/contourSegmentation/polylineXor.d.ts +2 -0
  32. package/dist/esm/utilities/contourSegmentation/polylineXor.js +41 -0
  33. package/dist/esm/utilities/contourSegmentation/sharedOperations.d.ts +2 -0
  34. package/dist/esm/utilities/contourSegmentation/sharedOperations.js +44 -0
  35. package/dist/esm/utilities/math/polyline/arePolylinesIdentical.d.ts +2 -0
  36. package/dist/esm/utilities/math/polyline/arePolylinesIdentical.js +53 -0
  37. package/dist/esm/utilities/math/polyline/combinePolyline.d.ts +1 -2
  38. package/dist/esm/utilities/math/polyline/combinePolyline.js +17 -47
  39. package/dist/esm/utilities/math/polyline/index.d.ts +5 -2
  40. package/dist/esm/utilities/math/polyline/index.js +5 -2
  41. package/dist/esm/utilities/math/polyline/intersectPolylines.d.ts +2 -0
  42. package/dist/esm/utilities/math/polyline/intersectPolylines.js +271 -0
  43. package/dist/esm/utilities/math/polyline/robustSegmentIntersection.d.ts +37 -0
  44. package/dist/esm/utilities/math/polyline/robustSegmentIntersection.js +78 -0
  45. package/dist/esm/utilities/math/polyline/subtractPolylines.d.ts +2 -0
  46. package/dist/esm/utilities/math/polyline/subtractPolylines.js +210 -0
  47. package/dist/esm/version.d.ts +1 -1
  48. package/dist/esm/version.js +1 -1
  49. package/package.json +3 -3
  50. package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.d.ts +0 -31
  51. package/dist/esm/utilities/contourSegmentation/unifyPolylineSets.js +0 -110
@@ -1,6 +1,8 @@
1
1
  import * as mathPoint from '../point';
2
2
  import getLineSegmentIntersectionsIndexes from './getLineSegmentIntersectionsIndexes';
3
3
  import containsPoint from './containsPoint';
4
+ import containsPoints from './containsPoints';
5
+ import intersectPolyline from './intersectPolyline';
4
6
  import getNormal2 from './getNormal2';
5
7
  import { glMatrix, vec3 } from 'gl-matrix';
6
8
  import getLinesIntersection from './getLinesIntersection';
@@ -123,12 +125,21 @@ function getSourceAndTargetPointsList(targetPolyline, sourcePolyline) {
123
125
  return { targetPolylinePoints, sourcePolylinePoints };
124
126
  }
125
127
  function getUnvisitedOutsidePoint(polylinePoints) {
128
+ for (let i = 0, len = polylinePoints.length; i < len; i++) {
129
+ const point = polylinePoints[i];
130
+ if (!point.visited &&
131
+ point.position === PolylinePointPosition.Outside &&
132
+ point.type === PolylinePointType.Vertex) {
133
+ return point;
134
+ }
135
+ }
126
136
  for (let i = 0, len = polylinePoints.length; i < len; i++) {
127
137
  const point = polylinePoints[i];
128
138
  if (!point.visited && point.position === PolylinePointPosition.Outside) {
129
139
  return point;
130
140
  }
131
141
  }
142
+ return undefined;
132
143
  }
133
144
  function mergePolylines(targetPolyline, sourcePolyline) {
134
145
  const targetNormal = getNormal2(targetPolyline);
@@ -137,6 +148,11 @@ function mergePolylines(targetPolyline, sourcePolyline) {
137
148
  if (!glMatrix.equals(1, dotNormals)) {
138
149
  sourcePolyline = sourcePolyline.slice().reverse();
139
150
  }
151
+ const lineSegmentsIntersect = intersectPolyline(sourcePolyline, targetPolyline);
152
+ const targetContainedInSource = !lineSegmentsIntersect && containsPoints(sourcePolyline, targetPolyline);
153
+ if (targetContainedInSource) {
154
+ return sourcePolyline.slice();
155
+ }
140
156
  const { targetPolylinePoints } = getSourceAndTargetPointsList(targetPolyline, sourcePolyline);
141
157
  const startPoint = getUnvisitedOutsidePoint(targetPolylinePoints);
142
158
  if (!startPoint) {
@@ -165,50 +181,4 @@ function mergePolylines(targetPolyline, sourcePolyline) {
165
181
  }
166
182
  return mergedPolyline;
167
183
  }
168
- function subtractPolylines(targetPolyline, sourcePolyline) {
169
- const targetNormal = getNormal2(targetPolyline);
170
- const sourceNormal = getNormal2(sourcePolyline);
171
- const dotNormals = vec3.dot(sourceNormal, targetNormal);
172
- if (!glMatrix.equals(-1, dotNormals)) {
173
- sourcePolyline = sourcePolyline.slice().reverse();
174
- }
175
- const { targetPolylinePoints } = getSourceAndTargetPointsList(targetPolyline, sourcePolyline);
176
- let startPoint = null;
177
- const subtractedPolylines = [];
178
- let outerIterationCount = 0;
179
- const maxOuterIterations = targetPolyline.length * 2;
180
- while ((startPoint = getUnvisitedOutsidePoint(targetPolylinePoints)) &&
181
- outerIterationCount < maxOuterIterations) {
182
- outerIterationCount++;
183
- const subtractedPolyline = [startPoint.coordinates];
184
- let currentPoint = startPoint.next;
185
- let innerIterationCount = 0;
186
- const maxInnerIterations = targetPolyline.length + sourcePolyline.length + 1000;
187
- startPoint.visited = true;
188
- while (currentPoint !== startPoint &&
189
- innerIterationCount < maxInnerIterations) {
190
- innerIterationCount++;
191
- currentPoint.visited = true;
192
- if (currentPoint.type === PolylinePointType.Intersection &&
193
- currentPoint.cloned) {
194
- currentPoint = currentPoint.next;
195
- continue;
196
- }
197
- subtractedPolyline.push(currentPoint.coordinates);
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');
206
- }
207
- subtractedPolylines.push(subtractedPolyline);
208
- }
209
- if (outerIterationCount >= maxOuterIterations) {
210
- console.warn('Maximum outer iterations reached in subtractPolylines, possible infinite loop detected');
211
- }
212
- return subtractedPolylines;
213
- }
214
- export { mergePolylines, subtractPolylines };
184
+ export { mergePolylines };
@@ -7,7 +7,9 @@ import getSignedArea from './getSignedArea';
7
7
  import getWindingDirection from './getWindingDirection';
8
8
  import getNormal3 from './getNormal3';
9
9
  import getNormal2 from './getNormal2';
10
- import { mergePolylines, subtractPolylines } from './combinePolyline';
10
+ import subtractPolylines from './subtractPolylines';
11
+ import intersectPolylines from './intersectPolylines';
12
+ import { mergePolylines } from './combinePolyline';
11
13
  import intersectPolyline from './intersectPolyline';
12
14
  import decimate from './decimate';
13
15
  import getFirstLineSegmentIntersectionIndexes from './getFirstLineSegmentIntersectionIndexes';
@@ -21,4 +23,5 @@ import pointCanProjectOnLine from './pointCanProjectOnLine';
21
23
  import { isPointInsidePolyline3D } from './isPointInsidePolyline3D';
22
24
  import { projectTo2D } from './projectTo2D';
23
25
  import convexHull from './convexHull';
24
- export { isClosed, containsPoint, containsPoints, getAABB, getArea, getSignedArea, getWindingDirection, getNormal3, getNormal2, intersectPolyline, decimate, getFirstLineSegmentIntersectionIndexes, getLineSegmentIntersectionsIndexes, getLineSegmentIntersectionsCoordinates, getClosestLineSegmentIntersection, getSubPixelSpacingAndXYDirections, pointsAreWithinCloseContourProximity, addCanvasPointsToArray, pointCanProjectOnLine, mergePolylines, subtractPolylines, isPointInsidePolyline3D, projectTo2D, convexHull, };
26
+ import arePolylinesIdentical from './arePolylinesIdentical';
27
+ export { isClosed, containsPoint, containsPoints, getAABB, getArea, getSignedArea, getWindingDirection, getNormal3, getNormal2, intersectPolyline, decimate, getFirstLineSegmentIntersectionIndexes, getLineSegmentIntersectionsIndexes, getLineSegmentIntersectionsCoordinates, getClosestLineSegmentIntersection, getSubPixelSpacingAndXYDirections, pointsAreWithinCloseContourProximity, addCanvasPointsToArray, pointCanProjectOnLine, mergePolylines, subtractPolylines, intersectPolylines, isPointInsidePolyline3D, projectTo2D, convexHull, arePolylinesIdentical, };
@@ -7,7 +7,9 @@ import getSignedArea from './getSignedArea';
7
7
  import getWindingDirection from './getWindingDirection';
8
8
  import getNormal3 from './getNormal3';
9
9
  import getNormal2 from './getNormal2';
10
- import { mergePolylines, subtractPolylines } from './combinePolyline';
10
+ import subtractPolylines from './subtractPolylines';
11
+ import intersectPolylines from './intersectPolylines';
12
+ import { mergePolylines } from './combinePolyline';
11
13
  import intersectPolyline from './intersectPolyline';
12
14
  import decimate from './decimate';
13
15
  import getFirstLineSegmentIntersectionIndexes from './getFirstLineSegmentIntersectionIndexes';
@@ -21,4 +23,5 @@ import pointCanProjectOnLine from './pointCanProjectOnLine';
21
23
  import { isPointInsidePolyline3D } from './isPointInsidePolyline3D';
22
24
  import { projectTo2D } from './projectTo2D';
23
25
  import convexHull from './convexHull';
24
- export { isClosed, containsPoint, containsPoints, getAABB, getArea, getSignedArea, getWindingDirection, getNormal3, getNormal2, intersectPolyline, decimate, getFirstLineSegmentIntersectionIndexes, getLineSegmentIntersectionsIndexes, getLineSegmentIntersectionsCoordinates, getClosestLineSegmentIntersection, getSubPixelSpacingAndXYDirections, pointsAreWithinCloseContourProximity, addCanvasPointsToArray, pointCanProjectOnLine, mergePolylines, subtractPolylines, isPointInsidePolyline3D, projectTo2D, convexHull, };
26
+ import arePolylinesIdentical from './arePolylinesIdentical';
27
+ export { isClosed, containsPoint, containsPoints, getAABB, getArea, getSignedArea, getWindingDirection, getNormal3, getNormal2, intersectPolyline, decimate, getFirstLineSegmentIntersectionIndexes, getLineSegmentIntersectionsIndexes, getLineSegmentIntersectionsCoordinates, getClosestLineSegmentIntersection, getSubPixelSpacingAndXYDirections, pointsAreWithinCloseContourProximity, addCanvasPointsToArray, pointCanProjectOnLine, mergePolylines, subtractPolylines, intersectPolylines, isPointInsidePolyline3D, projectTo2D, convexHull, arePolylinesIdentical, };
@@ -0,0 +1,2 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ export default function intersectPolylines(mainPolyCoords: Types.Point2[], clipPolyCoordsInput: Types.Point2[]): Types.Point2[][];
@@ -0,0 +1,271 @@
1
+ import { vec2 } from 'gl-matrix';
2
+ import containsPoint from './containsPoint';
3
+ import getSignedArea from './getSignedArea';
4
+ import { EPSILON, IntersectionDirection, pointsAreEqual, PolylineNodeType, robustSegmentIntersection, } from './robustSegmentIntersection';
5
+ export default function intersectPolylines(mainPolyCoords, clipPolyCoordsInput) {
6
+ if (mainPolyCoords.length < 3 || clipPolyCoordsInput.length < 3) {
7
+ return [];
8
+ }
9
+ let clipPolyCoords = clipPolyCoordsInput.slice();
10
+ const mainArea = getSignedArea(mainPolyCoords);
11
+ const clipArea = getSignedArea(clipPolyCoords);
12
+ if (Math.abs(mainArea) < EPSILON || Math.abs(clipArea) < EPSILON) {
13
+ return [];
14
+ }
15
+ if (mainArea < 0) {
16
+ mainPolyCoords = mainPolyCoords.slice().reverse();
17
+ }
18
+ if (clipArea < 0) {
19
+ clipPolyCoords = clipPolyCoords.slice().reverse();
20
+ }
21
+ const currentClipPolyForPIP = clipPolyCoords;
22
+ const intersections = [];
23
+ for (let i = 0; i < mainPolyCoords.length; i++) {
24
+ const p1 = mainPolyCoords[i];
25
+ const p2 = mainPolyCoords[(i + 1) % mainPolyCoords.length];
26
+ for (let j = 0; j < clipPolyCoords.length; j++) {
27
+ const q1 = clipPolyCoords[j];
28
+ const q2 = clipPolyCoords[(j + 1) % clipPolyCoords.length];
29
+ const intersectPt = robustSegmentIntersection(p1, p2, q1, q2);
30
+ if (intersectPt) {
31
+ const lenP = Math.sqrt(vec2.squaredDistance(p1, p2));
32
+ const lenQ = Math.sqrt(vec2.squaredDistance(q1, q2));
33
+ intersections.push({
34
+ coord: [...intersectPt],
35
+ seg1Idx: i,
36
+ seg2Idx: j,
37
+ alpha1: lenP < EPSILON
38
+ ? 0
39
+ : Math.sqrt(vec2.squaredDistance(p1, intersectPt)) / lenP,
40
+ alpha2: lenQ < EPSILON
41
+ ? 0
42
+ : Math.sqrt(vec2.squaredDistance(q1, intersectPt)) / lenQ,
43
+ });
44
+ }
45
+ }
46
+ }
47
+ if (intersections.length === 0) {
48
+ if (containsPoint(currentClipPolyForPIP, mainPolyCoords[0]) &&
49
+ mainPolyCoords.every((pt) => containsPoint(currentClipPolyForPIP, pt))) {
50
+ return [[...mainPolyCoords.map((p) => [...p])]];
51
+ }
52
+ if (containsPoint(mainPolyCoords, clipPolyCoords[0]) &&
53
+ clipPolyCoords.every((pt) => containsPoint(mainPolyCoords, pt))) {
54
+ return [[...clipPolyCoords.map((p) => [...p])]];
55
+ }
56
+ return [];
57
+ }
58
+ const buildAugmentedList = (polyCoords, polyIndex, allIntersections) => {
59
+ const augmentedList = [];
60
+ let nodeIdCounter = 0;
61
+ for (let i = 0; i < polyCoords.length; i++) {
62
+ const p1 = polyCoords[i];
63
+ augmentedList.push({
64
+ id: `${polyIndex}_v${nodeIdCounter++}`,
65
+ coordinates: [...p1],
66
+ type: PolylineNodeType.Vertex,
67
+ originalPolyIndex: polyIndex,
68
+ originalVertexIndex: i,
69
+ next: null,
70
+ prev: null,
71
+ isIntersection: false,
72
+ visited: false,
73
+ processedInPath: false,
74
+ intersectionDir: IntersectionDirection.Unknown,
75
+ });
76
+ const segmentIntersections = allIntersections
77
+ .filter((isect) => (polyIndex === 0 ? isect.seg1Idx : isect.seg2Idx) === i)
78
+ .sort((a, b) => (polyIndex === 0 ? a.alpha1 : a.alpha2) -
79
+ (polyIndex === 0 ? b.alpha1 : b.alpha2));
80
+ for (const isect of segmentIntersections) {
81
+ if (augmentedList.length > 0 &&
82
+ pointsAreEqual(augmentedList[augmentedList.length - 1].coordinates, isect.coord)) {
83
+ const lastNode = augmentedList[augmentedList.length - 1];
84
+ if (!lastNode.isIntersection) {
85
+ lastNode.isIntersection = true;
86
+ lastNode.intersectionInfo = isect;
87
+ lastNode.alpha = polyIndex === 0 ? isect.alpha1 : isect.alpha2;
88
+ lastNode.type = PolylineNodeType.Intersection;
89
+ }
90
+ continue;
91
+ }
92
+ augmentedList.push({
93
+ id: `${polyIndex}_i${nodeIdCounter++}`,
94
+ coordinates: [...isect.coord],
95
+ type: PolylineNodeType.Intersection,
96
+ originalPolyIndex: polyIndex,
97
+ next: null,
98
+ prev: null,
99
+ isIntersection: true,
100
+ visited: false,
101
+ processedInPath: false,
102
+ alpha: polyIndex === 0 ? isect.alpha1 : isect.alpha2,
103
+ intersectionInfo: isect,
104
+ intersectionDir: IntersectionDirection.Unknown,
105
+ });
106
+ }
107
+ }
108
+ const finalList = [];
109
+ if (augmentedList.length > 0) {
110
+ finalList.push(augmentedList[0]);
111
+ for (let i = 1; i < augmentedList.length; i++) {
112
+ if (!pointsAreEqual(augmentedList[i].coordinates, finalList[finalList.length - 1].coordinates)) {
113
+ finalList.push(augmentedList[i]);
114
+ }
115
+ else {
116
+ const lastNodeInFinal = finalList[finalList.length - 1];
117
+ if (augmentedList[i].isIntersection &&
118
+ augmentedList[i].intersectionInfo) {
119
+ lastNodeInFinal.isIntersection = true;
120
+ lastNodeInFinal.intersectionInfo =
121
+ augmentedList[i].intersectionInfo;
122
+ lastNodeInFinal.alpha = augmentedList[i].alpha;
123
+ lastNodeInFinal.type = PolylineNodeType.Intersection;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (finalList.length > 1 &&
129
+ pointsAreEqual(finalList[0].coordinates, finalList[finalList.length - 1].coordinates)) {
130
+ const firstNode = finalList[0];
131
+ const lastNodePopped = finalList.pop();
132
+ if (lastNodePopped.isIntersection &&
133
+ !firstNode.isIntersection &&
134
+ lastNodePopped.intersectionInfo) {
135
+ firstNode.isIntersection = true;
136
+ firstNode.intersectionInfo = lastNodePopped.intersectionInfo;
137
+ firstNode.alpha = lastNodePopped.alpha;
138
+ firstNode.type = PolylineNodeType.Intersection;
139
+ }
140
+ }
141
+ if (finalList.length > 0) {
142
+ for (let i = 0; i < finalList.length; i++) {
143
+ finalList[i].next = finalList[(i + 1) % finalList.length];
144
+ finalList[i].prev =
145
+ finalList[(i - 1 + finalList.length) % finalList.length];
146
+ }
147
+ }
148
+ return finalList;
149
+ };
150
+ const mainAugmented = buildAugmentedList(mainPolyCoords, 0, intersections);
151
+ const clipAugmented = buildAugmentedList(clipPolyCoords, 1, intersections);
152
+ if (mainAugmented.length === 0 || clipAugmented.length === 0) {
153
+ return [];
154
+ }
155
+ mainAugmented.forEach((mainNode) => {
156
+ if (mainNode.isIntersection && mainNode.intersectionInfo) {
157
+ const mainIntersectData = mainNode.intersectionInfo;
158
+ const partnerNode = clipAugmented.find((clipNode) => clipNode.isIntersection &&
159
+ clipNode.intersectionInfo &&
160
+ pointsAreEqual(clipNode.coordinates, mainNode.coordinates) &&
161
+ clipNode.intersectionInfo.seg1Idx === mainIntersectData.seg1Idx &&
162
+ clipNode.intersectionInfo.seg2Idx === mainIntersectData.seg2Idx);
163
+ if (partnerNode) {
164
+ mainNode.partnerNode = partnerNode;
165
+ partnerNode.partnerNode = mainNode;
166
+ const v_arrival_main = vec2.subtract(vec2.create(), mainNode.coordinates, mainNode.prev.coordinates);
167
+ const v_departure_clip = vec2.subtract(vec2.create(), partnerNode.next.coordinates, partnerNode.coordinates);
168
+ const crossZ = v_arrival_main[0] * v_departure_clip[1] -
169
+ v_arrival_main[1] * v_departure_clip[0];
170
+ if (crossZ > EPSILON) {
171
+ mainNode.intersectionDir = IntersectionDirection.Entering;
172
+ partnerNode.intersectionDir = IntersectionDirection.Exiting;
173
+ }
174
+ else if (crossZ < -EPSILON) {
175
+ mainNode.intersectionDir = IntersectionDirection.Exiting;
176
+ partnerNode.intersectionDir = IntersectionDirection.Entering;
177
+ }
178
+ else {
179
+ const midPrevMainSeg = [
180
+ (mainNode.prev.coordinates[0] + mainNode.coordinates[0]) / 2,
181
+ (mainNode.prev.coordinates[1] + mainNode.coordinates[1]) / 2,
182
+ ];
183
+ if (containsPoint(currentClipPolyForPIP, midPrevMainSeg)) {
184
+ mainNode.intersectionDir = IntersectionDirection.Exiting;
185
+ partnerNode.intersectionDir = IntersectionDirection.Entering;
186
+ }
187
+ else {
188
+ mainNode.intersectionDir = IntersectionDirection.Entering;
189
+ partnerNode.intersectionDir = IntersectionDirection.Exiting;
190
+ }
191
+ }
192
+ }
193
+ else {
194
+ mainNode.isIntersection = false;
195
+ mainNode.intersectionInfo = undefined;
196
+ }
197
+ }
198
+ });
199
+ const resultPolygons = [];
200
+ for (const startCand of mainAugmented) {
201
+ if (!startCand.isIntersection ||
202
+ startCand.visited ||
203
+ startCand.intersectionDir !== IntersectionDirection.Entering) {
204
+ continue;
205
+ }
206
+ let currentPathCoords = [];
207
+ let currentNode = startCand;
208
+ let onMainList = true;
209
+ const pathStartNode = startCand;
210
+ let safetyBreak = 0;
211
+ const maxIter = (mainAugmented.length + clipAugmented.length) * 2;
212
+ mainAugmented.forEach((n) => (n.processedInPath = false));
213
+ clipAugmented.forEach((n) => (n.processedInPath = false));
214
+ do {
215
+ if (safetyBreak++ > maxIter) {
216
+ console.warn('Intersection: Max iterations in path tracing.', pathStartNode.id, currentNode.id);
217
+ currentPathCoords = [];
218
+ break;
219
+ }
220
+ if (currentNode.processedInPath && currentNode !== pathStartNode) {
221
+ console.warn('Intersection: Path processing loop detected, discarding path segment.', pathStartNode.id, currentNode.id);
222
+ currentPathCoords = [];
223
+ break;
224
+ }
225
+ currentNode.processedInPath = true;
226
+ currentNode.visited = true;
227
+ if (currentPathCoords.length === 0 ||
228
+ !pointsAreEqual(currentPathCoords[currentPathCoords.length - 1], currentNode.coordinates)) {
229
+ currentPathCoords.push([...currentNode.coordinates]);
230
+ }
231
+ let switchedList = false;
232
+ if (currentNode.isIntersection && currentNode.partnerNode) {
233
+ if (onMainList) {
234
+ currentNode = currentNode.partnerNode;
235
+ onMainList = false;
236
+ switchedList = true;
237
+ }
238
+ else {
239
+ currentNode = currentNode.partnerNode;
240
+ onMainList = true;
241
+ switchedList = true;
242
+ }
243
+ }
244
+ if (!switchedList) {
245
+ currentNode = currentNode.next;
246
+ }
247
+ else {
248
+ currentNode = currentNode.next;
249
+ }
250
+ } while (currentNode !== pathStartNode ||
251
+ (onMainList && currentNode.originalPolyIndex !== 0) ||
252
+ (!onMainList && currentNode.originalPolyIndex !== 1));
253
+ if (safetyBreak > maxIter || currentPathCoords.length === 0) {
254
+ }
255
+ else if (currentPathCoords.length > 0 &&
256
+ pointsAreEqual(currentPathCoords[0], currentPathCoords[currentPathCoords.length - 1])) {
257
+ currentPathCoords.pop();
258
+ }
259
+ if (currentPathCoords.length >= 3) {
260
+ const resultArea = getSignedArea(currentPathCoords);
261
+ if (mainArea > 0 && resultArea < 0) {
262
+ currentPathCoords.reverse();
263
+ }
264
+ else if (mainArea < 0 && resultArea > 0) {
265
+ currentPathCoords.reverse();
266
+ }
267
+ resultPolygons.push(currentPathCoords.map((p) => [...p]));
268
+ }
269
+ }
270
+ return resultPolygons;
271
+ }
@@ -0,0 +1,37 @@
1
+ import { type Types } from '@cornerstonejs/core';
2
+ export declare const EPSILON = 1e-7;
3
+ export declare function vec2CrossZ(a: Types.Point2, b: Types.Point2): number;
4
+ export declare function pointsAreEqual(p1: Types.Point2, p2: Types.Point2): boolean;
5
+ export declare function robustSegmentIntersection(p1: Types.Point2, p2: Types.Point2, q1: Types.Point2, q2: Types.Point2): Types.Point2 | null;
6
+ export declare enum PolylineNodeType {
7
+ Vertex = 0,
8
+ Intersection = 1
9
+ }
10
+ export declare enum IntersectionDirection {
11
+ Entering = 0,
12
+ Exiting = 1,
13
+ Unknown = 2
14
+ }
15
+ export interface AugmentedPolyNode {
16
+ id: string;
17
+ coordinates: Types.Point2;
18
+ type: PolylineNodeType;
19
+ originalPolyIndex: 0 | 1;
20
+ originalVertexIndex?: number;
21
+ next: AugmentedPolyNode;
22
+ prev: AugmentedPolyNode;
23
+ isIntersection: boolean;
24
+ partnerNode?: AugmentedPolyNode;
25
+ intersectionDir?: IntersectionDirection;
26
+ intersectionInfo?: IntersectionInfo;
27
+ alpha?: number;
28
+ processedInPath?: boolean;
29
+ visited: boolean;
30
+ }
31
+ export type IntersectionInfo = {
32
+ coord: Types.Point2;
33
+ seg1Idx: number;
34
+ seg2Idx: number;
35
+ alpha1: number;
36
+ alpha2: number;
37
+ };
@@ -0,0 +1,78 @@
1
+ import { utilities } from '@cornerstonejs/core';
2
+ import { vec2 } from 'gl-matrix';
3
+ export const EPSILON = 1e-7;
4
+ export function vec2CrossZ(a, b) {
5
+ return a[0] * b[1] - a[1] * b[0];
6
+ }
7
+ export function pointsAreEqual(p1, p2) {
8
+ return utilities.isEqual(p1, p2, EPSILON);
9
+ }
10
+ export function robustSegmentIntersection(p1, p2, q1, q2) {
11
+ const r = vec2.subtract(vec2.create(), p2, p1);
12
+ const s = vec2.subtract(vec2.create(), q2, q1);
13
+ const rxs = vec2CrossZ(r, s);
14
+ const qmp = vec2.subtract(vec2.create(), q1, p1);
15
+ const qmpxr = vec2CrossZ(qmp, r);
16
+ if (Math.abs(rxs) < EPSILON) {
17
+ if (Math.abs(qmpxr) < EPSILON) {
18
+ const rDotR = vec2.dot(r, r);
19
+ const sDotS = vec2.dot(s, s);
20
+ if (rDotR < EPSILON || sDotS < EPSILON) {
21
+ if (pointsAreEqual(p1, q1) || pointsAreEqual(p1, q2)) {
22
+ return p1;
23
+ }
24
+ if (pointsAreEqual(p2, q1) || pointsAreEqual(p2, q2)) {
25
+ return p2;
26
+ }
27
+ return null;
28
+ }
29
+ const t0 = vec2.dot(vec2.subtract(vec2.create(), q1, p1), r) / rDotR;
30
+ const t1 = vec2.dot(vec2.subtract(vec2.create(), q2, p1), r) / rDotR;
31
+ const u0 = vec2.dot(vec2.subtract(vec2.create(), p1, q1), s) / sDotS;
32
+ const u1 = vec2.dot(vec2.subtract(vec2.create(), p2, q1), s) / sDotS;
33
+ const isInRange = (t) => t >= -EPSILON && t <= 1 + EPSILON;
34
+ if (isInRange(t0)) {
35
+ const projectedPoint = vec2.scaleAndAdd(vec2.create(), p1, r, t0);
36
+ if (pointsAreEqual(q1, projectedPoint)) {
37
+ return q1;
38
+ }
39
+ }
40
+ if (isInRange(t1)) {
41
+ const projectedPoint = vec2.scaleAndAdd(vec2.create(), p1, r, t1);
42
+ if (pointsAreEqual(q2, projectedPoint)) {
43
+ return q2;
44
+ }
45
+ }
46
+ if (isInRange(u0)) {
47
+ const projectedPoint = vec2.scaleAndAdd(vec2.create(), q1, s, u0);
48
+ if (pointsAreEqual(p1, projectedPoint)) {
49
+ return p1;
50
+ }
51
+ }
52
+ if (isInRange(u1)) {
53
+ const projectedPoint = vec2.scaleAndAdd(vec2.create(), q1, s, u1);
54
+ if (pointsAreEqual(p2, projectedPoint)) {
55
+ return p2;
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ const t = vec2CrossZ(qmp, s) / rxs;
62
+ const u = qmpxr / rxs;
63
+ if (t >= -EPSILON && t <= 1 + EPSILON && u >= -EPSILON && u <= 1 + EPSILON) {
64
+ return [p1[0] + t * r[0], p1[1] + t * r[1]];
65
+ }
66
+ return null;
67
+ }
68
+ export var PolylineNodeType;
69
+ (function (PolylineNodeType) {
70
+ PolylineNodeType[PolylineNodeType["Vertex"] = 0] = "Vertex";
71
+ PolylineNodeType[PolylineNodeType["Intersection"] = 1] = "Intersection";
72
+ })(PolylineNodeType || (PolylineNodeType = {}));
73
+ export var IntersectionDirection;
74
+ (function (IntersectionDirection) {
75
+ IntersectionDirection[IntersectionDirection["Entering"] = 0] = "Entering";
76
+ IntersectionDirection[IntersectionDirection["Exiting"] = 1] = "Exiting";
77
+ IntersectionDirection[IntersectionDirection["Unknown"] = 2] = "Unknown";
78
+ })(IntersectionDirection || (IntersectionDirection = {}));
@@ -0,0 +1,2 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ export default function subtractPolylines(targetPolylineCoords: Types.Point2[], sourcePolylineCoordsInput: Types.Point2[]): Types.Point2[][];