@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,75 +1,180 @@
1
- import { getAnnotation } from '../../stateManagement';
1
+ import { getAnnotation, removeAnnotation } from '../../stateManagement';
2
2
  import { convertContourPolylineToCanvasSpace, convertContourPolylineToWorld, } from './sharedOperations';
3
- import { subtractPolylineSets, unifyPolylineSets } from './unifyPolylineSets';
4
3
  import addPolylinesToSegmentation from './addPolylinesToSegmentation';
5
- function getPolylines(contourRepresentationData, segmentIndex) {
6
- const polylines = [];
7
- const { annotationUIDsMap } = contourRepresentationData;
4
+ import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
5
+ import { copyContourSegment } from './copyAnnotation';
6
+ import { removeContourSegmentationAnnotation } from './removeContourSegmentationAnnotation';
7
+ import { getViewportAssociatedToSegmentation } from '../../stateManagement/segmentation/utilities/getViewportAssociatedToSegmentation';
8
+ import { unifyPolylineSets } from './polylineUnify';
9
+ import { subtractPolylineSets } from './polylineSubtract';
10
+ import { intersectPolylinesSets } from './polylineIntersect';
11
+ import { xorPolylinesSets } from './polylineXor';
12
+ import { getViewReferenceFromAnnotation } from './getViewReferenceFromAnnotation';
13
+ export var LogicalOperation;
14
+ (function (LogicalOperation) {
15
+ LogicalOperation[LogicalOperation["Union"] = 0] = "Union";
16
+ LogicalOperation[LogicalOperation["Subtract"] = 1] = "Subtract";
17
+ LogicalOperation[LogicalOperation["Intersect"] = 2] = "Intersect";
18
+ LogicalOperation[LogicalOperation["XOR"] = 3] = "XOR";
19
+ LogicalOperation[LogicalOperation["Copy"] = 4] = "Copy";
20
+ LogicalOperation[LogicalOperation["Delete"] = 5] = "Delete";
21
+ })(LogicalOperation || (LogicalOperation = {}));
22
+ function getPolylinesInfoWorld(contourRepresentationData, segmentIndex) {
23
+ const polylinesInfo = [];
24
+ const { annotationUIDsMap } = contourRepresentationData || {};
25
+ if (!annotationUIDsMap?.has(segmentIndex)) {
26
+ return;
27
+ }
8
28
  const annotationUIDs = annotationUIDsMap.get(segmentIndex);
9
29
  for (const annotationUID of annotationUIDs) {
10
30
  const annotation = getAnnotation(annotationUID);
11
- const { polyline } = annotation.data
12
- .contour;
13
- polylines.push(polyline);
31
+ const { polyline } = annotation.data.contour;
32
+ polylinesInfo.push({
33
+ polyline,
34
+ viewReference: getViewReferenceFromAnnotation(annotation),
35
+ });
14
36
  }
15
- return polylines;
37
+ return polylinesInfo;
16
38
  }
17
- function extractPolylines(viewport, segmentation, segmentIndex1, segmentIndex2) {
18
- if (!segmentation) {
39
+ function extractPolylinesInCanvasSpace(viewport, segment1, segment2) {
40
+ const segmentation1 = getSegmentation(segment1.segmentationId);
41
+ const segmentation2 = getSegmentation(segment2.segmentationId);
42
+ if (!segmentation1 || !segmentation2) {
19
43
  return;
20
44
  }
21
- if (!segmentation.representationData.Contour) {
45
+ if (!segmentation1.representationData.Contour ||
46
+ !segmentation2.representationData.Contour) {
22
47
  return;
23
48
  }
24
- const contourRepresentationData = segmentation.representationData
25
- .Contour;
26
- const { annotationUIDsMap } = contourRepresentationData;
27
- if (!annotationUIDsMap) {
49
+ const polyLinesInfoWorld1 = getPolylinesInfoWorld(segmentation1.representationData.Contour, segment1.segmentIndex);
50
+ const polyLinesInfoWorld2 = getPolylinesInfoWorld(segmentation2.representationData.Contour, segment2.segmentIndex);
51
+ if (!polyLinesInfoWorld1 || !polyLinesInfoWorld2) {
28
52
  return;
29
53
  }
30
- if (!annotationUIDsMap.get(segmentIndex1)) {
54
+ const polyLinesInfoCanvas1 = polyLinesInfoWorld1.map(({ polyline, viewReference }) => {
55
+ return {
56
+ polyline: convertContourPolylineToCanvasSpace(polyline, viewport),
57
+ viewReference,
58
+ };
59
+ });
60
+ const polyLinesInfoCanvas2 = polyLinesInfoWorld2.map(({ polyline, viewReference }) => {
61
+ return {
62
+ polyline: convertContourPolylineToCanvasSpace(polyline, viewport),
63
+ viewReference,
64
+ };
65
+ });
66
+ return { polyLinesInfoCanvas1, polyLinesInfoCanvas2 };
67
+ }
68
+ function addSegmentInSegmentation(segmentation, { segmentIndex, label, color }) {
69
+ if (!segmentation?.segments) {
31
70
  return;
32
71
  }
33
- if (!annotationUIDsMap.get(segmentIndex2)) {
72
+ segmentation.segments[segmentIndex] = {
73
+ active: false,
74
+ locked: false,
75
+ label,
76
+ segmentIndex,
77
+ cachedStats: {},
78
+ color,
79
+ };
80
+ }
81
+ function removeAnnotations(annotationUIDList) {
82
+ annotationUIDList.forEach((annotationUID) => {
83
+ const annotation = getAnnotation(annotationUID);
84
+ removeAnnotation(annotationUID);
85
+ removeContourSegmentationAnnotation(annotation);
86
+ });
87
+ annotationUIDList.clear();
88
+ }
89
+ function applyLogicalOperation(segment1, segment2, options, operation) {
90
+ const viewport = getViewportAssociatedToSegmentation(segment1.segmentationId);
91
+ if (!viewport) {
34
92
  return;
35
93
  }
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) {
94
+ const { polyLinesInfoCanvas1, polyLinesInfoCanvas2 } = extractPolylinesInCanvasSpace(viewport, segment1, segment2) || {};
95
+ if (!polyLinesInfoCanvas1 || !polyLinesInfoCanvas2) {
46
96
  return;
47
97
  }
48
- const polylinesMerged = unifyPolylineSets(polyLinesCanvas1, polyLinesCanvas2);
49
- const polyLinesWorld = polylinesMerged.map((polyline) => convertContourPolylineToWorld(polyline, viewport));
50
- const annotationUIDsMapNew = addPolylinesToSegmentation(viewport, segmentation.segmentationId, polyLinesWorld, segmentIndex);
98
+ let polylinesMerged;
99
+ switch (operation) {
100
+ case LogicalOperation.Union:
101
+ polylinesMerged = unifyPolylineSets(polyLinesInfoCanvas1, polyLinesInfoCanvas2);
102
+ break;
103
+ case LogicalOperation.Subtract:
104
+ polylinesMerged = subtractPolylineSets(polyLinesInfoCanvas1, polyLinesInfoCanvas2);
105
+ break;
106
+ case LogicalOperation.Intersect:
107
+ polylinesMerged = intersectPolylinesSets(polyLinesInfoCanvas1, polyLinesInfoCanvas2);
108
+ break;
109
+ case LogicalOperation.XOR:
110
+ polylinesMerged = xorPolylinesSets(polyLinesInfoCanvas1, polyLinesInfoCanvas2);
111
+ break;
112
+ default:
113
+ polylinesMerged = unifyPolylineSets(polyLinesInfoCanvas1, polyLinesInfoCanvas2);
114
+ break;
115
+ }
116
+ const polyLinesWorld = polylinesMerged.map(({ polyline, viewReference }) => {
117
+ return {
118
+ polyline: convertContourPolylineToWorld(polyline, viewport),
119
+ viewReference,
120
+ };
121
+ });
122
+ const resultSegment = options;
123
+ const segmentation = getSegmentation(resultSegment.segmentationId);
124
+ const segmentIndex = resultSegment.segmentIndex;
125
+ const color = resultSegment.color;
126
+ const label = resultSegment.label;
51
127
  const contourRepresentationData = segmentation.representationData
52
128
  .Contour;
53
129
  const { annotationUIDsMap } = contourRepresentationData;
54
130
  if (!annotationUIDsMap) {
55
131
  return;
56
132
  }
57
- annotationUIDsMap.set(segmentIndex, annotationUIDsMapNew.get(segmentIndex));
133
+ if (segment1.segmentationId === resultSegment.segmentationId &&
134
+ segment1.segmentIndex === segmentIndex) {
135
+ const existingAnnotationUIDs = annotationUIDsMap.get(segmentIndex);
136
+ if (existingAnnotationUIDs) {
137
+ removeAnnotations(existingAnnotationUIDs);
138
+ }
139
+ }
140
+ addPolylinesToSegmentation(viewport, annotationUIDsMap, segmentation.segmentationId, polyLinesWorld, segmentIndex);
141
+ addSegmentInSegmentation(segmentation, { segmentIndex, color, label });
142
+ }
143
+ export function add(segment1, segment2, options) {
144
+ applyLogicalOperation(segment1, segment2, options, LogicalOperation.Union);
145
+ }
146
+ export function subtract(segment1, segment2, options) {
147
+ applyLogicalOperation(segment1, segment2, options, LogicalOperation.Subtract);
148
+ }
149
+ export function intersect(segment1, segment2, options) {
150
+ applyLogicalOperation(segment1, segment2, options, LogicalOperation.Intersect);
58
151
  }
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) {
152
+ export function xor(segment1, segment2, options) {
153
+ applyLogicalOperation(segment1, segment2, options, LogicalOperation.XOR);
154
+ }
155
+ export function copy(segment, options) {
156
+ copyContourSegment(segment.segmentationId, segment.segmentIndex, options.segmentationId, options.segmentIndex);
157
+ }
158
+ export function deleteOperation(segment) {
159
+ const segmentation = getSegmentation(segment.segmentationId);
160
+ if (!segmentation) {
161
+ console.log('No active segmentation detected');
63
162
  return;
64
163
  }
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;
164
+ if (!segmentation.representationData.Contour) {
165
+ console.log('No contour representation found');
166
+ return;
167
+ }
168
+ const representationData = segmentation.representationData.Contour;
169
+ const { annotationUIDsMap } = representationData;
71
170
  if (!annotationUIDsMap) {
171
+ console.log('No annotation map found');
172
+ return;
173
+ }
174
+ if (!annotationUIDsMap.has(segment.segmentIndex)) {
175
+ console.log('Segmentation index has no annotations');
72
176
  return;
73
177
  }
74
- annotationUIDsMap.set(segmentIndex, annotationUIDsMapNew.get(segmentIndex));
178
+ const annotationUIDList = annotationUIDsMap.get(segment.segmentIndex);
179
+ removeAnnotations(annotationUIDList);
75
180
  }
@@ -0,0 +1,9 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ export type PolylineInfoWorld = {
3
+ polyline: Types.Point3[];
4
+ viewReference: Types.ViewReference;
5
+ };
6
+ export type PolylineInfoCanvas = {
7
+ polyline: Types.Point2[];
8
+ viewReference: Types.ViewReference;
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { PolylineInfoCanvas } from './polylineInfoTypes';
2
+ export declare function intersectPolylinesSets(set1: PolylineInfoCanvas[], set2: PolylineInfoCanvas[]): PolylineInfoCanvas[];
@@ -0,0 +1,34 @@
1
+ import { checkIntersection, cleanupPolylines } from './sharedOperations';
2
+ import { intersectPolylines } from '../math/polyline';
3
+ import arePolylinesIdentical from '../math/polyline/arePolylinesIdentical';
4
+ import { areViewReferencesEqual } from './areViewReferencesEqual';
5
+ export function intersectPolylinesSets(set1, set2) {
6
+ if (!set1.length || !set2.length) {
7
+ return [];
8
+ }
9
+ const result = [];
10
+ for (const polyA of set1) {
11
+ for (const polyB of set2) {
12
+ if (!areViewReferencesEqual(polyA.viewReference, polyB.viewReference)) {
13
+ continue;
14
+ }
15
+ if (arePolylinesIdentical(polyA.polyline, polyB.polyline)) {
16
+ result.push({ ...polyA });
17
+ continue;
18
+ }
19
+ const intersection = checkIntersection(polyA.polyline, polyB.polyline);
20
+ if (intersection.hasIntersection && !intersection.isContourHole) {
21
+ const intersectionRegions = cleanupPolylines(intersectPolylines(polyA.polyline, polyB.polyline));
22
+ if (intersectionRegions && intersectionRegions.length > 0) {
23
+ intersectionRegions.forEach((region) => {
24
+ result.push({
25
+ polyline: region,
26
+ viewReference: polyA.viewReference,
27
+ });
28
+ });
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return result;
34
+ }
@@ -0,0 +1,6 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import type { PolylineInfoCanvas } from './polylineInfoTypes';
3
+ import type { ContourSegmentationAnnotation } from '../../types';
4
+ export declare function subtractPolylineSets(polylinesSetA: PolylineInfoCanvas[], polylinesSetB: PolylineInfoCanvas[]): PolylineInfoCanvas[];
5
+ export declare function subtractMultiplePolylineSets(basePolylineSet: PolylineInfoCanvas[], subtractorSets: PolylineInfoCanvas[][]): PolylineInfoCanvas[];
6
+ export declare function subtractAnnotationPolylines(baseAnnotations: ContourSegmentationAnnotation[], subtractorAnnotations: ContourSegmentationAnnotation[], viewport: Types.IViewport): PolylineInfoCanvas[];
@@ -0,0 +1,67 @@
1
+ import * as math from '../math';
2
+ import { checkIntersection, cleanupPolylines, convertContourPolylineToCanvasSpace, removeDuplicatePoints, } from './sharedOperations';
3
+ import arePolylinesIdentical from '../math/polyline/arePolylinesIdentical';
4
+ import { getViewReferenceFromAnnotation } from './getViewReferenceFromAnnotation';
5
+ import { areViewReferencesEqual } from './areViewReferencesEqual';
6
+ export function subtractPolylineSets(polylinesSetA, polylinesSetB) {
7
+ const result = [];
8
+ for (let i = 0; i < polylinesSetA.length; i++) {
9
+ let currentPolylines = [polylinesSetA[i]];
10
+ for (let j = 0; j < polylinesSetB.length; j++) {
11
+ const polylineB = polylinesSetB[j];
12
+ const newPolylines = [];
13
+ for (const currentPolyline of currentPolylines) {
14
+ if (!areViewReferencesEqual(currentPolyline.viewReference, polylineB.viewReference)) {
15
+ newPolylines.push(currentPolyline);
16
+ continue;
17
+ }
18
+ if (arePolylinesIdentical(currentPolyline.polyline, polylineB.polyline)) {
19
+ continue;
20
+ }
21
+ const intersection = checkIntersection(currentPolyline.polyline, polylineB.polyline);
22
+ if (intersection.hasIntersection && !intersection.isContourHole) {
23
+ const subtractedPolylines = cleanupPolylines(math.polyline.subtractPolylines(currentPolyline.polyline, polylineB.polyline));
24
+ for (const subtractedPolyline of subtractedPolylines) {
25
+ const cleaned = removeDuplicatePoints(subtractedPolyline);
26
+ if (cleaned.length >= 3) {
27
+ newPolylines.push({
28
+ polyline: cleaned,
29
+ viewReference: currentPolyline.viewReference,
30
+ });
31
+ }
32
+ }
33
+ }
34
+ else {
35
+ newPolylines.push({
36
+ polyline: currentPolyline.polyline,
37
+ viewReference: currentPolyline.viewReference,
38
+ });
39
+ }
40
+ }
41
+ currentPolylines = newPolylines;
42
+ }
43
+ result.push(...currentPolylines);
44
+ }
45
+ return result;
46
+ }
47
+ export function subtractMultiplePolylineSets(basePolylineSet, subtractorSets) {
48
+ if (subtractorSets.length === 0) {
49
+ return [...basePolylineSet];
50
+ }
51
+ let result = [...basePolylineSet];
52
+ for (let i = 0; i < subtractorSets.length; i++) {
53
+ result = subtractPolylineSets(result, subtractorSets[i]);
54
+ }
55
+ return result;
56
+ }
57
+ export function subtractAnnotationPolylines(baseAnnotations, subtractorAnnotations, viewport) {
58
+ const basePolylines = baseAnnotations.map((annotation) => ({
59
+ polyline: convertContourPolylineToCanvasSpace(annotation.data.contour.polyline, viewport),
60
+ viewReference: getViewReferenceFromAnnotation(annotation),
61
+ }));
62
+ const subtractorPolylines = subtractorAnnotations.map((annotation) => ({
63
+ polyline: convertContourPolylineToCanvasSpace(annotation.data.contour.polyline, viewport),
64
+ viewReference: getViewReferenceFromAnnotation(annotation),
65
+ }));
66
+ return subtractPolylineSets(basePolylines, subtractorPolylines);
67
+ }
@@ -0,0 +1,6 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import type { PolylineInfoCanvas } from './polylineInfoTypes';
3
+ import type { ContourSegmentationAnnotation } from '../../types';
4
+ export declare function unifyPolylineSets(polylinesSetA: PolylineInfoCanvas[], polylinesSetB: PolylineInfoCanvas[]): PolylineInfoCanvas[];
5
+ export declare function unifyMultiplePolylineSets(polylineSets: PolylineInfoCanvas[][]): PolylineInfoCanvas[];
6
+ export declare function unifyAnnotationPolylines(annotationsSetA: ContourSegmentationAnnotation[], annotationsSetB: ContourSegmentationAnnotation[], viewport: Types.IViewport): PolylineInfoCanvas[];
@@ -0,0 +1,79 @@
1
+ import * as math from '../math';
2
+ import { checkIntersection, convertContourPolylineToCanvasSpace, } from './sharedOperations';
3
+ import arePolylinesIdentical from '../math/polyline/arePolylinesIdentical';
4
+ import { getViewReferenceFromAnnotation } from './getViewReferenceFromAnnotation';
5
+ import { areViewReferencesEqual } from './areViewReferencesEqual';
6
+ export function unifyPolylineSets(polylinesSetA, polylinesSetB) {
7
+ const result = [];
8
+ const processedFromA = new Set();
9
+ const processedFromB = new Set();
10
+ for (let i = 0; i < polylinesSetA.length; i++) {
11
+ if (processedFromA.has(i)) {
12
+ continue;
13
+ }
14
+ const polylineA = polylinesSetA[i];
15
+ let merged = false;
16
+ for (let j = 0; j < polylinesSetB.length; j++) {
17
+ if (processedFromB.has(j)) {
18
+ continue;
19
+ }
20
+ const polylineB = polylinesSetB[j];
21
+ if (!areViewReferencesEqual(polylineA.viewReference, polylineB.viewReference)) {
22
+ continue;
23
+ }
24
+ if (arePolylinesIdentical(polylineA.polyline, polylineB.polyline)) {
25
+ result.push(polylineA);
26
+ processedFromA.add(i);
27
+ processedFromB.add(j);
28
+ merged = true;
29
+ break;
30
+ }
31
+ const intersection = checkIntersection(polylineA.polyline, polylineB.polyline);
32
+ if (intersection.hasIntersection && !intersection.isContourHole) {
33
+ const mergedPolyline = math.polyline.mergePolylines(polylineA.polyline, polylineB.polyline);
34
+ result.push({
35
+ polyline: mergedPolyline,
36
+ viewReference: polylineA.viewReference,
37
+ });
38
+ processedFromA.add(i);
39
+ processedFromB.add(j);
40
+ merged = true;
41
+ break;
42
+ }
43
+ }
44
+ if (!merged) {
45
+ result.push(polylineA);
46
+ processedFromA.add(i);
47
+ }
48
+ }
49
+ for (let j = 0; j < polylinesSetB.length; j++) {
50
+ if (!processedFromB.has(j)) {
51
+ result.push(polylinesSetB[j]);
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ export function unifyMultiplePolylineSets(polylineSets) {
57
+ if (polylineSets.length === 0) {
58
+ return [];
59
+ }
60
+ if (polylineSets.length === 1) {
61
+ return [...polylineSets[0]];
62
+ }
63
+ let result = [...polylineSets[0]];
64
+ for (let i = 1; i < polylineSets.length; i++) {
65
+ result = unifyPolylineSets(result, polylineSets[i]);
66
+ }
67
+ return result;
68
+ }
69
+ export function unifyAnnotationPolylines(annotationsSetA, annotationsSetB, viewport) {
70
+ const polylinesSetA = annotationsSetA.map((annotation) => ({
71
+ polyline: convertContourPolylineToCanvasSpace(annotation.data.contour.polyline, viewport),
72
+ viewReference: getViewReferenceFromAnnotation(annotation),
73
+ }));
74
+ const polylinesSetB = annotationsSetB.map((annotation) => ({
75
+ polyline: convertContourPolylineToCanvasSpace(annotation.data.contour.polyline, viewport),
76
+ viewReference: getViewReferenceFromAnnotation(annotation),
77
+ }));
78
+ return unifyPolylineSets(polylinesSetA, polylinesSetB);
79
+ }
@@ -0,0 +1,2 @@
1
+ import type { PolylineInfoCanvas } from './polylineInfoTypes';
2
+ export declare function xorPolylinesSets(polylinesSetA: PolylineInfoCanvas[], polylinesSetB: PolylineInfoCanvas[]): PolylineInfoCanvas[];
@@ -0,0 +1,41 @@
1
+ import { cleanupPolylines } from './sharedOperations';
2
+ import arePolylinesIdentical from '../math/polyline/arePolylinesIdentical';
3
+ import { subtractPolylineSets } from './polylineSubtract';
4
+ import { areViewReferencesEqual } from './areViewReferencesEqual';
5
+ export function xorPolylinesSets(polylinesSetA, polylinesSetB) {
6
+ if (!polylinesSetA.length && !polylinesSetB.length) {
7
+ return [];
8
+ }
9
+ if (!polylinesSetA.length) {
10
+ return polylinesSetB;
11
+ }
12
+ if (!polylinesSetB.length) {
13
+ return polylinesSetA;
14
+ }
15
+ if (polylinesSetA.length === polylinesSetB.length) {
16
+ let allIdentical = true;
17
+ for (let i = 0; i < polylinesSetA.length; i++) {
18
+ let foundMatch = false;
19
+ for (let j = 0; j < polylinesSetB.length; j++) {
20
+ if (!areViewReferencesEqual(polylinesSetA[i].viewReference, polylinesSetB[j].viewReference)) {
21
+ continue;
22
+ }
23
+ if (arePolylinesIdentical(polylinesSetA[i].polyline, polylinesSetB[j].polyline)) {
24
+ foundMatch = true;
25
+ break;
26
+ }
27
+ }
28
+ if (!foundMatch) {
29
+ allIdentical = false;
30
+ break;
31
+ }
32
+ }
33
+ if (allIdentical) {
34
+ return [];
35
+ }
36
+ }
37
+ const aMinusB = subtractPolylineSets(polylinesSetA, polylinesSetB);
38
+ const bMinusA = subtractPolylineSets(polylinesSetB, polylinesSetA);
39
+ const xorResult = [...aMinusB, ...bMinusA];
40
+ return xorResult;
41
+ }
@@ -14,3 +14,5 @@ export declare function createPolylineHole(viewport: Types.IViewport, targetAnno
14
14
  export declare function combinePolylines(viewport: Types.IViewport, targetAnnotation: ContourSegmentationAnnotation, targetPolyline: Types.Point2[], sourceAnnotation: ContourSegmentationAnnotation, sourcePolyline: Types.Point2[]): void;
15
15
  export declare function createNewAnnotationFromPolyline(viewport: Types.IViewport, templateAnnotation: ContourSegmentationAnnotation, polyline: Types.Point2[]): ContourSegmentationAnnotation;
16
16
  export declare function updateViewportsForAnnotations(viewport: Types.IViewport, annotations: ContourSegmentationAnnotation[]): void;
17
+ export declare function removeDuplicatePoints(polyline: Types.Point2[]): Types.Point2[];
18
+ export declare function cleanupPolylines(polylines: Types.Point2[][]): Types.Point2[][];
@@ -9,6 +9,7 @@ import { triggerAnnotationModified } from '../../stateManagement/annotation/help
9
9
  import triggerAnnotationRenderForViewportIds from '../triggerAnnotationRenderForViewportIds';
10
10
  import { getViewportIdsWithToolToRender } from '../viewportFilters';
11
11
  import { hasToolByName } from '../../store/addTool';
12
+ const TOLERANCE = 1e-10;
12
13
  const DEFAULT_CONTOUR_SEG_TOOL_NAME = 'PlanarFreehandContourSegmentationTool';
13
14
  export function convertContourPolylineToCanvasSpace(polyline, viewport) {
14
15
  const numPoints = polyline.length;
@@ -181,3 +182,46 @@ export function updateViewportsForAnnotations(viewport, annotations) {
181
182
  }
182
183
  }
183
184
  }
185
+ export function removeDuplicatePoints(polyline) {
186
+ if (!polyline || polyline.length < 2) {
187
+ return polyline;
188
+ }
189
+ const cleaned = [polyline[0]];
190
+ for (let i = 1; i < polyline.length; i++) {
191
+ const currentPoint = polyline[i];
192
+ const lastPoint = cleaned[cleaned.length - 1];
193
+ const dx = Math.abs(currentPoint[0] - lastPoint[0]);
194
+ const dy = Math.abs(currentPoint[1] - lastPoint[1]);
195
+ if (dx > TOLERANCE || dy > TOLERANCE) {
196
+ cleaned.push(currentPoint);
197
+ }
198
+ }
199
+ return cleaned;
200
+ }
201
+ export function cleanupPolylines(polylines) {
202
+ const validPolylines = [];
203
+ const seenPolylines = new Set();
204
+ for (let polyline of polylines) {
205
+ if (!polyline || polyline.length < 3) {
206
+ continue;
207
+ }
208
+ polyline = removeDuplicatePoints(polyline);
209
+ if (polyline.length < 3) {
210
+ continue;
211
+ }
212
+ const sortedPoints = [...polyline].sort((a, b) => {
213
+ if (a[0] !== b[0]) {
214
+ return a[0] - b[0];
215
+ }
216
+ return a[1] - b[1];
217
+ });
218
+ const polylineKey = sortedPoints
219
+ .map((p) => `${p[0].toFixed(6)},${p[1].toFixed(6)}`)
220
+ .join('|');
221
+ if (!seenPolylines.has(polylineKey)) {
222
+ seenPolylines.add(polylineKey);
223
+ validPolylines.push(polyline);
224
+ }
225
+ }
226
+ return validPolylines;
227
+ }
@@ -0,0 +1,2 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ export default function arePolylinesIdentical(poly1: Types.Point2[], poly2: Types.Point2[]): boolean;
@@ -0,0 +1,53 @@
1
+ import { pointsAreEqual } from './robustSegmentIntersection';
2
+ export default function arePolylinesIdentical(poly1, poly2) {
3
+ if (poly1.length !== poly2.length) {
4
+ return false;
5
+ }
6
+ const len = poly1.length;
7
+ if (len === 0) {
8
+ return true;
9
+ }
10
+ let identicalForward = true;
11
+ for (let i = 0; i < len; i++) {
12
+ if (!pointsAreEqual(poly1[i], poly2[i])) {
13
+ identicalForward = false;
14
+ break;
15
+ }
16
+ }
17
+ if (identicalForward) {
18
+ return true;
19
+ }
20
+ let identicalReverse = true;
21
+ for (let i = 0; i < len; i++) {
22
+ if (!pointsAreEqual(poly1[i], poly2[len - 1 - i])) {
23
+ identicalReverse = false;
24
+ break;
25
+ }
26
+ }
27
+ if (identicalReverse) {
28
+ return true;
29
+ }
30
+ for (let offset = 1; offset < len; offset++) {
31
+ let cyclicForward = true;
32
+ for (let i = 0; i < len; i++) {
33
+ if (!pointsAreEqual(poly1[i], poly2[(i + offset) % len])) {
34
+ cyclicForward = false;
35
+ break;
36
+ }
37
+ }
38
+ if (cyclicForward) {
39
+ return true;
40
+ }
41
+ let cyclicReverse = true;
42
+ for (let i = 0; i < len; i++) {
43
+ if (!pointsAreEqual(poly1[i], poly2[(len - 1 - i + offset) % len])) {
44
+ cyclicReverse = false;
45
+ break;
46
+ }
47
+ }
48
+ if (cyclicReverse) {
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
@@ -1,4 +1,3 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  declare function mergePolylines(targetPolyline: Types.Point2[], sourcePolyline: Types.Point2[]): Types.Point2[];
3
- declare function subtractPolylines(targetPolyline: Types.Point2[], sourcePolyline: Types.Point2[]): Types.Point2[][];
4
- export { mergePolylines, subtractPolylines };
3
+ export { mergePolylines };