@cornerstonejs/tools 1.70.15 → 1.71.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.
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/synchronizers/synchronizers/createVOISynchronizer.js.map +1 -1
- package/dist/cjs/tools/OrientationMarkerTool.js.map +1 -1
- package/dist/cjs/tools/SculptorTool/CircleSculptCursor.d.ts +20 -0
- package/dist/cjs/tools/SculptorTool/CircleSculptCursor.js +110 -0
- package/dist/cjs/tools/SculptorTool/CircleSculptCursor.js.map +1 -0
- package/dist/cjs/tools/SculptorTool.d.ts +41 -0
- package/dist/cjs/tools/SculptorTool.js +298 -0
- package/dist/cjs/tools/SculptorTool.js.map +1 -0
- package/dist/cjs/tools/distancePointToContour.d.ts +3 -0
- package/dist/cjs/tools/distancePointToContour.js +24 -0
- package/dist/cjs/tools/distancePointToContour.js.map +1 -0
- package/dist/cjs/tools/index.d.ts +2 -1
- package/dist/cjs/tools/index.js +3 -1
- package/dist/cjs/tools/index.js.map +1 -1
- package/dist/cjs/types/ISculptToolShape.d.ts +12 -0
- package/dist/cjs/types/ISculptToolShape.js +3 -0
- package/dist/cjs/types/ISculptToolShape.js.map +1 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/synchronizers/synchronizers/createVOISynchronizer.js.map +1 -1
- package/dist/esm/tools/OrientationMarkerTool.js.map +1 -1
- package/dist/esm/tools/SculptorTool/CircleSculptCursor.js +108 -0
- package/dist/esm/tools/SculptorTool/CircleSculptCursor.js.map +1 -0
- package/dist/esm/tools/SculptorTool.js +290 -0
- package/dist/esm/tools/SculptorTool.js.map +1 -0
- package/dist/esm/tools/distancePointToContour.js +19 -0
- package/dist/esm/tools/distancePointToContour.js.map +1 -0
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/types/ISculptToolShape.js +2 -0
- package/dist/esm/types/ISculptToolShape.js.map +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/synchronizers/synchronizers/createVOISynchronizer.d.ts.map +1 -1
- package/dist/types/tools/SculptorTool/CircleSculptCursor.d.ts +21 -0
- package/dist/types/tools/SculptorTool/CircleSculptCursor.d.ts.map +1 -0
- package/dist/types/tools/SculptorTool.d.ts +42 -0
- package/dist/types/tools/SculptorTool.d.ts.map +1 -0
- package/dist/types/tools/distancePointToContour.d.ts +4 -0
- package/dist/types/tools/distancePointToContour.d.ts.map +1 -0
- package/dist/types/tools/index.d.ts +2 -1
- package/dist/types/tools/index.d.ts.map +1 -1
- package/dist/types/types/ISculptToolShape.d.ts +13 -0
- package/dist/types/types/ISculptToolShape.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +2 -1
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/synchronizers/synchronizers/createVOISynchronizer.ts +5 -2
- package/src/tools/OrientationMarkerTool.ts +3 -3
- package/src/tools/SculptorTool/CircleSculptCursor.ts +212 -0
- package/src/tools/SculptorTool.ts +604 -0
- package/src/tools/distancePointToContour.ts +35 -0
- package/src/tools/index.ts +2 -0
- package/src/types/ISculptToolShape.ts +63 -0
- package/src/types/index.ts +2 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { getEnabledElement } from '@cornerstonejs/core';
|
|
2
|
+
import type { Types } from '@cornerstonejs/core';
|
|
3
|
+
import { BaseTool } from './base';
|
|
4
|
+
import { getAnnotations } from '../stateManagement';
|
|
5
|
+
import {
|
|
6
|
+
EventTypes,
|
|
7
|
+
PublicToolProps,
|
|
8
|
+
ToolProps,
|
|
9
|
+
SVGDrawingHelper,
|
|
10
|
+
ContourAnnotation,
|
|
11
|
+
} from '../types';
|
|
12
|
+
import { point } from '../utilities/math';
|
|
13
|
+
import { Events, ToolModes, AnnotationStyleStates } from '../enums';
|
|
14
|
+
import { ToolGroupManager } from '../store';
|
|
15
|
+
import { triggerAnnotationRenderForViewportIds } from '../utilities/triggerAnnotationRenderForViewportIds';
|
|
16
|
+
import {
|
|
17
|
+
hideElementCursor,
|
|
18
|
+
resetElementCursor,
|
|
19
|
+
} from '../cursors/elementCursor';
|
|
20
|
+
import { StyleSpecifier } from '../types/AnnotationStyle';
|
|
21
|
+
import { getStyleProperty } from '../stateManagement/annotation/config/helpers';
|
|
22
|
+
import { triggerAnnotationModified } from '../stateManagement/annotation/helpers/state';
|
|
23
|
+
import CircleSculptCursor from './SculptorTool/CircleSculptCursor';
|
|
24
|
+
import type { ISculptToolShape } from '../types/ISculptToolShape';
|
|
25
|
+
import { distancePointToContour } from './distancePointToContour';
|
|
26
|
+
|
|
27
|
+
export type SculptData = {
|
|
28
|
+
mousePoint: Types.Point3;
|
|
29
|
+
mouseCanvasPoint: Types.Point2;
|
|
30
|
+
points: Array<Types.Point3>;
|
|
31
|
+
maxSpacing: number;
|
|
32
|
+
element: HTMLDivElement;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type CommonData = {
|
|
36
|
+
activeAnnotationUID: string | null;
|
|
37
|
+
viewportIdsToRender: any[];
|
|
38
|
+
isEditingOpenContour: boolean;
|
|
39
|
+
canvasLocation: Types.Point2 | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This tool allows modifying the contour data for planar freehand by sculpting
|
|
44
|
+
* it externally using another shape to push the contour in one direction or the other.
|
|
45
|
+
*/
|
|
46
|
+
class SculptorTool extends BaseTool {
|
|
47
|
+
static toolName: string;
|
|
48
|
+
registeredShapes = new Map();
|
|
49
|
+
private isActive = false;
|
|
50
|
+
private selectedShape: string;
|
|
51
|
+
private commonData: CommonData = {
|
|
52
|
+
activeAnnotationUID: null,
|
|
53
|
+
viewportIdsToRender: [],
|
|
54
|
+
isEditingOpenContour: false,
|
|
55
|
+
canvasLocation: undefined,
|
|
56
|
+
};
|
|
57
|
+
private sculptData?: SculptData;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
toolProps: PublicToolProps = {},
|
|
61
|
+
defaultToolProps: ToolProps = {
|
|
62
|
+
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
63
|
+
configuration: {
|
|
64
|
+
minSpacing: 1,
|
|
65
|
+
referencedToolNames: [
|
|
66
|
+
'PlanarFreehandROI',
|
|
67
|
+
'PlanarFreehandContourSegmentationTool',
|
|
68
|
+
],
|
|
69
|
+
toolShape: 'circle',
|
|
70
|
+
referencedToolName: 'PlanarFreehandROI',
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
) {
|
|
74
|
+
super(toolProps, defaultToolProps);
|
|
75
|
+
this.registerShapes(CircleSculptCursor.shapeName, CircleSculptCursor);
|
|
76
|
+
this.setToolShape(this.configuration.toolShape);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register different tool shapes for sculptor tool
|
|
81
|
+
* @param shapeName name of shape
|
|
82
|
+
* @param shapeClass shape class
|
|
83
|
+
*/
|
|
84
|
+
registerShapes<T extends ISculptToolShape>(
|
|
85
|
+
shapeName: string,
|
|
86
|
+
shapeClass: new () => T
|
|
87
|
+
): void {
|
|
88
|
+
const shape = new shapeClass();
|
|
89
|
+
this.registeredShapes.set(shapeName, shape);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => {
|
|
93
|
+
const eventData = evt.detail;
|
|
94
|
+
const element = eventData.element;
|
|
95
|
+
|
|
96
|
+
this.configureToolSize(evt);
|
|
97
|
+
this.selectFreehandTool(eventData);
|
|
98
|
+
|
|
99
|
+
if (this.commonData.activeAnnotationUID === null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.isActive = true;
|
|
104
|
+
|
|
105
|
+
hideElementCursor(element);
|
|
106
|
+
this.activateModify(element);
|
|
107
|
+
return true;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
mouseMoveCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
111
|
+
if (this.mode === ToolModes.Active) {
|
|
112
|
+
this.configureToolSize(evt);
|
|
113
|
+
this.updateCursor(evt);
|
|
114
|
+
} else {
|
|
115
|
+
this.commonData.canvasLocation = undefined;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sculpts the freehand ROI with freehandSculpter tool, moving,
|
|
121
|
+
* adding and removing handles as necessary.
|
|
122
|
+
*
|
|
123
|
+
* @param eventData - Data object associated with the event.
|
|
124
|
+
* @param points - Array of points
|
|
125
|
+
*/
|
|
126
|
+
protected sculpt(eventData: any, points: Array<Types.Point3>): void {
|
|
127
|
+
const config = this.configuration;
|
|
128
|
+
const element = eventData.element;
|
|
129
|
+
|
|
130
|
+
const enabledElement = getEnabledElement(element);
|
|
131
|
+
const { viewport } = enabledElement;
|
|
132
|
+
const cursorShape = this.registeredShapes.get(this.selectedShape);
|
|
133
|
+
|
|
134
|
+
this.sculptData = {
|
|
135
|
+
mousePoint: eventData.currentPoints.world,
|
|
136
|
+
mouseCanvasPoint: eventData.currentPoints.canvas,
|
|
137
|
+
points,
|
|
138
|
+
maxSpacing: cursorShape.getMaxSpacing(config.minSpacing),
|
|
139
|
+
element: element,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const pushedHandles = cursorShape.pushHandles(viewport, this.sculptData);
|
|
143
|
+
|
|
144
|
+
if (pushedHandles.first !== undefined) {
|
|
145
|
+
this.insertNewHandles(pushedHandles);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Interpolates or fills in points between two points within a specified
|
|
151
|
+
* maximum spacing constraint.
|
|
152
|
+
*/
|
|
153
|
+
protected interpolatePointsWithinMaxSpacing(
|
|
154
|
+
i: number,
|
|
155
|
+
points: Array<Types.Point3>,
|
|
156
|
+
indicesToInsertAfter: Array<number>,
|
|
157
|
+
maxSpacing: number
|
|
158
|
+
): void {
|
|
159
|
+
const { element } = this.sculptData;
|
|
160
|
+
const enabledElement = getEnabledElement(element);
|
|
161
|
+
const { viewport } = enabledElement;
|
|
162
|
+
const nextHandleIndex = contourIndex(i + 1, points.length);
|
|
163
|
+
|
|
164
|
+
const currentCanvasPoint = viewport.worldToCanvas(points[i]);
|
|
165
|
+
const nextCanvasPoint = viewport.worldToCanvas(points[nextHandleIndex]);
|
|
166
|
+
|
|
167
|
+
const distanceToNextHandle = point.distanceToPoint(
|
|
168
|
+
currentCanvasPoint,
|
|
169
|
+
nextCanvasPoint
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (distanceToNextHandle > maxSpacing) {
|
|
173
|
+
indicesToInsertAfter.push(i);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Updates cursor size
|
|
179
|
+
*
|
|
180
|
+
* @param evt - The event
|
|
181
|
+
*/
|
|
182
|
+
private updateCursor(evt: EventTypes.InteractionEventType): void {
|
|
183
|
+
const eventData = evt.detail;
|
|
184
|
+
const element = eventData.element;
|
|
185
|
+
|
|
186
|
+
const enabledElement = getEnabledElement(element);
|
|
187
|
+
const { renderingEngine, viewport } = enabledElement;
|
|
188
|
+
|
|
189
|
+
this.commonData.viewportIdsToRender = [viewport.id];
|
|
190
|
+
|
|
191
|
+
const annotations = this.filterSculptableAnnotationsForElement(element);
|
|
192
|
+
|
|
193
|
+
if (!annotations?.length) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const activeAnnotation = annotations.find(
|
|
198
|
+
(annotation) =>
|
|
199
|
+
annotation.annotationUID === this.commonData.activeAnnotationUID
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
this.commonData.canvasLocation = eventData.currentPoints.canvas;
|
|
203
|
+
|
|
204
|
+
if (this.isActive) {
|
|
205
|
+
activeAnnotation.highlighted = true;
|
|
206
|
+
} else {
|
|
207
|
+
const cursorShape = this.registeredShapes.get(this.selectedShape);
|
|
208
|
+
const canvasCoords = eventData.currentPoints.canvas;
|
|
209
|
+
cursorShape.updateToolSize(canvasCoords, viewport, activeAnnotation);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
triggerAnnotationRenderForViewportIds(
|
|
213
|
+
renderingEngine,
|
|
214
|
+
this.commonData.viewportIdsToRender
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Returns sculptable freehand ROI annotations
|
|
220
|
+
*
|
|
221
|
+
* @param element - The viewport element
|
|
222
|
+
*/
|
|
223
|
+
private filterSculptableAnnotationsForElement(
|
|
224
|
+
element: HTMLDivElement
|
|
225
|
+
): ContourAnnotation[] {
|
|
226
|
+
const config = this.configuration;
|
|
227
|
+
const enabledElement = getEnabledElement(element);
|
|
228
|
+
const { renderingEngineId, viewportId } = enabledElement;
|
|
229
|
+
const sculptableAnnotations = [];
|
|
230
|
+
|
|
231
|
+
const toolGroup = ToolGroupManager.getToolGroupForViewport(
|
|
232
|
+
viewportId,
|
|
233
|
+
renderingEngineId
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const toolInstance = toolGroup.getToolInstance(config.referencedToolName);
|
|
237
|
+
|
|
238
|
+
config.referencedToolNames.forEach((referencedToolName: string) => {
|
|
239
|
+
const annotations = getAnnotations(referencedToolName, element);
|
|
240
|
+
if (annotations) {
|
|
241
|
+
sculptableAnnotations.push(...annotations);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return toolInstance.filterInteractableAnnotationsForElement(
|
|
246
|
+
element,
|
|
247
|
+
sculptableAnnotations
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Just pass the tool size interaction onto the internal tool size */
|
|
252
|
+
private configureToolSize(evt: EventTypes.InteractionEventType): void {
|
|
253
|
+
const cursorShape = this.registeredShapes.get(this.selectedShape);
|
|
254
|
+
cursorShape.configureToolSize(evt);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Inserts additional handles in sparsely sampled regions of the contour
|
|
259
|
+
*/
|
|
260
|
+
private insertNewHandles(pushedHandles: {
|
|
261
|
+
first: number;
|
|
262
|
+
last: number | undefined;
|
|
263
|
+
}): void {
|
|
264
|
+
const indicesToInsertAfter = this.findNewHandleIndices(pushedHandles);
|
|
265
|
+
let newIndexModifier = 0;
|
|
266
|
+
for (let i = 0; i < indicesToInsertAfter?.length; i++) {
|
|
267
|
+
const insertIndex = indicesToInsertAfter[i] + 1 + newIndexModifier;
|
|
268
|
+
|
|
269
|
+
this.insertHandleRadially(insertIndex);
|
|
270
|
+
newIndexModifier++;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Returns an array of indicies that describe where new handles should be inserted
|
|
276
|
+
*
|
|
277
|
+
* @param pushedHandles - The first and last handles that were pushed.
|
|
278
|
+
*/
|
|
279
|
+
private findNewHandleIndices(pushedHandles: {
|
|
280
|
+
first: number | undefined;
|
|
281
|
+
last: number | undefined;
|
|
282
|
+
}): Array<number> {
|
|
283
|
+
const { points, maxSpacing } = this.sculptData;
|
|
284
|
+
const indicesToInsertAfter = [];
|
|
285
|
+
|
|
286
|
+
for (let i = pushedHandles.first; i <= pushedHandles.last; i++) {
|
|
287
|
+
this.interpolatePointsWithinMaxSpacing(
|
|
288
|
+
i,
|
|
289
|
+
points,
|
|
290
|
+
indicesToInsertAfter,
|
|
291
|
+
maxSpacing
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return indicesToInsertAfter;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Inserts a handle on the surface of the circle defined by toolSize and the mousePoint.
|
|
300
|
+
*
|
|
301
|
+
* @param insertIndex - The index to insert the new handle.
|
|
302
|
+
*/
|
|
303
|
+
private insertHandleRadially(insertIndex: number): void {
|
|
304
|
+
const { points } = this.sculptData;
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
insertIndex > points.length - 1 &&
|
|
308
|
+
this.commonData.isEditingOpenContour
|
|
309
|
+
) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const cursorShape = this.registeredShapes.get(this.selectedShape);
|
|
314
|
+
|
|
315
|
+
const previousIndex = insertIndex - 1;
|
|
316
|
+
const nextIndex = contourIndex(insertIndex, points.length);
|
|
317
|
+
const insertPosition = cursorShape.getInsertPosition(
|
|
318
|
+
previousIndex,
|
|
319
|
+
nextIndex,
|
|
320
|
+
this.sculptData
|
|
321
|
+
);
|
|
322
|
+
const handleData = insertPosition;
|
|
323
|
+
|
|
324
|
+
points.splice(insertIndex, 0, handleData);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Select the freehand tool to be edited
|
|
329
|
+
*
|
|
330
|
+
* @param eventData - Data object associated with the event.
|
|
331
|
+
*/
|
|
332
|
+
private selectFreehandTool(eventData: any): void {
|
|
333
|
+
const closestAnnotationUID =
|
|
334
|
+
this.getClosestFreehandToolOnElement(eventData);
|
|
335
|
+
|
|
336
|
+
if (closestAnnotationUID === undefined) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.commonData.activeAnnotationUID = closestAnnotationUID;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Finds the nearest handle to the mouse cursor for all freehand
|
|
345
|
+
* data on the element.
|
|
346
|
+
*
|
|
347
|
+
* @param eventData - Data object associated with the event.
|
|
348
|
+
*/
|
|
349
|
+
private getClosestFreehandToolOnElement(eventData: any): string {
|
|
350
|
+
const { element } = eventData;
|
|
351
|
+
const enabledElement = getEnabledElement(element);
|
|
352
|
+
const { viewport } = enabledElement;
|
|
353
|
+
const config = this.configuration;
|
|
354
|
+
|
|
355
|
+
const annotations = this.filterSculptableAnnotationsForElement(element);
|
|
356
|
+
|
|
357
|
+
if (!annotations?.length) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const canvasPoints = eventData.currentPoints.canvas;
|
|
362
|
+
|
|
363
|
+
const closest = {
|
|
364
|
+
distance: Infinity,
|
|
365
|
+
toolIndex: undefined,
|
|
366
|
+
annotationUID: undefined,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
for (let i = 0; i < annotations?.length; i++) {
|
|
370
|
+
if (annotations[i].isLocked || !annotations[i].isVisible) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const distanceFromTool = distancePointToContour(
|
|
375
|
+
viewport,
|
|
376
|
+
annotations[i],
|
|
377
|
+
canvasPoints
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
if (distanceFromTool === -1) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (distanceFromTool < closest.distance) {
|
|
385
|
+
closest.distance = distanceFromTool;
|
|
386
|
+
closest.toolIndex = i;
|
|
387
|
+
closest.annotationUID = annotations[i].annotationUID;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
this.commonData.isEditingOpenContour =
|
|
392
|
+
!annotations[closest.toolIndex].data.contour.closed;
|
|
393
|
+
|
|
394
|
+
config.referencedToolName =
|
|
395
|
+
annotations[closest.toolIndex].metadata.toolName;
|
|
396
|
+
|
|
397
|
+
return closest.annotationUID;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Event handler for MOUSE_UP during the active loop.
|
|
402
|
+
*
|
|
403
|
+
* @param evt - The event
|
|
404
|
+
*/
|
|
405
|
+
private endCallback = (
|
|
406
|
+
evt: EventTypes.MouseUpEventType | EventTypes.MouseClickEventType
|
|
407
|
+
): void => {
|
|
408
|
+
const eventData = evt.detail;
|
|
409
|
+
const { element } = eventData;
|
|
410
|
+
const config = this.configuration;
|
|
411
|
+
const enabledElement = getEnabledElement(element);
|
|
412
|
+
|
|
413
|
+
this.isActive = false;
|
|
414
|
+
this.deactivateModify(element);
|
|
415
|
+
resetElementCursor(element);
|
|
416
|
+
|
|
417
|
+
const { renderingEngineId, viewportId } = enabledElement;
|
|
418
|
+
|
|
419
|
+
const toolGroup = ToolGroupManager.getToolGroupForViewport(
|
|
420
|
+
viewportId,
|
|
421
|
+
renderingEngineId
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const toolInstance = toolGroup.getToolInstance(config.referencedToolName);
|
|
425
|
+
|
|
426
|
+
const annotations = this.filterSculptableAnnotationsForElement(element);
|
|
427
|
+
|
|
428
|
+
const activeAnnotation = annotations.find(
|
|
429
|
+
(annotation) =>
|
|
430
|
+
annotation.annotationUID === this.commonData.activeAnnotationUID
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (toolInstance.configuration.calculateStats) {
|
|
434
|
+
activeAnnotation.invalidated = true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
triggerAnnotationModified(activeAnnotation, element);
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Event handler for MOUSE_DRAG during the active loop.
|
|
442
|
+
*
|
|
443
|
+
* @param evt - The event
|
|
444
|
+
*/
|
|
445
|
+
private dragCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
446
|
+
const eventData = evt.detail;
|
|
447
|
+
const element = eventData.element;
|
|
448
|
+
|
|
449
|
+
this.updateCursor(evt);
|
|
450
|
+
|
|
451
|
+
const annotations = this.filterSculptableAnnotationsForElement(element);
|
|
452
|
+
const activeAnnotation = annotations.find(
|
|
453
|
+
(annotation) =>
|
|
454
|
+
annotation.annotationUID === this.commonData.activeAnnotationUID
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
if (!annotations?.length || !this.isActive) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const points = activeAnnotation.data.contour.polyline;
|
|
462
|
+
|
|
463
|
+
this.sculpt(eventData, points);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Attaches event listeners to the element such that is is visible, modifiable, and new data can be created.
|
|
468
|
+
* @param element - - The viewport element to attach event listeners to.
|
|
469
|
+
*/
|
|
470
|
+
protected activateModify(element: HTMLDivElement): void {
|
|
471
|
+
element.addEventListener(
|
|
472
|
+
Events.MOUSE_UP,
|
|
473
|
+
this.endCallback as EventListener
|
|
474
|
+
);
|
|
475
|
+
element.addEventListener(
|
|
476
|
+
Events.MOUSE_CLICK,
|
|
477
|
+
this.endCallback as EventListener
|
|
478
|
+
);
|
|
479
|
+
element.addEventListener(
|
|
480
|
+
Events.MOUSE_DRAG,
|
|
481
|
+
this.dragCallback as EventListener
|
|
482
|
+
);
|
|
483
|
+
element.addEventListener(
|
|
484
|
+
Events.TOUCH_TAP,
|
|
485
|
+
this.endCallback as EventListener
|
|
486
|
+
);
|
|
487
|
+
element.addEventListener(
|
|
488
|
+
Events.TOUCH_END,
|
|
489
|
+
this.endCallback as EventListener
|
|
490
|
+
);
|
|
491
|
+
element.addEventListener(
|
|
492
|
+
Events.TOUCH_DRAG,
|
|
493
|
+
this.dragCallback as EventListener
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Removes event listeners from the element.
|
|
499
|
+
* @param element - The viewport element to remove event listeners from.
|
|
500
|
+
*/
|
|
501
|
+
protected deactivateModify(element: HTMLDivElement): void {
|
|
502
|
+
element.removeEventListener(
|
|
503
|
+
Events.MOUSE_UP,
|
|
504
|
+
this.endCallback as EventListener
|
|
505
|
+
);
|
|
506
|
+
element.removeEventListener(
|
|
507
|
+
Events.MOUSE_CLICK,
|
|
508
|
+
this.endCallback as EventListener
|
|
509
|
+
);
|
|
510
|
+
element.removeEventListener(
|
|
511
|
+
Events.MOUSE_DRAG,
|
|
512
|
+
this.dragCallback as EventListener
|
|
513
|
+
);
|
|
514
|
+
element.removeEventListener(
|
|
515
|
+
Events.TOUCH_TAP,
|
|
516
|
+
this.endCallback as EventListener
|
|
517
|
+
);
|
|
518
|
+
element.removeEventListener(
|
|
519
|
+
Events.TOUCH_END,
|
|
520
|
+
this.endCallback as EventListener
|
|
521
|
+
);
|
|
522
|
+
element.removeEventListener(
|
|
523
|
+
Events.TOUCH_DRAG,
|
|
524
|
+
this.dragCallback as EventListener
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Sets the tool shape to the specified tool
|
|
530
|
+
*/
|
|
531
|
+
public setToolShape(toolShape: string): void {
|
|
532
|
+
this.selectedShape =
|
|
533
|
+
this.registeredShapes.get(toolShape) ?? CircleSculptCursor.shapeName;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Renders the cursor annotation on screen so that the user can choose the
|
|
538
|
+
* annotation size.
|
|
539
|
+
*/
|
|
540
|
+
renderAnnotation(
|
|
541
|
+
enabledElement: Types.IEnabledElement,
|
|
542
|
+
svgDrawingHelper: SVGDrawingHelper
|
|
543
|
+
): void {
|
|
544
|
+
const { viewport } = enabledElement;
|
|
545
|
+
const { element } = viewport;
|
|
546
|
+
|
|
547
|
+
const viewportIdsToRender = this.commonData.viewportIdsToRender;
|
|
548
|
+
|
|
549
|
+
if (
|
|
550
|
+
!this.commonData.canvasLocation ||
|
|
551
|
+
this.mode !== ToolModes.Active ||
|
|
552
|
+
!viewportIdsToRender.includes(viewport.id)
|
|
553
|
+
) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const annotations = this.filterSculptableAnnotationsForElement(element);
|
|
558
|
+
|
|
559
|
+
if (!annotations?.length) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const styleSpecifier: StyleSpecifier = {
|
|
564
|
+
toolGroupId: this.toolGroupId,
|
|
565
|
+
toolName: this.getToolName(),
|
|
566
|
+
viewportId: enabledElement.viewport.id,
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
let color = getStyleProperty(
|
|
570
|
+
'color',
|
|
571
|
+
styleSpecifier,
|
|
572
|
+
AnnotationStyleStates.Default,
|
|
573
|
+
this.mode
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
if (this.isActive) {
|
|
577
|
+
color = getStyleProperty(
|
|
578
|
+
'color',
|
|
579
|
+
styleSpecifier,
|
|
580
|
+
AnnotationStyleStates.Highlighted,
|
|
581
|
+
this.mode
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const cursorShape = this.registeredShapes.get(this.selectedShape);
|
|
586
|
+
|
|
587
|
+
cursorShape.renderShape(svgDrawingHelper, this.commonData.canvasLocation, {
|
|
588
|
+
color,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Function calculates the index of a contour given a position `i` and the length of the contour.
|
|
595
|
+
* It ensures that the resulting index is within the bounds of the contour by wrapping around if needed.
|
|
596
|
+
* This function is useful for obtaining neighboring indices or other related indices within the contour,
|
|
597
|
+
* such as for navigating or accessing elements in a circular or looped structure
|
|
598
|
+
*/
|
|
599
|
+
export const contourIndex = (i: number, length: number): number => {
|
|
600
|
+
return (i + length) % length;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
SculptorTool.toolName = 'SculptorTool';
|
|
604
|
+
export default SculptorTool;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import { ContourAnnotationData } from '../types';
|
|
3
|
+
import { point } from '../utilities/math';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calculates the shortest distance from the provided point to any contour
|
|
7
|
+
* point in the given annotation.
|
|
8
|
+
*/
|
|
9
|
+
export const distancePointToContour = (
|
|
10
|
+
viewport: Types.IViewport,
|
|
11
|
+
annotation: ContourAnnotationData,
|
|
12
|
+
coords: Types.Point2
|
|
13
|
+
): number => {
|
|
14
|
+
if (!annotation?.data?.contour?.polyline?.length) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { polyline } = annotation.data.contour;
|
|
18
|
+
const { length } = polyline;
|
|
19
|
+
|
|
20
|
+
let distance = Infinity;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < length; i++) {
|
|
23
|
+
const canvasPoint = viewport.worldToCanvas(polyline[i]);
|
|
24
|
+
const distanceToPoint = point.distanceToPoint(canvasPoint, coords);
|
|
25
|
+
|
|
26
|
+
distance = Math.min(distance, distanceToPoint);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If an error caused distance not to be calculated, return undefined.
|
|
30
|
+
if (distance === Infinity || isNaN(distance)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return distance;
|
|
35
|
+
};
|
package/src/tools/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import SegmentationIntersectionTool from './SegmentationIntersectionTool';
|
|
|
17
17
|
import ReferenceCursors from './ReferenceCursors';
|
|
18
18
|
import ReferenceLines from './ReferenceLinesTool';
|
|
19
19
|
import ScaleOverlayTool from './ScaleOverlayTool';
|
|
20
|
+
import SculptorTool from './SculptorTool';
|
|
20
21
|
|
|
21
22
|
// Annotation tools
|
|
22
23
|
import BidirectionalTool from './annotation/BidirectionalTool';
|
|
@@ -110,5 +111,6 @@ export {
|
|
|
110
111
|
PaintFillTool,
|
|
111
112
|
ScaleOverlayTool,
|
|
112
113
|
OrientationMarkerTool,
|
|
114
|
+
SculptorTool,
|
|
113
115
|
SegmentSelectTool,
|
|
114
116
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { SVGDrawingHelper, EventTypes, ContourAnnotation } from '.';
|
|
3
|
+
import type { PushedHandles } from '../tools/SculptorTool/CircleSculptCursor';
|
|
4
|
+
import type { SculptData } from '../tools/SculptorTool';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This interface defines a contract for implementing various shapes within the sculptor tool.
|
|
8
|
+
* Classes such as `CircleSculptCursor` adhere to this interface,
|
|
9
|
+
* providing specific implementations for sculptor tools to utilize the shape
|
|
10
|
+
* during sculpting operations.
|
|
11
|
+
*/
|
|
12
|
+
export interface ISculptToolShape {
|
|
13
|
+
/**
|
|
14
|
+
* Used to render shapes supported for sculptor tool
|
|
15
|
+
*
|
|
16
|
+
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
|
|
17
|
+
* @param canvasLocation - Current canvas location in canvas index coordinates
|
|
18
|
+
* @param options - Options for drawing shapes
|
|
19
|
+
*/
|
|
20
|
+
renderShape(
|
|
21
|
+
svgDrawingHelper: SVGDrawingHelper,
|
|
22
|
+
canvasLocation: Types.Point2,
|
|
23
|
+
options
|
|
24
|
+
): void;
|
|
25
|
+
/**
|
|
26
|
+
* Pushes the points radially away from the mouse if they are
|
|
27
|
+
* contained within the shape defined by the freehandSculpter tool
|
|
28
|
+
*/
|
|
29
|
+
pushHandles(viewport: Types.IViewport, sculptData: SculptData): PushedHandles;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Function configures the tool size
|
|
33
|
+
*/
|
|
34
|
+
configureToolSize(evt: EventTypes.InteractionEventType): void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Updates the tool size
|
|
38
|
+
* @param canvasCoords - Current canvas points
|
|
39
|
+
*/
|
|
40
|
+
updateToolSize(
|
|
41
|
+
canvasCoords: Types.Point2,
|
|
42
|
+
viewport: Types.IViewport,
|
|
43
|
+
activeAnnotation: ContourAnnotation
|
|
44
|
+
): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function returns max spacing between two handles
|
|
48
|
+
* @param minSpacing -
|
|
49
|
+
*/
|
|
50
|
+
getMaxSpacing(minSpacing: number): number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Function returns the the position to insert new handle
|
|
54
|
+
* @param previousIndex - Previous handle index
|
|
55
|
+
* @param nextIndex - Next handle index
|
|
56
|
+
* @param sculptData - Sculpt data
|
|
57
|
+
*/
|
|
58
|
+
getInsertPosition(
|
|
59
|
+
previousIndex: number,
|
|
60
|
+
nextIndex: number,
|
|
61
|
+
sculptData: SculptData
|
|
62
|
+
): Types.Point3;
|
|
63
|
+
}
|