@cornerstonejs/tools 4.22.10 → 4.22.12

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.
@@ -18,6 +18,7 @@ declare class CircleROITool extends AnnotationTool {
18
18
  constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
19
19
  addNewAnnotation: (evt: EventTypes.InteractionEventType) => CircleROIAnnotation;
20
20
  isPointNearTool: (element: HTMLDivElement, annotation: CircleROIAnnotation, canvasCoords: Types.Point2, proximity: number) => boolean;
21
+ _pointInEllipseCanvas(ellipse: any, location: Types.Point2): boolean;
21
22
  toolSelectedCallback: (evt: EventTypes.InteractionEventType, annotation: CircleROIAnnotation) => void;
22
23
  handleSelectedCallback: (evt: EventTypes.InteractionEventType, annotation: CircleROIAnnotation, handle: ToolHandle) => void;
23
24
  _endCallback: (evt: EventTypes.InteractionEventType) => void;
@@ -7,7 +7,7 @@ import { addAnnotation, getAnnotations, removeAnnotation, } from '../../stateMan
7
7
  import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
8
8
  import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
9
9
  import { triggerAnnotationCompleted, triggerAnnotationModified, } from '../../stateManagement/annotation/helpers/state';
10
- import { drawCircle as drawCircleSvg, drawHandles as drawHandlesSvg, } from '../../drawingSvg';
10
+ import { drawCircle as drawCircleSvg, drawHandles as drawHandlesSvg, drawEllipseByCoordinates, } from '../../drawingSvg';
11
11
  import { state } from '../../store/state';
12
12
  import { ChangeTypes, Events, MeasurementType } from '../../enums';
13
13
  import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
@@ -16,9 +16,10 @@ import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnota
16
16
  import { getPixelValueUnits } from '../../utilities/getPixelValueUnits';
17
17
  import { isViewportPreScaled } from '../../utilities/viewport/isViewportPreScaled';
18
18
  import { getCanvasCircleCorners, getCanvasCircleRadius, } from '../../utilities/math/circle';
19
- import { pointInEllipse } from '../../utilities/math/ellipse';
19
+ import { getCanvasEllipseCorners, pointInEllipse, } from '../../utilities/math/ellipse';
20
20
  import { BasicStatsCalculator } from '../../utilities/math/basic';
21
21
  import { getStyleProperty } from '../../stateManagement/annotation/config/helpers';
22
+ import getEllipseWorldCoordinates from '../../utilities/getEllipseWorldCoordinates';
22
23
  const { transformWorldToIndex } = csUtils;
23
24
  class CircleROITool extends AnnotationTool {
24
25
  static { this.toolName = 'CircleROI'; }
@@ -74,11 +75,28 @@ class CircleROITool extends AnnotationTool {
74
75
  const enabledElement = getEnabledElement(element);
75
76
  const { viewport } = enabledElement;
76
77
  const { points } = annotation.data.handles;
77
- const canvasHandles = points.map((p) => viewport.worldToCanvas(p));
78
- const canvasCenter = canvasHandles[0];
79
- const radius = getCanvasCircleRadius([canvasCenter, canvasHandles[1]]);
80
- const radiusPoint = getCanvasCircleRadius([canvasCenter, canvasCoords]);
81
- return Math.abs(radiusPoint - radius) < proximity / 2;
78
+ const ellipseWorldCoordinates = getEllipseWorldCoordinates(points.slice(0, 2), viewport);
79
+ const ellipseCanvasCoordinates = ellipseWorldCoordinates.map((p) => viewport.worldToCanvas(p));
80
+ const canvasCorners = getCanvasEllipseCorners(ellipseCanvasCoordinates);
81
+ const [canvasPoint1, canvasPoint2] = canvasCorners;
82
+ const minorEllipse = {
83
+ left: Math.min(canvasPoint1[0], canvasPoint2[0]) + proximity / 2,
84
+ top: Math.min(canvasPoint1[1], canvasPoint2[1]) + proximity / 2,
85
+ width: Math.abs(canvasPoint1[0] - canvasPoint2[0]) - proximity,
86
+ height: Math.abs(canvasPoint1[1] - canvasPoint2[1]) - proximity,
87
+ };
88
+ const majorEllipse = {
89
+ left: Math.min(canvasPoint1[0], canvasPoint2[0]) - proximity / 2,
90
+ top: Math.min(canvasPoint1[1], canvasPoint2[1]) - proximity / 2,
91
+ width: Math.abs(canvasPoint1[0] - canvasPoint2[0]) + proximity,
92
+ height: Math.abs(canvasPoint1[1] - canvasPoint2[1]) + proximity,
93
+ };
94
+ const pointInMinorEllipse = this._pointInEllipseCanvas(minorEllipse, canvasCoords);
95
+ const pointInMajorEllipse = this._pointInEllipseCanvas(majorEllipse, canvasCoords);
96
+ if (pointInMajorEllipse && !pointInMinorEllipse) {
97
+ return true;
98
+ }
99
+ return false;
82
100
  };
83
101
  this.toolSelectedCallback = (evt, annotation) => {
84
102
  const eventDetail = evt.detail;
@@ -417,7 +435,9 @@ class CircleROITool extends AnnotationTool {
417
435
  }
418
436
  const dataId = `${annotationUID}-circle`;
419
437
  const circleUID = '0';
420
- drawCircleSvg(svgDrawingHelper, annotationUID, circleUID, center, radius, {
438
+ const ellipseWorldCoordinates = getEllipseWorldCoordinates([points[0], points[1]], viewport);
439
+ const ellipseCanvasCoordinates = ellipseWorldCoordinates.map((p) => viewport.worldToCanvas(p));
440
+ drawEllipseByCoordinates(svgDrawingHelper, annotationUID, circleUID, ellipseCanvasCoordinates, {
421
441
  color,
422
442
  lineDash,
423
443
  lineWidth,
@@ -578,6 +598,19 @@ class CircleROITool extends AnnotationTool {
578
598
  };
579
599
  this._throttledCalculateCachedStats = throttle(this._calculateCachedStats, 100, { trailing: true });
580
600
  }
601
+ _pointInEllipseCanvas(ellipse, location) {
602
+ const xRadius = ellipse.width / 2;
603
+ const yRadius = ellipse.height / 2;
604
+ if (xRadius <= 0.0 || yRadius <= 0.0) {
605
+ return false;
606
+ }
607
+ const center = [ellipse.left + xRadius, ellipse.top + yRadius];
608
+ const normalized = [location[0] - center[0], location[1] - center[1]];
609
+ const inEllipse = (normalized[0] * normalized[0]) / (xRadius * xRadius) +
610
+ (normalized[1] * normalized[1]) / (yRadius * yRadius) <=
611
+ 1.0;
612
+ return inEllipse;
613
+ }
581
614
  static { this.hydrate = (viewportId, points, options) => {
582
615
  const enabledElement = getEnabledElementByViewportId(viewportId);
583
616
  if (!enabledElement) {
@@ -9,6 +9,8 @@ import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnota
9
9
  import { segmentLocking, activeSegmentation, segmentIndex as segmentIndexController, config as segmentationConfig, } from '../../stateManagement/segmentation';
10
10
  import { getCurrentLabelmapImageIdForViewport, getSegmentation, } from '../../stateManagement/segmentation/segmentationState';
11
11
  import LabelmapBaseTool from './LabelmapBaseTool';
12
+ import getEllipseWorldCoordinates from '../../utilities/getEllipseWorldCoordinates';
13
+ import getCenterAndRadiusInCanvas from '../../utilities/getCenterAndRadiusInCanvas';
12
14
  class CircleScissorsTool extends LabelmapBaseTool {
13
15
  constructor(toolProps = {}, defaultToolProps = {
14
16
  supportedInteractionTypes: ['Mouse', 'Touch'],
@@ -123,28 +125,9 @@ class CircleScissorsTool extends LabelmapBaseTool {
123
125
  const { canvasToWorld } = viewport;
124
126
  const { annotation, viewportIdsToRender, centerCanvas } = this.editData;
125
127
  const { data } = annotation;
126
- const dX = Math.abs(currentCanvasPoints[0] - centerCanvas[0]);
127
- const dY = Math.abs(currentCanvasPoints[1] - centerCanvas[1]);
128
- const radius = Math.sqrt(dX * dX + dY * dY);
129
- const bottomCanvas = [
130
- centerCanvas[0],
131
- centerCanvas[1] + radius,
132
- ];
133
- const topCanvas = [centerCanvas[0], centerCanvas[1] - radius];
134
- const leftCanvas = [
135
- centerCanvas[0] - radius,
136
- centerCanvas[1],
137
- ];
138
- const rightCanvas = [
139
- centerCanvas[0] + radius,
140
- centerCanvas[1],
141
- ];
142
- data.handles.points = [
143
- canvasToWorld(bottomCanvas),
144
- canvasToWorld(topCanvas),
145
- canvasToWorld(leftCanvas),
146
- canvasToWorld(rightCanvas),
147
- ];
128
+ const centerWorld = canvasToWorld(centerCanvas);
129
+ const currentWorld = canvasToWorld(currentCanvasPoints);
130
+ data.handles.points = getEllipseWorldCoordinates([centerWorld, currentWorld], viewport);
148
131
  annotation.invalidated = true;
149
132
  this.editData.hasMoved = true;
150
133
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
@@ -207,14 +190,7 @@ class CircleScissorsTool extends LabelmapBaseTool {
207
190
  const annotationUID = annotation.annotationUID;
208
191
  const data = annotation.data;
209
192
  const { points } = data.handles;
210
- const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
211
- const bottom = canvasCoordinates[0];
212
- const top = canvasCoordinates[1];
213
- const center = [
214
- Math.floor((bottom[0] + top[0]) / 2),
215
- Math.floor((bottom[1] + top[1]) / 2),
216
- ];
217
- const radius = Math.abs(bottom[1] - Math.floor((bottom[1] + top[1]) / 2));
193
+ const { center, radius } = getCenterAndRadiusInCanvas(points, viewport);
218
194
  const color = `rgb(${toolMetadata.segmentColor.slice(0, 3)})`;
219
195
  if (!viewport.getRenderingEngine()) {
220
196
  console.warn('Rendering Engine has been destroyed');
@@ -8,6 +8,8 @@ import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnota
8
8
  import { config as segmentationConfig, segmentLocking, segmentIndex as segmentIndexController, activeSegmentation, } from '../../stateManagement/segmentation';
9
9
  import { getSegmentation } from '../../stateManagement/segmentation/segmentationState';
10
10
  import LabelmapBaseTool from './LabelmapBaseTool';
11
+ import getEllipseWorldCoordinates from '../../utilities/getEllipseWorldCoordinates';
12
+ import { getCenterAndRadiusInCanvas } from '../../utilities/getCenterAndRadiusInCanvas';
11
13
  class SphereScissorsTool extends LabelmapBaseTool {
12
14
  constructor(toolProps = {}, defaultToolProps = {
13
15
  supportedInteractionTypes: ['Mouse', 'Touch'],
@@ -114,28 +116,9 @@ class SphereScissorsTool extends LabelmapBaseTool {
114
116
  const { canvasToWorld } = viewport;
115
117
  const { annotation, viewportIdsToRender, centerCanvas } = this.editData;
116
118
  const { data } = annotation;
117
- const dX = Math.abs(currentCanvasPoints[0] - centerCanvas[0]);
118
- const dY = Math.abs(currentCanvasPoints[1] - centerCanvas[1]);
119
- const radius = Math.sqrt(dX * dX + dY * dY);
120
- const bottomCanvas = [
121
- centerCanvas[0],
122
- centerCanvas[1] + radius,
123
- ];
124
- const topCanvas = [centerCanvas[0], centerCanvas[1] - radius];
125
- const leftCanvas = [
126
- centerCanvas[0] - radius,
127
- centerCanvas[1],
128
- ];
129
- const rightCanvas = [
130
- centerCanvas[0] + radius,
131
- centerCanvas[1],
132
- ];
133
- data.handles.points = [
134
- canvasToWorld(bottomCanvas),
135
- canvasToWorld(topCanvas),
136
- canvasToWorld(leftCanvas),
137
- canvasToWorld(rightCanvas),
138
- ];
119
+ const centerWorld = canvasToWorld(centerCanvas);
120
+ const currentWorld = canvasToWorld(currentCanvasPoints);
121
+ data.handles.points = getEllipseWorldCoordinates([centerWorld, currentWorld], viewport);
139
122
  annotation.invalidated = true;
140
123
  this.editData.hasMoved = true;
141
124
  triggerAnnotationRenderForViewportIds(viewportIdsToRender);
@@ -201,14 +184,7 @@ class SphereScissorsTool extends LabelmapBaseTool {
201
184
  const annotationUID = annotation.annotationUID;
202
185
  const data = annotation.data;
203
186
  const { points } = data.handles;
204
- const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
205
- const bottom = canvasCoordinates[0];
206
- const top = canvasCoordinates[1];
207
- const center = [
208
- Math.floor((bottom[0] + top[0]) / 2),
209
- Math.floor((bottom[1] + top[1]) / 2),
210
- ];
211
- const radius = Math.abs(bottom[1] - Math.floor((bottom[1] + top[1]) / 2));
187
+ const { center, radius } = getCenterAndRadiusInCanvas(points, viewport);
212
188
  const color = `rgb(${toolMetadata.segmentColor.slice(0, 3)})`;
213
189
  if (!viewport.getRenderingEngine()) {
214
190
  console.warn('Rendering Engine has been destroyed');
@@ -1,6 +1,7 @@
1
1
  import { vec3 } from 'gl-matrix';
2
2
  import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
3
3
  import { drawCircle as drawCircleSvg } from '../../../../drawingSvg';
4
+ import { getCenterAndRadiusInCanvas } from '../../../../utilities/getCenterAndRadiusInCanvas';
4
5
  export default {
5
6
  [StrategyCallbacks.CalculateCursorGeometry]: function (enabledElement, operationData) {
6
7
  if (!operationData) {
@@ -67,14 +68,7 @@ export default {
67
68
  const annotationUID = toolMetadata.brushCursorUID;
68
69
  const data = brushCursor.data;
69
70
  const { points } = data.handles;
70
- const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
71
- const bottom = canvasCoordinates[0];
72
- const top = canvasCoordinates[1];
73
- const center = [
74
- Math.floor((bottom[0] + top[0]) / 2),
75
- Math.floor((bottom[1] + top[1]) / 2),
76
- ];
77
- const radius = Math.abs(bottom[1] - Math.floor((bottom[1] + top[1]) / 2));
71
+ const { center, radius } = getCenterAndRadiusInCanvas(points, viewport);
78
72
  const color = `rgb(${toolMetadata.segmentColor?.slice(0, 3) || [0, 0, 0]})`;
79
73
  if (!viewport.getRenderingEngine()) {
80
74
  console.warn('Rendering Engine has been destroyed');
@@ -6,7 +6,9 @@ export declare function getEllipseCornersFromCanvasCoordinates(canvasCoordinates
6
6
  declare function createPointInEllipse(cornersInWorld?: Types.Point3[], options?: {
7
7
  strokePointsWorld?: Types.Point3[];
8
8
  segmentationImageData?: vtkImageData;
9
- radius?: number;
9
+ xRadius?: number;
10
+ yRadius?: number;
11
+ aspectRatio?: [number, number];
10
12
  }): (pointLPS: Types.Point3 | null, pointIJK?: Types.Point3) => boolean;
11
13
  declare const CIRCLE_STRATEGY: BrushStrategy;
12
14
  declare const CIRCLE_THRESHOLD_STRATEGY: BrushStrategy;
@@ -5,7 +5,7 @@ import BrushStrategy from './BrushStrategy';
5
5
  import { StrategyCallbacks } from '../../../enums';
6
6
  import compositions from './compositions';
7
7
  import { pointInSphere } from '../../../utilities/math/sphere';
8
- const { transformWorldToIndex, transformIndexToWorld, isEqual } = csUtils;
8
+ const { transformWorldToIndex, transformIndexToWorld, isEqual, getNormalizedAspectRatio, } = csUtils;
9
9
  export function getEllipseCornersFromCanvasCoordinates(canvasCoordinates) {
10
10
  const [bottom, top, left, right] = canvasCoordinates;
11
11
  const topLeft = [left[0], top[1]];
@@ -14,16 +14,16 @@ export function getEllipseCornersFromCanvasCoordinates(canvasCoordinates) {
14
14
  const topRight = [right[0], top[1]];
15
15
  return [topLeft, bottomRight, bottomLeft, topRight];
16
16
  }
17
- function createCircleCornersForCenter(center, viewUp, viewRight, radius) {
17
+ function createCircleCornersForCenter(center, viewUp, viewRight, yRadius, xRadius) {
18
18
  const centerVec = vec3.fromValues(center[0], center[1], center[2]);
19
19
  const top = vec3.create();
20
- vec3.scaleAndAdd(top, centerVec, viewUp, radius);
20
+ vec3.scaleAndAdd(top, centerVec, viewUp, yRadius);
21
21
  const bottom = vec3.create();
22
- vec3.scaleAndAdd(bottom, centerVec, viewUp, -radius);
22
+ vec3.scaleAndAdd(bottom, centerVec, viewUp, -yRadius);
23
23
  const right = vec3.create();
24
- vec3.scaleAndAdd(right, centerVec, viewRight, radius);
24
+ vec3.scaleAndAdd(right, centerVec, viewRight, xRadius);
25
25
  const left = vec3.create();
26
- vec3.scaleAndAdd(left, centerVec, viewRight, -radius);
26
+ vec3.scaleAndAdd(left, centerVec, viewRight, -xRadius);
27
27
  return [
28
28
  bottom,
29
29
  top,
@@ -31,11 +31,12 @@ function createCircleCornersForCenter(center, viewUp, viewRight, radius) {
31
31
  right,
32
32
  ];
33
33
  }
34
- function createStrokePredicate(centers, radius) {
35
- if (!centers.length || radius <= 0) {
34
+ function createStrokePredicate(centers, xRadius, yRadius) {
35
+ if (!centers.length || xRadius <= 0 || yRadius <= 0) {
36
36
  return null;
37
37
  }
38
- const radiusSquared = radius * radius;
38
+ const xRadiusSquared = xRadius * xRadius;
39
+ const yRadiusSquared = yRadius * yRadius;
39
40
  const centerVecs = centers.map((point) => [point[0], point[1], point[2]]);
40
41
  const segments = [];
41
42
  for (let i = 1; i < centerVecs.length; i++) {
@@ -55,7 +56,8 @@ function createStrokePredicate(centers, radius) {
55
56
  const dx = worldPoint[0] - centerVec[0];
56
57
  const dy = worldPoint[1] - centerVec[1];
57
58
  const dz = worldPoint[2] - centerVec[2];
58
- if (dx * dx + dy * dy + dz * dz <= radiusSquared) {
59
+ if ((dx * dx) / xRadiusSquared + (dy * dy) / yRadiusSquared + dz * dz <=
60
+ 1) {
59
61
  return true;
60
62
  }
61
63
  }
@@ -64,7 +66,8 @@ function createStrokePredicate(centers, radius) {
64
66
  const dx = worldPoint[0] - start[0];
65
67
  const dy = worldPoint[1] - start[1];
66
68
  const dz = worldPoint[2] - start[2];
67
- if (dx * dx + dy * dy + dz * dz <= radiusSquared) {
69
+ if ((dx * dx) / xRadiusSquared + (dy * dy) / yRadiusSquared + dz * dz <=
70
+ 1) {
68
71
  return true;
69
72
  }
70
73
  continue;
@@ -80,7 +83,10 @@ function createStrokePredicate(centers, radius) {
80
83
  const distX = worldPoint[0] - projX;
81
84
  const distY = worldPoint[1] - projY;
82
85
  const distZ = worldPoint[2] - projZ;
83
- if (distX * distX + distY * distY + distZ * distZ <= radiusSquared) {
86
+ if ((distX * distX) / xRadiusSquared +
87
+ (distY * distY) / yRadiusSquared +
88
+ distZ * distZ <=
89
+ 1) {
84
90
  return true;
85
91
  }
86
92
  }
@@ -103,7 +109,13 @@ const initializeCircle = {
103
109
  }
104
110
  operationData.centerWorld = center;
105
111
  operationData.centerIJK = transformWorldToIndex(segmentationImageData, center);
106
- const brushRadius = points.length >= 2 ? vec3.distance(points[0], points[1]) / 2 : 0;
112
+ const aspectRatio = getNormalizedAspectRatio(viewport.getAspectRatio());
113
+ const yRadius = points.length >= 2
114
+ ? vec3.distance(points[0], points[1]) / 2 / aspectRatio[1]
115
+ : 0;
116
+ const xRadius = points.length >= 2
117
+ ? vec3.distance(points[2], points[3]) / 2 / aspectRatio[0]
118
+ : 0;
107
119
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
108
120
  const corners = getEllipseCornersFromCanvasCoordinates(canvasCoordinates);
109
121
  const cornersInWorld = corners.map((corner) => viewport.canvasToWorld(corner));
@@ -119,14 +131,16 @@ const initializeCircle = {
119
131
  ? operationData.strokePointsWorld
120
132
  : [operationData.centerWorld];
121
133
  const strokeCenters = strokeCentersSource.map((point) => vec3.clone(point));
122
- const strokeCornersWorld = strokeCenters.flatMap((centerPoint) => createCircleCornersForCenter(centerPoint, normalizedViewUp, viewRight, brushRadius));
134
+ const strokeCornersWorld = strokeCenters.flatMap((centerPoint) => createCircleCornersForCenter(centerPoint, normalizedViewUp, viewRight, yRadius, xRadius));
123
135
  const circleCornersIJK = strokeCornersWorld.map((world) => transformWorldToIndex(segmentationImageData, world));
124
136
  const boundsIJK = getBoundingBoxAroundShapeIJK(circleCornersIJK, segmentationImageData.getDimensions());
125
137
  operationData.strokePointsWorld = strokeCenters;
126
138
  operationData.isInObject = createPointInEllipse(cornersInWorld, {
127
139
  strokePointsWorld: strokeCenters,
128
140
  segmentationImageData,
129
- radius: brushRadius,
141
+ xRadius,
142
+ yRadius,
143
+ aspectRatio,
130
144
  });
131
145
  operationData.isInObjectBoundsIJK = boundsIJK;
132
146
  },
@@ -136,22 +150,22 @@ function createPointInEllipse(cornersInWorld = [], options = {}) {
136
150
  throw new Error('createPointInEllipse: cornersInWorld must have 4 points');
137
151
  }
138
152
  const [topLeft, bottomRight, bottomLeft, topRight] = cornersInWorld;
153
+ const aspectRatio = options.aspectRatio || [1, 1];
139
154
  const center = vec3.create();
140
155
  vec3.add(center, topLeft, bottomRight);
141
156
  vec3.scale(center, center, 0.5);
142
157
  const majorAxisVec = vec3.create();
143
158
  vec3.subtract(majorAxisVec, topRight, topLeft);
144
- const xRadius = vec3.length(majorAxisVec) / 2;
159
+ const originalRadius = vec3.length(majorAxisVec) / 2;
145
160
  vec3.normalize(majorAxisVec, majorAxisVec);
146
161
  const minorAxisVec = vec3.create();
147
162
  vec3.subtract(minorAxisVec, bottomLeft, topLeft);
148
- const yRadius = vec3.length(minorAxisVec) / 2;
149
163
  vec3.normalize(minorAxisVec, minorAxisVec);
150
- const normal = vec3.create();
151
- vec3.cross(normal, majorAxisVec, minorAxisVec);
152
- vec3.normalize(normal, normal);
153
- const radiusForStroke = options.radius ?? Math.max(xRadius, yRadius);
154
- const strokePredicate = createStrokePredicate(options.strokePointsWorld || [], radiusForStroke);
164
+ const xRadius = originalRadius / aspectRatio[0];
165
+ const yRadius = originalRadius / aspectRatio[1];
166
+ const xRadiusForStroke = options.xRadius ?? xRadius;
167
+ const yRadiusForStroke = options.yRadius ?? yRadius;
168
+ const strokePredicate = createStrokePredicate(options.strokePointsWorld || [], xRadiusForStroke, yRadiusForStroke);
155
169
  if (isEqual(xRadius, yRadius)) {
156
170
  const radius = xRadius;
157
171
  const sphereObj = {
@@ -186,15 +200,8 @@ function createPointInEllipse(cornersInWorld = [], options = {}) {
186
200
  }
187
201
  const pointVec = vec3.create();
188
202
  vec3.subtract(pointVec, worldPoint, center);
189
- const distToPlane = vec3.dot(pointVec, normal);
190
- const proj = vec3.create();
191
- vec3.scaleAndAdd(proj, pointVec, normal, -distToPlane);
192
- const fromTopLeft = vec3.create();
193
- const centerToTopLeft = vec3.create();
194
- vec3.subtract(centerToTopLeft, center, topLeft);
195
- vec3.subtract(fromTopLeft, proj, centerToTopLeft);
196
- const x = vec3.dot(fromTopLeft, majorAxisVec);
197
- const y = vec3.dot(fromTopLeft, minorAxisVec);
203
+ const x = vec3.dot(pointVec, majorAxisVec);
204
+ const y = vec3.dot(pointVec, minorAxisVec);
198
205
  return (x * x) / (xRadius * xRadius) + (y * y) / (yRadius * yRadius) <= 1;
199
206
  };
200
207
  }
@@ -4,7 +4,7 @@ import BrushStrategy from './BrushStrategy';
4
4
  import compositions from './compositions';
5
5
  import StrategyCallbacks from '../../../enums/StrategyCallbacks';
6
6
  import { createEllipseInPoint, getEllipseCornersFromCanvasCoordinates, } from './fillCircle';
7
- const { transformWorldToIndex } = csUtils;
7
+ const { transformWorldToIndex, getNormalizedAspectRatio } = csUtils;
8
8
  import { getSphereBoundsInfoFromViewport } from '../../../utilities/getSphereBoundsInfo';
9
9
  const sphereComposition = {
10
10
  [StrategyCallbacks.Initialize]: (operationData) => {
@@ -26,7 +26,13 @@ const sphereComposition = {
26
26
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
27
27
  const corners = getEllipseCornersFromCanvasCoordinates(canvasCoordinates);
28
28
  const cornersInWorld = corners.map((corner) => viewport.canvasToWorld(corner));
29
- const strokeRadius = points.length >= 2 ? vec3.distance(points[0], points[1]) / 2 : undefined;
29
+ const aspectRatio = getNormalizedAspectRatio(viewport.getAspectRatio());
30
+ const yRadius = points.length >= 2
31
+ ? vec3.distance(points[0], points[1]) / 2 / aspectRatio[1]
32
+ : 0;
33
+ const xRadius = points.length >= 2
34
+ ? vec3.distance(points[2], points[3]) / 2 / aspectRatio[0]
35
+ : 0;
30
36
  const strokeCenters = operationData.strokePointsWorld &&
31
37
  operationData.strokePointsWorld.length > 0
32
38
  ? operationData.strokePointsWorld
@@ -90,7 +96,9 @@ const sphereComposition = {
90
96
  operationData.isInObject = createEllipseInPoint(cornersInWorld, {
91
97
  strokePointsWorld: operationData.strokePointsWorld,
92
98
  segmentationImageData,
93
- radius: strokeRadius,
99
+ xRadius,
100
+ yRadius,
101
+ aspectRatio,
94
102
  });
95
103
  },
96
104
  };
@@ -0,0 +1,6 @@
1
+ import { type Types, type VolumeViewport } from '@cornerstonejs/core';
2
+ export declare function getCenterAndRadiusInCanvas(points: Types.Point3[], viewport: Types.IStackViewport | VolumeViewport): {
3
+ center: Types.Point2;
4
+ radius: number;
5
+ };
6
+ export default getCenterAndRadiusInCanvas;
@@ -0,0 +1,26 @@
1
+ import {} from '@cornerstonejs/core';
2
+ import { vec2, vec3 } from 'gl-matrix';
3
+ const EPSILON = 1e-4;
4
+ export function getCenterAndRadiusInCanvas(points, viewport) {
5
+ const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
6
+ const [cBottom, cTop, cLeft, cRight] = canvasPoints;
7
+ const center = [
8
+ (cBottom[0] + cTop[0]) / 2,
9
+ (cBottom[1] + cTop[1]) / 2,
10
+ ];
11
+ const worldHeight = vec3.distance(points[0], points[1]);
12
+ const worldWidth = vec3.distance(points[2], points[3]);
13
+ const canvasHeight = vec2.distance(cBottom, cTop);
14
+ const canvasWidth = vec2.distance(cLeft, cRight);
15
+ const scaleX = canvasWidth / worldWidth;
16
+ const scaleY = canvasHeight / worldHeight;
17
+ const worldRadius = worldHeight / 2;
18
+ const radius = Math.abs(scaleX - scaleY) > EPSILON
19
+ ? worldRadius * Math.min(scaleX, scaleY)
20
+ : canvasHeight / 2;
21
+ return {
22
+ center: center,
23
+ radius: Math.round(radius),
24
+ };
25
+ }
26
+ export default getCenterAndRadiusInCanvas;
@@ -0,0 +1,2 @@
1
+ import type { VolumeViewport, Types } from '@cornerstonejs/core';
2
+ export default function getEllipseWorldCoordinates(points: [Types.Point3, Types.Point3], viewport: Types.IStackViewport | VolumeViewport): Types.Point3[];
@@ -0,0 +1,26 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ export default function getEllipseWorldCoordinates(points, viewport) {
3
+ const camera = viewport.getCamera();
4
+ const { viewUp, viewPlaneNormal } = camera;
5
+ const viewRight = vec3.create();
6
+ vec3.cross(viewRight, viewUp, viewPlaneNormal);
7
+ const [centerWorld, endWorld] = points;
8
+ const centerToEndDistance = vec3.distance(centerWorld, endWorld);
9
+ const bottomWorld = vec3.create();
10
+ const topWorld = vec3.create();
11
+ const leftWorld = vec3.create();
12
+ const rightWorld = vec3.create();
13
+ for (let i = 0; i <= 2; i++) {
14
+ bottomWorld[i] = centerWorld[i] - viewUp[i] * centerToEndDistance;
15
+ topWorld[i] = centerWorld[i] + viewUp[i] * centerToEndDistance;
16
+ leftWorld[i] = centerWorld[i] - viewRight[i] * centerToEndDistance;
17
+ rightWorld[i] = centerWorld[i] + viewRight[i] * centerToEndDistance;
18
+ }
19
+ const ellipseWorldCoordinates = [
20
+ bottomWorld,
21
+ topWorld,
22
+ leftWorld,
23
+ rightWorld,
24
+ ];
25
+ return ellipseWorldCoordinates;
26
+ }
@@ -1 +1 @@
1
- export declare const version = "4.22.10";
1
+ export declare const version = "4.22.12";
@@ -1 +1 @@
1
- export const version = '4.22.10';
1
+ export const version = '4.22.12';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "4.22.10",
3
+ "version": "4.22.12",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "types": "./dist/esm/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -105,11 +105,11 @@
105
105
  "lodash.get": "4.4.2"
106
106
  },
107
107
  "devDependencies": {
108
- "@cornerstonejs/core": "4.22.10",
108
+ "@cornerstonejs/core": "4.22.12",
109
109
  "canvas": "3.2.0"
110
110
  },
111
111
  "peerDependencies": {
112
- "@cornerstonejs/core": "4.22.10",
112
+ "@cornerstonejs/core": "4.22.12",
113
113
  "@kitware/vtk.js": "34.15.1",
114
114
  "@types/d3-array": "3.2.1",
115
115
  "@types/d3-interpolate": "3.0.4",
@@ -128,5 +128,5 @@
128
128
  "type": "individual",
129
129
  "url": "https://ohif.org/donate"
130
130
  },
131
- "gitHead": "c6b2f1a3649b96a44a71e64c389f82079d74ba53"
131
+ "gitHead": "5cb108650fe38eac81881750b859b08de4c45cce"
132
132
  }