@cornerstonejs/tools 4.12.3 → 4.12.4

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.
@@ -1,6 +1,5 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import type { ISculptToolShape } from '../../types/ISculptToolShape';
3
- import type { SculptData } from '../SculptorTool';
4
3
  import type { SVGDrawingHelper, EventTypes, ContourAnnotationData } from '../../types';
5
4
  export type PushedHandles = {
6
5
  first?: number;
@@ -8,19 +7,18 @@ export type PushedHandles = {
8
7
  };
9
8
  declare class CircleSculptCursor implements ISculptToolShape {
10
9
  static shapeName: string;
11
- static readonly CHAIN_MAINTENANCE_ITERATIONS = 3;
12
- static readonly CHAIN_PULL_STRENGTH_FACTOR = 0.3;
13
- static readonly MAX_INTER_DISTANCE_FACTOR = 1.2;
14
10
  private toolInfo;
15
11
  renderShape(svgDrawingHelper: SVGDrawingHelper, canvasLocation: Types.Point2, options: unknown): void;
16
- pushHandles(viewport: Types.IViewport, sculptData: SculptData): PushedHandles;
17
12
  configureToolSize(evt: EventTypes.InteractionEventType): void;
18
13
  updateToolSize(canvasCoords: Types.Point2, viewport: Types.IViewport, activeAnnotation: ContourAnnotationData): void;
19
14
  getMaxSpacing(minSpacing: number): number;
20
- getInsertPosition(previousIndex: number, nextIndex: number, sculptData: SculptData): Types.Point3;
21
- private pushOneHandle;
22
- private directionalVector;
23
- private calculateMeanConsecutiveDistance;
24
- private maintainChainStructure;
15
+ computeWorldRadius(viewport: any, clearExisting?: boolean): any;
16
+ getEdge(viewport: any, p1: Types.Point3, p2: Types.Point3, mouseCanvas: Types.Point2): {
17
+ point: any;
18
+ angle: number;
19
+ canvasPoint: any;
20
+ };
21
+ interpolatePoint(viewport: any, angle: any, center: any): any;
22
+ isInCursor(point: any, mousePoint: any): boolean;
25
23
  }
26
24
  export default CircleSculptCursor;
@@ -1,4 +1,4 @@
1
- import { vec3 } from 'gl-matrix';
1
+ import { vec2, vec3 } from 'gl-matrix';
2
2
  import { getEnabledElement } from '@cornerstonejs/core';
3
3
  import { distancePointToContour } from '../distancePointToContour';
4
4
  import { drawCircle as drawCircleSvg } from '../../drawingSvg';
@@ -7,46 +7,15 @@ class CircleSculptCursor {
7
7
  constructor() {
8
8
  this.toolInfo = {
9
9
  toolSize: null,
10
+ radius: null,
10
11
  maxToolSize: null,
11
12
  };
12
13
  }
13
14
  static { this.shapeName = 'Circle'; }
14
- static { this.CHAIN_MAINTENANCE_ITERATIONS = 3; }
15
- static { this.CHAIN_PULL_STRENGTH_FACTOR = 0.3; }
16
- static { this.MAX_INTER_DISTANCE_FACTOR = 1.2; }
17
15
  renderShape(svgDrawingHelper, canvasLocation, options) {
18
16
  const circleUID = '0';
19
17
  drawCircleSvg(svgDrawingHelper, 'SculptorTool', circleUID, canvasLocation, this.toolInfo.toolSize, options);
20
18
  }
21
- pushHandles(viewport, sculptData) {
22
- const { points, mouseCanvasPoint } = sculptData;
23
- const pushedHandles = { first: undefined, last: undefined };
24
- const worldRadius = point.distanceToPoint(viewport.canvasToWorld(mouseCanvasPoint), viewport.canvasToWorld([
25
- mouseCanvasPoint[0] + this.toolInfo.toolSize,
26
- mouseCanvasPoint[1],
27
- ]));
28
- for (let i = 0; i < points.length; i++) {
29
- const handleCanvasPoint = viewport.worldToCanvas(points[i]);
30
- const distanceToHandle = point.distanceToPoint(handleCanvasPoint, mouseCanvasPoint);
31
- if (distanceToHandle > this.toolInfo.toolSize) {
32
- continue;
33
- }
34
- this.pushOneHandle(i, worldRadius, sculptData);
35
- if (pushedHandles.first === undefined) {
36
- pushedHandles.first = i;
37
- pushedHandles.last = i;
38
- }
39
- else {
40
- pushedHandles.last = i;
41
- }
42
- }
43
- if (pushedHandles.first !== undefined && pushedHandles.last !== undefined) {
44
- for (let i = 0; i < CircleSculptCursor.CHAIN_MAINTENANCE_ITERATIONS; i++) {
45
- this.maintainChainStructure(sculptData, pushedHandles);
46
- }
47
- }
48
- return pushedHandles;
49
- }
50
19
  configureToolSize(evt) {
51
20
  const toolInfo = this.toolInfo;
52
21
  if (toolInfo.toolSize && toolInfo.maxToolSize) {
@@ -55,8 +24,9 @@ class CircleSculptCursor {
55
24
  const eventData = evt.detail;
56
25
  const element = eventData.element;
57
26
  const minDim = Math.min(element.clientWidth, element.clientHeight);
58
- const maxRadius = minDim / 12;
27
+ const maxRadius = minDim / 24;
59
28
  toolInfo.toolSize = maxRadius;
29
+ toolInfo.radius = null;
60
30
  toolInfo.maxToolSize = maxRadius;
61
31
  }
62
32
  updateToolSize(canvasCoords, viewport, activeAnnotation) {
@@ -64,114 +34,44 @@ class CircleSculptCursor {
64
34
  const radius = distancePointToContour(viewport, activeAnnotation, canvasCoords);
65
35
  if (radius > 0) {
66
36
  toolInfo.toolSize = Math.min(toolInfo.maxToolSize, radius);
37
+ this.computeWorldRadius(viewport, true);
67
38
  }
68
39
  }
69
40
  getMaxSpacing(minSpacing) {
70
41
  return Math.max(this.toolInfo.toolSize / 4, minSpacing);
71
42
  }
72
- getInsertPosition(previousIndex, nextIndex, sculptData) {
73
- let insertPosition;
74
- const { points, element, mouseCanvasPoint } = sculptData;
75
- const toolSize = this.toolInfo.toolSize;
76
- const enabledElement = getEnabledElement(element);
77
- const { viewport } = enabledElement;
78
- const previousCanvasPoint = viewport.worldToCanvas(points[previousIndex]);
79
- const nextCanvasPoint = viewport.worldToCanvas(points[nextIndex]);
80
- const midPoint = [
81
- (previousCanvasPoint[0] + nextCanvasPoint[0]) / 2.0,
82
- (previousCanvasPoint[1] + nextCanvasPoint[1]) / 2.0,
83
- ];
84
- const distanceToMidPoint = point.distanceToPoint(mouseCanvasPoint, midPoint);
85
- if (distanceToMidPoint < toolSize) {
86
- const directionUnitVector = {
87
- x: (midPoint[0] - mouseCanvasPoint[0]) / distanceToMidPoint,
88
- y: (midPoint[1] - mouseCanvasPoint[1]) / distanceToMidPoint,
89
- };
90
- insertPosition = [
91
- mouseCanvasPoint[0] + toolSize * directionUnitVector.x,
92
- mouseCanvasPoint[1] + toolSize * directionUnitVector.y,
93
- ];
43
+ computeWorldRadius(viewport, clearExisting = false) {
44
+ if (!this.toolInfo.radius || clearExisting) {
45
+ const p0 = viewport.canvasToWorld([0, 0]);
46
+ const p1 = viewport.canvasToWorld([this.toolInfo.toolSize, 0]);
47
+ this.toolInfo.radius = vec3.length(vec3.sub(vec3.create(), p0, p1));
94
48
  }
95
- else {
96
- insertPosition = midPoint;
97
- }
98
- const worldPosition = viewport.canvasToWorld(insertPosition);
99
- return worldPosition;
49
+ return this.toolInfo.radius;
100
50
  }
101
- pushOneHandle(i, worldRadius, sculptData) {
102
- const { points, mousePoint } = sculptData;
103
- const handle = points[i];
104
- const directionUnitVector = this.directionalVector(mousePoint, handle);
105
- const position = vec3.scaleAndAdd(vec3.create(), mousePoint, directionUnitVector, worldRadius);
106
- handle[0] = position[0];
107
- handle[1] = position[1];
108
- handle[2] = position[2];
51
+ getEdge(viewport, p1, p2, mouseCanvas) {
52
+ const midPoint = vec3.add(vec3.create(), p1, p2 || p1);
53
+ vec3.scale(midPoint, midPoint, 0.5);
54
+ const canvasMidPoint = viewport.worldToCanvas(midPoint);
55
+ const canvasDelta = vec2.sub(vec2.create(), canvasMidPoint, mouseCanvas);
56
+ const angle = Math.atan2(canvasDelta[1], canvasDelta[0]);
57
+ const point = this.interpolatePoint(viewport, angle, mouseCanvas);
58
+ const canvasPoint = viewport.worldToCanvas(point);
59
+ return {
60
+ point,
61
+ angle,
62
+ canvasPoint,
63
+ };
109
64
  }
110
- directionalVector(p1, p2) {
111
- return vec3.normalize(vec3.create(), [
112
- p2[0] - p1[0],
113
- p2[1] - p1[1],
114
- p2[2] - p1[2],
115
- ]);
65
+ interpolatePoint(viewport, angle, center) {
66
+ const [cx, cy] = center;
67
+ const r = this.toolInfo.toolSize;
68
+ const dx = Math.cos(angle) * r;
69
+ const dy = Math.sin(angle) * r;
70
+ const newPoint2 = [cx + dx, cy + dy];
71
+ return viewport.canvasToWorld(newPoint2);
116
72
  }
117
- calculateMeanConsecutiveDistance(points) {
118
- if (points.length < 2) {
119
- return 0;
120
- }
121
- let totalDistance = 0;
122
- const numPoints = points.length;
123
- for (let i = 0; i < numPoints; i++) {
124
- const nextIndex = (i + 1) % numPoints;
125
- const distance = point.distanceToPoint(points[i], points[nextIndex]);
126
- totalDistance += distance;
127
- }
128
- return totalDistance / numPoints;
129
- }
130
- maintainChainStructure(sculptData, pushedHandles) {
131
- const { points } = sculptData;
132
- const first = pushedHandles.first;
133
- const last = pushedHandles.last;
134
- const mean = Math.round((first + last) / 2);
135
- const numPoints = points.length;
136
- if (!sculptData.meanDistance) {
137
- sculptData.meanDistance = this.calculateMeanConsecutiveDistance(points);
138
- }
139
- const maxInterDistance = sculptData.meanDistance * CircleSculptCursor.MAX_INTER_DISTANCE_FACTOR;
140
- for (let i = mean; i >= 0; i--) {
141
- if (i >= numPoints - 1 || i < 0) {
142
- continue;
143
- }
144
- const nextIndex = i + 1;
145
- const distanceToNext = point.distanceToPoint(points[i], points[nextIndex]);
146
- if (distanceToNext > maxInterDistance) {
147
- const pullDirection = this.directionalVector(points[i], points[nextIndex]);
148
- const pullStrength = (distanceToNext - sculptData.meanDistance) / sculptData.meanDistance;
149
- const adjustmentMagnitude = pullStrength *
150
- sculptData.meanDistance *
151
- CircleSculptCursor.CHAIN_PULL_STRENGTH_FACTOR;
152
- points[i][0] += pullDirection[0] * adjustmentMagnitude;
153
- points[i][1] += pullDirection[1] * adjustmentMagnitude;
154
- points[i][2] += pullDirection[2] * adjustmentMagnitude;
155
- }
156
- }
157
- for (let i = mean + 1; i < numPoints; i++) {
158
- if (i >= numPoints || i <= 0) {
159
- continue;
160
- }
161
- const previousIndex = i - 1;
162
- const distanceToPrevious = point.distanceToPoint(points[i], points[previousIndex]);
163
- if (distanceToPrevious > maxInterDistance) {
164
- const pullDirection = this.directionalVector(points[i], points[previousIndex]);
165
- const pullStrength = (distanceToPrevious - sculptData.meanDistance) /
166
- sculptData.meanDistance;
167
- const adjustmentMagnitude = pullStrength *
168
- sculptData.meanDistance *
169
- CircleSculptCursor.CHAIN_PULL_STRENGTH_FACTOR;
170
- points[i][0] += pullDirection[0] * adjustmentMagnitude;
171
- points[i][1] += pullDirection[1] * adjustmentMagnitude;
172
- points[i][2] += pullDirection[2] * adjustmentMagnitude;
173
- }
174
- }
73
+ isInCursor(point, mousePoint) {
74
+ return vec3.distance(point, mousePoint) < this.toolInfo.radius;
175
75
  }
176
76
  }
177
77
  export default CircleSculptCursor;
@@ -2,15 +2,27 @@ import type { Types } from '@cornerstonejs/core';
2
2
  import { BaseTool } from './base';
3
3
  import type { EventTypes, PublicToolProps, ToolProps, SVGDrawingHelper } from '../types';
4
4
  import type { ISculptToolShape } from '../types/ISculptToolShape';
5
+ export type Contour = {
6
+ annotationUID: string;
7
+ points: Array<Types.Point3>;
8
+ };
5
9
  export type SculptData = {
6
10
  mousePoint: Types.Point3;
7
- deltaWorld: Types.Point3;
8
11
  mouseCanvasPoint: Types.Point2;
9
12
  points: Array<Types.Point3>;
10
13
  maxSpacing: number;
11
- meanDistance?: number;
12
14
  element: HTMLDivElement;
15
+ contours: Contour[];
16
+ };
17
+ export type SculptIntersect = {
18
+ annotationUID: string;
19
+ isEnter: boolean;
20
+ index: number;
21
+ relIndex?: number;
22
+ point: Types.Point3;
23
+ angle: number;
13
24
  };
25
+ export type ContourSelection = Array<SculptIntersect>;
14
26
  declare class SculptorTool extends BaseTool {
15
27
  static toolName: string;
16
28
  registeredShapes: Map<any, any>;
@@ -23,13 +35,16 @@ declare class SculptorTool extends BaseTool {
23
35
  preMouseDownCallback: (evt: EventTypes.InteractionEventType) => boolean;
24
36
  mouseMoveCallback: (evt: EventTypes.InteractionEventType) => void;
25
37
  protected sculpt(eventData: any, points: Array<Types.Point3>): void;
38
+ intersect(viewport: Types.IViewport, cursorShape: any): SculptIntersect[];
39
+ interpolatePoints(viewport: any, enter: any, exit: any, existing: any, newPoints: any): void;
40
+ getContourSelections(intersections: any, pointLength: any): ContourSelection[];
41
+ findMergeable(contours: any, testIntersection: any, currentIndex: any): any;
42
+ findNext(intersections: any, lastAngle: any, isEnter?: boolean): any;
26
43
  protected interpolatePointsWithinMaxSpacing(i: number, points: Array<Types.Point3>, indicesToInsertAfter: Array<number>, maxSpacing: number): void;
27
44
  private updateCursor;
45
+ protected getToolInstance(element: HTMLDivElement): any;
28
46
  private filterSculptableAnnotationsForElement;
29
47
  private configureToolSize;
30
- private insertNewHandles;
31
- private findNewHandleIndices;
32
- private insertHandleRadially;
33
48
  private selectFreehandTool;
34
49
  private getClosestFreehandToolOnElement;
35
50
  private endCallback;
@@ -1,6 +1,7 @@
1
- import { getEnabledElement } from '@cornerstonejs/core';
1
+ import { getEnabledElement, utilities } from '@cornerstonejs/core';
2
+ import { vec3 } from 'gl-matrix';
2
3
  import { BaseTool } from './base';
3
- import { getAnnotations } from '../stateManagement';
4
+ import { getAnnotations, getAnnotation } from '../stateManagement';
4
5
  import { point } from '../utilities/math';
5
6
  import { Events, ToolModes, AnnotationStyleStates, ChangeTypes, } from '../enums';
6
7
  import { triggerAnnotationRenderForViewportIds } from '../utilities/triggerAnnotationRenderForViewportIds';
@@ -10,6 +11,8 @@ import { triggerAnnotationModified } from '../stateManagement/annotation/helpers
10
11
  import CircleSculptCursor from './SculptorTool/CircleSculptCursor';
11
12
  import { distancePointToContour } from './distancePointToContour';
12
13
  import { getToolGroupForViewport } from '../store/ToolGroupManager';
14
+ import { getSignedArea, containsPoint } from '../utilities/math/polyline';
15
+ const { isEqual } = utilities;
13
16
  class SculptorTool extends BaseTool {
14
17
  constructor(toolProps = {}, defaultToolProps = {
15
18
  supportedInteractionTypes: ['Mouse', 'Touch'],
@@ -21,7 +24,6 @@ class SculptorTool extends BaseTool {
21
24
  ],
22
25
  toolShape: 'circle',
23
26
  referencedToolName: 'PlanarFreehandROI',
24
- updateCursorSize: 'dynamic',
25
27
  },
26
28
  }) {
27
29
  super(toolProps, defaultToolProps);
@@ -32,10 +34,12 @@ class SculptorTool extends BaseTool {
32
34
  viewportIdsToRender: [],
33
35
  isEditingOpenContour: false,
34
36
  canvasLocation: undefined,
37
+ external: true,
38
+ closed: false,
35
39
  };
36
40
  this.preMouseDownCallback = (evt) => {
37
41
  const eventData = evt.detail;
38
- const element = eventData.element;
42
+ const { element } = eventData;
39
43
  this.configureToolSize(evt);
40
44
  this.selectFreehandTool(eventData);
41
45
  if (this.commonData.activeAnnotationUID === null) {
@@ -58,14 +62,11 @@ class SculptorTool extends BaseTool {
58
62
  this.endCallback = (evt) => {
59
63
  const eventData = evt.detail;
60
64
  const { element } = eventData;
61
- const config = this.configuration;
62
- const enabledElement = getEnabledElement(element);
63
65
  this.isActive = false;
64
66
  this.deactivateModify(element);
65
67
  resetElementCursor(element);
66
- const { renderingEngineId, viewportId } = enabledElement;
67
- const toolGroup = getToolGroupForViewport(viewportId, renderingEngineId);
68
- const toolInstance = toolGroup.getToolInstance(config.referencedToolName);
68
+ const toolInstance = this.getToolInstance(element);
69
+ toolInstance.doneEditMemo?.();
69
70
  const annotations = this.filterSculptableAnnotationsForElement(element);
70
71
  const activeAnnotation = annotations.find((annotation) => annotation.annotationUID === this.commonData.activeAnnotationUID);
71
72
  if (toolInstance.configuration.calculateStats) {
@@ -101,15 +102,209 @@ class SculptorTool extends BaseTool {
101
102
  this.sculptData = {
102
103
  mousePoint: eventData.currentPoints.world,
103
104
  mouseCanvasPoint: eventData.currentPoints.canvas,
104
- deltaWorld: eventData.deltaPoints.world,
105
105
  points,
106
106
  maxSpacing: cursorShape.getMaxSpacing(config.minSpacing),
107
107
  element: element,
108
+ contours: [
109
+ {
110
+ annotationUID: this.commonData.activeAnnotationUID,
111
+ points,
112
+ },
113
+ ],
108
114
  };
109
- const pushedHandles = cursorShape.pushHandles(viewport, this.sculptData);
110
- if (pushedHandles.first !== undefined) {
111
- this.insertNewHandles(pushedHandles);
115
+ const intersections = this.intersect(viewport, cursorShape);
116
+ if (!intersections.length) {
117
+ return;
118
+ }
119
+ const contourSelections = this.getContourSelections(intersections, points.length);
120
+ const { closed } = this.commonData;
121
+ for (const contour of contourSelections) {
122
+ const newPoints = new Array();
123
+ const lastExit = contour[contour.length - 1];
124
+ let lastIndex = closed ? lastExit.relIndex : 0;
125
+ let lastEnter;
126
+ for (const intersection of contour) {
127
+ if (intersection.isEnter) {
128
+ pushArr(newPoints, points, lastIndex, intersection.index);
129
+ lastEnter = intersection;
130
+ }
131
+ else {
132
+ this.interpolatePoints(viewport, lastEnter, intersection, points, newPoints);
133
+ }
134
+ lastIndex = intersection.index;
135
+ }
136
+ if (contourSelections.length > 1) {
137
+ const signedArea = getSignedArea(newPoints.map(viewport.worldToCanvas));
138
+ if (signedArea < 0) {
139
+ console.warn('Skipping internal area');
140
+ continue;
141
+ }
142
+ }
143
+ if (!closed && lastIndex < points.length - 1) {
144
+ pushArr(newPoints, points, lastIndex);
145
+ }
146
+ points.splice(0, points.length);
147
+ pushArr(points, newPoints);
148
+ return;
149
+ }
150
+ }
151
+ intersect(viewport, cursorShape) {
152
+ const { contours, mousePoint, mouseCanvasPoint } = this.sculptData;
153
+ const { closed } = this.commonData;
154
+ cursorShape.computeWorldRadius(viewport);
155
+ const result = new Array();
156
+ for (const contour of contours) {
157
+ const { annotationUID, points } = contour;
158
+ let lastIn = false;
159
+ let anyIn = false;
160
+ let anyOut = false;
161
+ const { length } = points;
162
+ for (let i = 0; i <= length; i++) {
163
+ const index = i % length;
164
+ const point = points[index];
165
+ const inCursor = cursorShape.isInCursor(point, mousePoint);
166
+ anyIn ||= inCursor;
167
+ anyOut ||= !inCursor;
168
+ if (i === 0) {
169
+ lastIn = inCursor;
170
+ if (!closed && inCursor) {
171
+ const edge = cursorShape.getEdge(viewport, point, null, mouseCanvasPoint);
172
+ result.push({
173
+ annotationUID,
174
+ isEnter: inCursor,
175
+ index: i,
176
+ point: edge.point,
177
+ angle: edge.angle,
178
+ });
179
+ }
180
+ continue;
181
+ }
182
+ if (index === 0 && !closed) {
183
+ if (lastIn) {
184
+ const edge = cursorShape.getEdge(viewport, points[length - 1], null, mouseCanvasPoint);
185
+ result.push({
186
+ annotationUID,
187
+ isEnter: false,
188
+ index: length - 1,
189
+ point: edge.point,
190
+ angle: edge.angle,
191
+ });
192
+ }
193
+ continue;
194
+ }
195
+ if (lastIn === inCursor) {
196
+ continue;
197
+ }
198
+ lastIn = inCursor;
199
+ const edge = cursorShape.getEdge(viewport, point, points[i - 1], mouseCanvasPoint);
200
+ result.push({
201
+ annotationUID,
202
+ isEnter: inCursor,
203
+ index: i,
204
+ point: edge.point,
205
+ angle: edge.angle,
206
+ });
207
+ }
208
+ }
209
+ return result;
210
+ }
211
+ interpolatePoints(viewport, enter, exit, existing, newPoints) {
212
+ const { external, closed } = this.commonData;
213
+ const p0 = existing[enter.index % existing.length];
214
+ const p1 = existing[exit.index % existing.length];
215
+ const v = vec3.sub(vec3.create(), p1, p0);
216
+ if (isEqual(vec3.length(v), 0)) {
217
+ return;
218
+ }
219
+ const cursorShape = this.registeredShapes.get(this.selectedShape);
220
+ const a0 = (enter.angle + 2 * Math.PI) % (Math.PI * 2);
221
+ const a1 = (exit.angle + 2 * Math.PI) % (Math.PI * 2);
222
+ let ae = a1 < a0 ? a1 + 2 * Math.PI : a1;
223
+ const aeAlt = a1 > a0 ? a1 - 2 * Math.PI : a1;
224
+ if ((external && !closed && Math.abs(aeAlt - a0) < Math.abs(ae - a0)) ||
225
+ (external && closed)) {
226
+ ae = aeAlt;
227
+ }
228
+ const count = Math.ceil(Math.abs(a0 - ae) / 0.25);
229
+ for (let i = 0; i <= count; i++) {
230
+ const a = (a0 * (count - i) + i * ae) / count;
231
+ newPoints.push(cursorShape.interpolatePoint(viewport, a, this.sculptData.mouseCanvasPoint));
232
+ }
233
+ }
234
+ getContourSelections(intersections, pointLength) {
235
+ const result = new Array();
236
+ const enterLength = intersections.length / 2;
237
+ if (!enterLength || intersections.length % 2) {
238
+ return result;
239
+ }
240
+ let lastAngle = Number.NEGATIVE_INFINITY;
241
+ for (let enterCount = 0; enterCount < enterLength; enterCount++) {
242
+ const enter = this.findNext(intersections, lastAngle);
243
+ if (!enter) {
244
+ console.error("Couldnt' find an entry");
245
+ continue;
246
+ }
247
+ const exit = this.findNext(intersections, enter.angle, false);
248
+ if (!exit) {
249
+ console.error("Couldn't find an exit for", enter);
250
+ continue;
251
+ }
252
+ exit.relIndex ||=
253
+ exit.index < enter.index ? exit.index + pointLength : exit.index;
254
+ result.push([enter, exit]);
255
+ }
256
+ result.sort((a, b) => a[0].index - b[0].index);
257
+ for (let i = 0; i < result.length - 1;) {
258
+ const testIntersection = result[i];
259
+ const mergeableResult = this.findMergeable(result, testIntersection, i);
260
+ if (mergeableResult) {
261
+ testIntersection.push(...mergeableResult);
262
+ }
263
+ else {
264
+ i++;
265
+ }
266
+ }
267
+ if (result.length > 1) {
268
+ console.warn('************* More than 1 result', result);
112
269
  }
270
+ return result;
271
+ }
272
+ findMergeable(contours, testIntersection, currentIndex) {
273
+ const end = testIntersection[testIntersection.length - 1];
274
+ for (let i = currentIndex + 1; i < contours.length; i++) {
275
+ const [enter] = contours[i];
276
+ if (enter.index >= end.relIndex) {
277
+ const contour = contours[i];
278
+ contours.splice(i, 1);
279
+ return contour;
280
+ }
281
+ }
282
+ }
283
+ findNext(intersections, lastAngle, isEnter = true) {
284
+ if (intersections.length === 1) {
285
+ const [intersection] = intersections;
286
+ intersections.splice(0, 1);
287
+ return intersection;
288
+ }
289
+ let foundItem;
290
+ let testAngle;
291
+ for (let i = 0; i < intersections.length; i++) {
292
+ const intersection = intersections[i];
293
+ if (intersection.isEnter == isEnter) {
294
+ const relativeAngle = (intersection.angle - lastAngle + 2 * Math.PI) % (2 * Math.PI);
295
+ if (!foundItem || relativeAngle < testAngle) {
296
+ foundItem = { i, intersection };
297
+ testAngle = relativeAngle;
298
+ }
299
+ }
300
+ }
301
+ if (!foundItem) {
302
+ console.warn("Couldn't find an exit point for entry", JSON.stringify(intersections));
303
+ return;
304
+ }
305
+ intersections.splice(foundItem.i, 1);
306
+ const { intersection } = foundItem;
307
+ return intersection;
113
308
  }
114
309
  interpolatePointsWithinMaxSpacing(i, points, indicesToInsertAfter, maxSpacing) {
115
310
  const { element } = this.sculptData;
@@ -127,7 +322,7 @@ class SculptorTool extends BaseTool {
127
322
  const eventData = evt.detail;
128
323
  const element = eventData.element;
129
324
  const enabledElement = getEnabledElement(element);
130
- const { renderingEngine, viewport } = enabledElement;
325
+ const { viewport } = enabledElement;
131
326
  this.commonData.viewportIdsToRender = [viewport.id];
132
327
  const annotations = this.filterSculptableAnnotationsForElement(element);
133
328
  if (!annotations?.length) {
@@ -141,20 +336,22 @@ class SculptorTool extends BaseTool {
141
336
  else {
142
337
  const cursorShape = this.registeredShapes.get(this.selectedShape);
143
338
  const canvasCoords = eventData.currentPoints.canvas;
144
- if (this.configuration.updateCursorSize === 'dynamic') {
145
- cursorShape.updateToolSize(canvasCoords, viewport, activeAnnotation);
146
- }
339
+ cursorShape.updateToolSize(canvasCoords, viewport, activeAnnotation);
147
340
  }
148
341
  triggerAnnotationRenderForViewportIds(this.commonData.viewportIdsToRender);
149
342
  }
150
- filterSculptableAnnotationsForElement(element) {
151
- const config = this.configuration;
343
+ getToolInstance(element) {
152
344
  const enabledElement = getEnabledElement(element);
153
345
  const { renderingEngineId, viewportId } = enabledElement;
154
- const sculptableAnnotations = [];
155
346
  const toolGroup = getToolGroupForViewport(viewportId, renderingEngineId);
156
- const toolInstance = toolGroup.getToolInstance(config.referencedToolName);
157
- config.referencedToolNames.forEach((referencedToolName) => {
347
+ const toolInstance = toolGroup.getToolInstance(this.configuration.referencedToolName);
348
+ return toolInstance;
349
+ }
350
+ filterSculptableAnnotationsForElement(element) {
351
+ const { configuration } = this;
352
+ const sculptableAnnotations = [];
353
+ const toolInstance = this.getToolInstance(element);
354
+ configuration.referencedToolNames.forEach((referencedToolName) => {
158
355
  const annotations = getAnnotations(referencedToolName, element);
159
356
  if (annotations) {
160
357
  sculptableAnnotations.push(...annotations);
@@ -166,42 +363,25 @@ class SculptorTool extends BaseTool {
166
363
  const cursorShape = this.registeredShapes.get(this.selectedShape);
167
364
  cursorShape.configureToolSize(evt);
168
365
  }
169
- insertNewHandles(pushedHandles) {
170
- const indicesToInsertAfter = this.findNewHandleIndices(pushedHandles);
171
- let newIndexModifier = 0;
172
- for (let i = 0; i < indicesToInsertAfter?.length; i++) {
173
- const insertIndex = indicesToInsertAfter[i] + 1 + newIndexModifier;
174
- this.insertHandleRadially(insertIndex);
175
- newIndexModifier++;
176
- }
177
- }
178
- findNewHandleIndices(pushedHandles) {
179
- const { points, maxSpacing } = this.sculptData;
180
- const indicesToInsertAfter = [];
181
- for (let i = pushedHandles.first; i <= pushedHandles.last; i++) {
182
- this.interpolatePointsWithinMaxSpacing(i, points, indicesToInsertAfter, maxSpacing);
183
- }
184
- return indicesToInsertAfter;
185
- }
186
- insertHandleRadially(insertIndex) {
187
- const { points } = this.sculptData;
188
- if (insertIndex > points.length - 1 &&
189
- this.commonData.isEditingOpenContour) {
190
- return;
191
- }
192
- const cursorShape = this.registeredShapes.get(this.selectedShape);
193
- const previousIndex = insertIndex - 1;
194
- const nextIndex = contourIndex(insertIndex, points.length);
195
- const insertPosition = cursorShape.getInsertPosition(previousIndex, nextIndex, this.sculptData);
196
- const handleData = insertPosition;
197
- points.splice(insertIndex, 0, handleData);
198
- }
199
366
  selectFreehandTool(eventData) {
200
367
  const closestAnnotationUID = this.getClosestFreehandToolOnElement(eventData);
201
368
  if (closestAnnotationUID === undefined) {
202
369
  return;
203
370
  }
371
+ const annotation = getAnnotation(closestAnnotationUID);
204
372
  this.commonData.activeAnnotationUID = closestAnnotationUID;
373
+ this.commonData.closed = annotation.data.contour.closed;
374
+ this.commonData.external = true;
375
+ if (this.commonData.closed) {
376
+ const { element } = eventData;
377
+ const enabledElement = getEnabledElement(element);
378
+ const { viewport } = enabledElement;
379
+ const polyline = annotation.data.contour.polyline.map((p) => viewport.worldToCanvas(p));
380
+ const canvasPoint = eventData.currentPoints.canvas;
381
+ this.commonData.external = !containsPoint(polyline, canvasPoint, {
382
+ closed: true,
383
+ });
384
+ }
205
385
  }
206
386
  getClosestFreehandToolOnElement(eventData) {
207
387
  const { element } = eventData;
@@ -239,6 +419,9 @@ class SculptorTool extends BaseTool {
239
419
  return closest.annotationUID;
240
420
  }
241
421
  activateModify(element) {
422
+ const annotation = getAnnotation(this.commonData.activeAnnotationUID);
423
+ const instance = this.getToolInstance(element);
424
+ instance.createMemo?.(element, annotation);
242
425
  element.addEventListener(Events.MOUSE_UP, this.endCallback);
243
426
  element.addEventListener(Events.MOUSE_CLICK, this.endCallback);
244
427
  element.addEventListener(Events.MOUSE_DRAG, this.dragCallback);
@@ -286,6 +469,14 @@ class SculptorTool extends BaseTool {
286
469
  });
287
470
  }
288
471
  }
472
+ function pushArr(dest, src, start = 0, end = src.length) {
473
+ if (end < start) {
474
+ end = end + src.length;
475
+ }
476
+ for (let i = start; i < end; i++) {
477
+ dest.push(src[i % src.length]);
478
+ }
479
+ }
289
480
  export const contourIndex = (i, length) => {
290
481
  return (i + length) % length;
291
482
  };
@@ -255,12 +255,12 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool {
255
255
  this._throttledCalculateCachedStats = throttle(this._calculateCachedStats, 100, { trailing: true });
256
256
  }
257
257
  filterInteractableAnnotationsForElement(element, annotations) {
258
- if (!annotations || !annotations.length) {
259
- return;
258
+ if (!annotations?.length) {
259
+ return [];
260
260
  }
261
261
  const baseFilteredAnnotations = super.filterInteractableAnnotationsForElement(element, annotations);
262
- if (!baseFilteredAnnotations || !baseFilteredAnnotations.length) {
263
- return;
262
+ if (!baseFilteredAnnotations?.length) {
263
+ return [];
264
264
  }
265
265
  const enabledElement = getEnabledElement(element);
266
266
  const { viewport } = enabledElement;
@@ -113,6 +113,9 @@ class BrushTool extends LabelmapBaseTool {
113
113
  const hoverData = this._hoverData || this.createHoverData(element);
114
114
  triggerAnnotationRenderForViewportUIDs(hoverData.viewportIdsToRender);
115
115
  const operationData = this.getOperationData(element);
116
+ if (!operationData) {
117
+ return false;
118
+ }
116
119
  this.applyActiveStrategyCallback(enabledElement, operationData, StrategyCallbacks.OnInteractionStart);
117
120
  return true;
118
121
  };
@@ -218,6 +221,9 @@ class BrushTool extends LabelmapBaseTool {
218
221
  this._hoverData = this.createHoverData(element, currentCanvas);
219
222
  this._calculateCursor(element, currentCanvas);
220
223
  const operationData = this.getOperationData(element);
224
+ if (!operationData) {
225
+ return;
226
+ }
221
227
  operationData.strokePointsWorld = [
222
228
  vec3.clone(this._lastDragInfo.world),
223
229
  vec3.clone(currentWorld),
@@ -238,6 +244,9 @@ class BrushTool extends LabelmapBaseTool {
238
244
  const { element } = eventData;
239
245
  const enabledElement = getEnabledElement(element);
240
246
  const operationData = this.getOperationData(element);
247
+ if (!operationData) {
248
+ return;
249
+ }
241
250
  if (!this._previewData.preview && !this._previewData.isDrag) {
242
251
  this.applyActiveStrategy(enabledElement, operationData);
243
252
  }
@@ -1,12 +1,14 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import type { SVGDrawingHelper, EventTypes, ContourAnnotation } from '.';
3
- import type { PushedHandles } from '../tools/SculptorTool/CircleSculptCursor';
4
- import type { SculptData } from '../tools/SculptorTool';
5
3
  export interface ISculptToolShape {
6
4
  renderShape(svgDrawingHelper: SVGDrawingHelper, canvasLocation: Types.Point2, options: any): void;
7
- pushHandles(viewport: Types.IViewport, sculptData: SculptData): PushedHandles;
8
5
  configureToolSize(evt: EventTypes.InteractionEventType): void;
6
+ interpolatePoint(viewport: Types.IViewport, angle: number, center: Types.Point2): Types.Point2;
7
+ getEdge(viewport: Types.IViewport, p1: Types.Point3, p2: Types.Point3, mouseCanvas: Types.Point2): {
8
+ point: Types.Point3;
9
+ angle: number;
10
+ canvasPoint: Types.Point2;
11
+ };
9
12
  updateToolSize(canvasCoords: Types.Point2, viewport: Types.IViewport, activeAnnotation: ContourAnnotation): void;
10
13
  getMaxSpacing(minSpacing: number): number;
11
- getInsertPosition(previousIndex: number, nextIndex: number, sculptData: SculptData): Types.Point3;
12
14
  }
@@ -1 +1 @@
1
- export declare const version = "4.12.3";
1
+ export declare const version = "4.12.4";
@@ -1 +1 @@
1
- export const version = '4.12.3';
1
+ export const version = '4.12.4';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "4.12.3",
3
+ "version": "4.12.4",
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.2.0"
109
109
  },
110
110
  "peerDependencies": {
111
- "@cornerstonejs/core": "4.12.3",
111
+ "@cornerstonejs/core": "4.12.4",
112
112
  "@kitware/vtk.js": "34.15.1",
113
113
  "@types/d3-array": "3.2.1",
114
114
  "@types/d3-interpolate": "3.0.4",
@@ -127,5 +127,5 @@
127
127
  "type": "individual",
128
128
  "url": "https://ohif.org/donate"
129
129
  },
130
- "gitHead": "ea79a24d2099225dd33ac53e06e106f15322007f"
130
+ "gitHead": "709d5a61b1acccf6a2dd8f9193907f98b5ed6c4c"
131
131
  }