@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.
- package/dist/esm/tools/annotation/CircleROITool.d.ts +1 -0
- package/dist/esm/tools/annotation/CircleROITool.js +41 -8
- package/dist/esm/tools/segmentation/CircleScissorsTool.js +6 -30
- package/dist/esm/tools/segmentation/SphereScissorsTool.js +6 -30
- package/dist/esm/tools/segmentation/strategies/compositions/circularCursor.js +2 -8
- package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -1
- package/dist/esm/tools/segmentation/strategies/fillCircle.js +38 -31
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +11 -3
- package/dist/esm/utilities/getCenterAndRadiusInCanvas.d.ts +6 -0
- package/dist/esm/utilities/getCenterAndRadiusInCanvas.js +26 -0
- package/dist/esm/utilities/getEllipseWorldCoordinates.d.ts +2 -0
- package/dist/esm/utilities/getEllipseWorldCoordinates.js +26 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -4
|
@@ -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
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
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
|
-
|
|
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
|
|
127
|
-
const
|
|
128
|
-
|
|
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
|
|
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
|
|
118
|
-
const
|
|
119
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
20
|
+
vec3.scaleAndAdd(top, centerVec, viewUp, yRadius);
|
|
21
21
|
const bottom = vec3.create();
|
|
22
|
-
vec3.scaleAndAdd(bottom, centerVec, viewUp, -
|
|
22
|
+
vec3.scaleAndAdd(bottom, centerVec, viewUp, -yRadius);
|
|
23
23
|
const right = vec3.create();
|
|
24
|
-
vec3.scaleAndAdd(right, centerVec, viewRight,
|
|
24
|
+
vec3.scaleAndAdd(right, centerVec, viewRight, xRadius);
|
|
25
25
|
const left = vec3.create();
|
|
26
|
-
vec3.scaleAndAdd(left, centerVec, viewRight, -
|
|
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,
|
|
35
|
-
if (!centers.length ||
|
|
34
|
+
function createStrokePredicate(centers, xRadius, yRadius) {
|
|
35
|
+
if (!centers.length || xRadius <= 0 || yRadius <= 0) {
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
|
-
const
|
|
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 <=
|
|
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 <=
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const strokePredicate = createStrokePredicate(options.strokePointsWorld || [],
|
|
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
|
|
190
|
-
const
|
|
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
|
|
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
|
-
|
|
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,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
|
+
}
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "4.22.
|
|
1
|
+
export declare const version = "4.22.12";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '4.22.
|
|
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.
|
|
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.
|
|
108
|
+
"@cornerstonejs/core": "4.22.12",
|
|
109
109
|
"canvas": "3.2.0"
|
|
110
110
|
},
|
|
111
111
|
"peerDependencies": {
|
|
112
|
-
"@cornerstonejs/core": "4.22.
|
|
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": "
|
|
131
|
+
"gitHead": "5cb108650fe38eac81881750b859b08de4c45cce"
|
|
132
132
|
}
|