larvitar 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/larvitar.js +5 -3
  3. package/dist/larvitar.js.map +1 -1
  4. package/package.json +6 -2
  5. package/.github/workflows/build-docs.yml +0 -59
  6. package/.github/workflows/codeql-analysis.yml +0 -71
  7. package/.github/workflows/deploy.yml +0 -37
  8. package/.vscode/settings.json +0 -4
  9. package/CODE_OF_CONDUCT.md +0 -76
  10. package/MIGRATION.md +0 -25
  11. package/bundler/webpack.common.js +0 -27
  12. package/bundler/webpack.dev.js +0 -23
  13. package/bundler/webpack.prod.js +0 -19
  14. package/decs.d.ts +0 -12
  15. package/imaging/MetaDataReadable.ts +0 -42
  16. package/imaging/MetaDataTypes.ts +0 -3491
  17. package/imaging/dataDictionary.json +0 -21866
  18. package/imaging/imageAnonymization.ts +0 -135
  19. package/imaging/imageColormaps.ts +0 -217
  20. package/imaging/imageContours.ts +0 -196
  21. package/imaging/imageIo.ts +0 -251
  22. package/imaging/imageLayers.ts +0 -121
  23. package/imaging/imageLoading.ts +0 -299
  24. package/imaging/imageParsing.ts +0 -444
  25. package/imaging/imagePresets.ts +0 -156
  26. package/imaging/imageRendering.ts +0 -1091
  27. package/imaging/imageReslice.ts +0 -87
  28. package/imaging/imageStore.ts +0 -487
  29. package/imaging/imageTags.ts +0 -609
  30. package/imaging/imageTools.js +0 -708
  31. package/imaging/imageUtils.ts +0 -1079
  32. package/imaging/loaders/commonLoader.ts +0 -275
  33. package/imaging/loaders/dicomLoader.ts +0 -66
  34. package/imaging/loaders/fileLoader.ts +0 -71
  35. package/imaging/loaders/multiframeLoader.ts +0 -435
  36. package/imaging/loaders/nrrdLoader.ts +0 -630
  37. package/imaging/loaders/resliceLoader.ts +0 -205
  38. package/imaging/monitors/memory.ts +0 -151
  39. package/imaging/monitors/performance.ts +0 -34
  40. package/imaging/parsers/ecg.ts +0 -54
  41. package/imaging/parsers/nrrd.js +0 -485
  42. package/imaging/tools/README.md +0 -27
  43. package/imaging/tools/custom/4dSliceScrollTool.js +0 -146
  44. package/imaging/tools/custom/BorderMagnifyTool.js +0 -99
  45. package/imaging/tools/custom/contourTool.js +0 -1884
  46. package/imaging/tools/custom/diameterTool.js +0 -141
  47. package/imaging/tools/custom/editMaskTool.js +0 -141
  48. package/imaging/tools/custom/ellipticalRoiOverlayTool.js +0 -534
  49. package/imaging/tools/custom/polygonSegmentationMixin.js +0 -245
  50. package/imaging/tools/custom/polylineScissorsTool.js +0 -59
  51. package/imaging/tools/custom/rectangleRoiOverlayTool.js +0 -564
  52. package/imaging/tools/custom/seedTool.js +0 -342
  53. package/imaging/tools/custom/setLabelMap3D.ts +0 -242
  54. package/imaging/tools/custom/thresholdsBrushTool.js +0 -161
  55. package/imaging/tools/default.ts +0 -594
  56. package/imaging/tools/interaction.ts +0 -266
  57. package/imaging/tools/io.ts +0 -229
  58. package/imaging/tools/main.ts +0 -424
  59. package/imaging/tools/segmentation.ts +0 -532
  60. package/imaging/tools/segmentations.md +0 -38
  61. package/imaging/tools/state.ts +0 -74
  62. package/imaging/tools/strategies/eraseFreehand.js +0 -76
  63. package/imaging/tools/strategies/fillFreehand.js +0 -79
  64. package/imaging/tools/strategies/index.js +0 -2
  65. package/imaging/tools/types.d.ts +0 -243
  66. package/imaging/types.d.ts +0 -200
  67. package/imaging/waveforms/ecg.ts +0 -191
  68. package/index.ts +0 -431
  69. package/jsdoc.json +0 -52
  70. package/rollup.config.js +0 -51
  71. package/template/.gitkeep +0 -0
  72. package/tsconfig.json +0 -102
@@ -1,1884 +0,0 @@
1
- /** @module imaging/tools/custom/contourTool
2
- * @desc This file provides functionalities for
3
- * rendering segmentation contours with a
4
- * custom cornestone tool
5
- */
6
-
7
- // external libraries
8
- import cornerstone from "cornerstone-core";
9
- import csTools from "cornerstone-tools";
10
- import { each, map } from "lodash";
11
-
12
- // internal libraries
13
- import { addToolStateSingleSlice } from "../../imageTools";
14
-
15
- // cornerstone tools imports
16
- const external = csTools.external;
17
- const EVENTS = csTools.EVENTS;
18
- const BaseAnnotationTool = csTools.importInternal("base/BaseAnnotationTool");
19
- const getToolState = csTools.getToolState;
20
- const addToolState = csTools.addToolState;
21
- const removeToolState = csTools.removeToolState;
22
- const toolStyle = csTools.toolStyle;
23
- const toolColors = csTools.toolColors;
24
- const triggerEvent = csTools.importInternal("util/triggerEvent");
25
- const pointInsideBoundingBox = csTools.importInternal(
26
- "util/pointInsideBoundingBox"
27
- );
28
- const calculateSUV = csTools.importInternal("util/calculateSUV");
29
- const numbersWithCommas = csTools.importInternal("util/numbersWithCommas");
30
- const getNewContext = csTools.importInternal("drawing/getNewContext");
31
- const draw = csTools.importInternal("drawing/draw");
32
- const drawJoinedLines = csTools.importInternal("drawing/drawJoinedLines");
33
- const drawHandles = csTools.importInternal("drawing/drawHandles");
34
- const drawLinkedTextBox = csTools.importInternal("drawing/drawLinkedTextBox");
35
- const clipToBox = csTools.importInternal("util/clip");
36
- const freehandRoiCursor = csTools.importInternal("cursors/freehandRoiCursor");
37
- const getLogger = csTools.importInternal("util/getLogger");
38
- const throttle = csTools.importInternal("util/throttle");
39
- const logger = getLogger("tools:annotation:FreehandRoiTool");
40
- const freehandUtils = csTools.importInternal("util/freehandUtils");
41
-
42
- // TODO check how to import these
43
- // const toolCursor = csTools.importInternal("store/setToolCursor");
44
- // const hideToolCursor = toolCursor.hideToolCursor;
45
- // const setToolCursor = toolCursor.setToolCursor;
46
- // const findAndMoveHelpers = csTools.importInternal("util/findAndMoveHelpers");
47
- // const moveHandleNearImagePoint = findAndMoveHelpers.moveHandleNearImagePoint;
48
-
49
- const {
50
- insertOrDelete,
51
- freehandArea,
52
- calculateFreehandStatistics,
53
- freehandIntersect,
54
- FreehandHandleData
55
- } = freehandUtils;
56
-
57
- const state = {
58
- // Global
59
- globalTools: {},
60
- globalToolChangeHistory: [],
61
- // Tracking
62
- enabledElements: [],
63
- tools: [],
64
- isToolLocked: false,
65
- activeMultiPartTool: null,
66
- mousePositionImage: {},
67
- // Settings
68
- clickProximity: 6,
69
- touchProximity: 10,
70
- handleRadius: 6,
71
- deleteIfHandleOutsideImage: true,
72
- preventHandleOutsideImage: false,
73
- // Cursor
74
- svgCursorUrl: null
75
- };
76
-
77
- /**
78
- * @public
79
- * @class ContoursTool
80
- * @memberof Tools.Annotation
81
- * @classdesc Tool for drawing a set of contours
82
- * @extends Tools.Base.BaseAnnotationTool
83
- */
84
- export class ContoursTool extends BaseAnnotationTool {
85
- constructor(props = {}) {
86
- const defaultProps = {
87
- name: "ContoursTool",
88
- supportedInteractionTypes: ["Mouse", "Touch"],
89
- configuration: defaultFreehandConfiguration(),
90
- svgCursor: freehandRoiCursor
91
- };
92
-
93
- super(props, defaultProps);
94
-
95
- this.initializeContours(props.contoursParsedData, props.segmentationName);
96
-
97
- this.isMultiPartTool = true;
98
-
99
- this._drawing = false;
100
- this._dragging = false;
101
- this._modifying = false;
102
-
103
- // Create bound callback functions for private event loops
104
- this._drawingMouseDownCallback = this._drawingMouseDownCallback.bind(this);
105
- this._drawingMouseMoveCallback = this._drawingMouseMoveCallback.bind(this);
106
- this._drawingMouseDragCallback = this._drawingMouseDragCallback.bind(this);
107
- this._drawingMouseUpCallback = this._drawingMouseUpCallback.bind(this);
108
- this._drawingMouseDoubleClickCallback =
109
- this._drawingMouseDoubleClickCallback.bind(this);
110
- this._editMouseUpCallback = this._editMouseUpCallback.bind(this);
111
- this._editMouseDragCallback = this._editMouseDragCallback.bind(this);
112
-
113
- this._drawingTouchStartCallback =
114
- this._drawingTouchStartCallback.bind(this);
115
- this._drawingTouchDragCallback = this._drawingTouchDragCallback.bind(this);
116
- this._drawingDoubleTapClickCallback =
117
- this._drawingDoubleTapClickCallback.bind(this);
118
- this._editTouchDragCallback = this._editTouchDragCallback.bind(this);
119
-
120
- this.throttledUpdateCachedStats = throttle(this.updateCachedStats, 110);
121
- }
122
-
123
- initializeContours(contourData, segmentationName) {
124
- var elements = cornerstone.getEnabledElements();
125
- each(elements, el => {
126
- var slices = contourData[el.element.id][segmentationName];
127
-
128
- for (var slice in slices) {
129
- var linesPerSlice =
130
- contourData[el.element.id][segmentationName][slice].lines;
131
- var lines = map(linesPerSlice, line => {
132
- let dataToInject = {
133
- visible: true,
134
- active: false,
135
- invalidated: false,
136
- color: "#FF0000",
137
- handles: {
138
- points: line
139
- }
140
- };
141
- return dataToInject;
142
- });
143
-
144
- addToolStateSingleSlice(el.element, "ContoursTool", lines, slice);
145
- }
146
- });
147
- }
148
-
149
- createNewMeasurement(eventData) {
150
- const goodEventData =
151
- eventData && eventData.currentPoints && eventData.currentPoints.image;
152
-
153
- if (!goodEventData) {
154
- logger.error(
155
- `required eventData not supplied to tool ${this.name}'s createNewMeasurement`
156
- );
157
-
158
- return;
159
- }
160
-
161
- const measurementData = {
162
- visible: true,
163
- active: true,
164
- invalidated: true,
165
- color: undefined,
166
- handles: {
167
- points: []
168
- }
169
- };
170
-
171
- measurementData.handles.textBox = {
172
- active: false,
173
- hasMoved: false,
174
- movesIndependently: false,
175
- drawnIndependently: true,
176
- allowedOutsideImage: true,
177
- hasBoundingBox: true
178
- };
179
-
180
- return measurementData;
181
- }
182
-
183
- /**
184
- *
185
- *
186
- * @param {*} element element
187
- * @param {*} data data
188
- * @param {*} coords coords
189
- * @returns {Boolean}
190
- */
191
- pointNearTool(element, data, coords) {
192
- const validParameters = data && data.handles && data.handles.points;
193
-
194
- if (!validParameters) {
195
- throw new Error(
196
- `invalid parameters supplied to tool ${this.name}'s pointNearTool`
197
- );
198
- }
199
-
200
- if (!validParameters || data.visible === false) {
201
- return false;
202
- }
203
-
204
- const isPointNearTool = this._pointNearHandle(element, data, coords);
205
-
206
- if (isPointNearTool !== undefined) {
207
- return true;
208
- }
209
-
210
- return false;
211
- }
212
-
213
- /**
214
- * @param {*} element
215
- * @param {*} data
216
- * @param {*} coords
217
- * @returns {number} the distance in px from the provided coordinates to the
218
- * closest rendered portion of the annotation. -1 if the distance cannot be
219
- * calculated.
220
- */
221
- distanceFromPoint(element, data, coords) {
222
- let distance = Infinity;
223
-
224
- for (let i = 0; i < data.handles.points.length; i++) {
225
- const distanceI = external.cornerstoneMath.point.distance(
226
- data.handles.points[i],
227
- coords
228
- );
229
-
230
- distance = Math.min(distance, distanceI);
231
- }
232
-
233
- // If an error caused distance not to be calculated, return -1.
234
- if (distance === Infinity) {
235
- return -1;
236
- }
237
-
238
- return distance;
239
- }
240
-
241
- /**
242
- * @param {*} element
243
- * @param {*} data
244
- * @param {*} coords
245
- * @returns {number} the distance in canvas units from the provided coordinates to the
246
- * closest rendered portion of the annotation. -1 if the distance cannot be
247
- * calculated.
248
- */
249
- distanceFromPointCanvas(element, data, coords) {
250
- let distance = Infinity;
251
-
252
- if (!data) {
253
- return -1;
254
- }
255
-
256
- const canvasCoords = external.cornerstone.pixelToCanvas(element, coords);
257
-
258
- const points = data.handles.points;
259
-
260
- for (let i = 0; i < points.length; i++) {
261
- const handleCanvas = external.cornerstone.pixelToCanvas(
262
- element,
263
- points[i]
264
- );
265
-
266
- const distanceI = external.cornerstoneMath.point.distance(
267
- handleCanvas,
268
- canvasCoords
269
- );
270
-
271
- distance = Math.min(distance, distanceI);
272
- }
273
-
274
- // If an error caused distance not to be calculated, return -1.
275
- if (distance === Infinity) {
276
- return -1;
277
- }
278
-
279
- return distance;
280
- }
281
-
282
- /**
283
- *
284
- *
285
- *
286
- * @param {Object} image image
287
- * @param {Object} element element
288
- * @param {Object} data data
289
- *
290
- * @returns {void} void
291
- */
292
- updateCachedStats(image, element, data) {
293
- // Define variables for the area and mean/standard deviation
294
- let meanStdDev, meanStdDevSUV;
295
-
296
- const seriesModule = external.cornerstone.metaData.get(
297
- "generalSeriesModule",
298
- image.imageId
299
- );
300
- const modality = seriesModule ? seriesModule.modality : null;
301
-
302
- const points = data.handles.points;
303
- // If the data has been invalidated, and the tool is not currently active,
304
- // We need to calculate it again.
305
-
306
- // Retrieve the bounds of the ROI in image coordinates
307
- const bounds = {
308
- left: points[0].x,
309
- right: points[0].x,
310
- bottom: points[0].y,
311
- top: points[0].x
312
- };
313
-
314
- for (let i = 0; i < points.length; i++) {
315
- bounds.left = Math.min(bounds.left, points[i].x);
316
- bounds.right = Math.max(bounds.right, points[i].x);
317
- bounds.bottom = Math.min(bounds.bottom, points[i].y);
318
- bounds.top = Math.max(bounds.top, points[i].y);
319
- }
320
-
321
- const polyBoundingBox = {
322
- left: bounds.left,
323
- top: bounds.bottom,
324
- width: Math.abs(bounds.right - bounds.left),
325
- height: Math.abs(bounds.top - bounds.bottom)
326
- };
327
-
328
- // Store the bounding box information for the text box
329
- data.polyBoundingBox = polyBoundingBox;
330
-
331
- // First, make sure this is not a color image, since no mean / standard
332
- // Deviation will be calculated for color images.
333
- if (!image.color) {
334
- // Retrieve the array of pixels that the ROI bounds cover
335
- const pixels = external.cornerstone.getPixels(
336
- element,
337
- polyBoundingBox.left,
338
- polyBoundingBox.top,
339
- polyBoundingBox.width,
340
- polyBoundingBox.height
341
- );
342
-
343
- // Calculate the mean & standard deviation from the pixels and the object shape
344
- meanStdDev = calculateFreehandStatistics.call(
345
- this,
346
- pixels,
347
- polyBoundingBox,
348
- data.handles.points
349
- );
350
-
351
- if (modality === "PT") {
352
- // If the image is from a PET scan, use the DICOM tags to
353
- // Calculate the SUV from the mean and standard deviation.
354
-
355
- // Note that because we are using modality pixel values from getPixels, and
356
- // The calculateSUV routine also rescales to modality pixel values, we are first
357
- // Returning the values to storedPixel values before calcuating SUV with them.
358
- // TODO: Clean this up? Should we add an option to not scale in calculateSUV?
359
- meanStdDevSUV = {
360
- mean: calculateSUV(
361
- image,
362
- (meanStdDev.mean - image.intercept) / image.slope
363
- ),
364
- stdDev: calculateSUV(
365
- image,
366
- (meanStdDev.stdDev - image.intercept) / image.slope
367
- )
368
- };
369
- }
370
-
371
- // If the mean and standard deviation values are sane, store them for later retrieval
372
- if (meanStdDev && !isNaN(meanStdDev.mean)) {
373
- data.meanStdDev = meanStdDev;
374
- data.meanStdDevSUV = meanStdDevSUV;
375
- }
376
- }
377
-
378
- // Retrieve the pixel spacing values, and if they are not
379
- // Real non-zero values, set them to 1
380
- const columnPixelSpacing = image.columnPixelSpacing || 1;
381
- const rowPixelSpacing = image.rowPixelSpacing || 1;
382
- const scaling = columnPixelSpacing * rowPixelSpacing;
383
-
384
- const area = freehandArea(data.handles.points, scaling);
385
-
386
- // If the area value is sane, store it for later retrieval
387
- if (!isNaN(area)) {
388
- data.area = area;
389
- }
390
-
391
- // Set the invalidated flag to false so that this data won't automatically be recalculated
392
- data.invalidated = false;
393
- }
394
-
395
- /**
396
- *
397
- *
398
- * @param {*} evt
399
- * @returns {undefined}
400
- */
401
- renderToolData(evt) {
402
- const eventData = evt.detail;
403
-
404
- // If we have no toolState for this element, return immediately as there is nothing to do
405
- const toolState = getToolState(evt.currentTarget, this.name);
406
-
407
- if (!toolState) {
408
- return;
409
- }
410
-
411
- const { image, element } = eventData;
412
- const config = this.configuration;
413
- const seriesModule = external.cornerstone.metaData.get(
414
- "generalSeriesModule",
415
- image.imageId
416
- );
417
- const modality = seriesModule ? seriesModule.modality : null;
418
-
419
- // We have tool data for this element - iterate over each one and draw it
420
- const context = getNewContext(eventData.canvasContext.canvas);
421
- const lineWidth = toolStyle.getToolWidth();
422
-
423
- for (let i = 0; i < toolState.data.length; i++) {
424
- const data = toolState.data[i];
425
-
426
- if (data.visible === false) {
427
- continue;
428
- }
429
-
430
- draw(context, context => {
431
- let color = toolColors.getColorIfActive(data);
432
- let fillColor;
433
-
434
- if (data.active) {
435
- if (data.handles.invalidHandlePlacement) {
436
- color = config.invalidColor;
437
- fillColor = config.invalidColor;
438
- } else {
439
- color = toolColors.getColorIfActive(data);
440
- fillColor = toolColors.getFillColor();
441
- }
442
- } else {
443
- fillColor = toolColors.getToolColor();
444
- }
445
-
446
- if (data.handles.points.length) {
447
- for (let j = 0; j < data.handles.points.length; j++) {
448
- const lines = [...data.handles.points[j].lines];
449
- const points = data.handles.points;
450
-
451
- if (j === points.length - 1 && !data.polyBoundingBox) {
452
- // If it's still being actively drawn, keep the last line to
453
- // The mouse location
454
- lines.push(config.mouseLocation.handles.start);
455
- }
456
- drawJoinedLines(context, element, data.handles.points[j], lines, {
457
- color
458
- });
459
- }
460
- }
461
-
462
- // Draw handles
463
-
464
- const options = {
465
- color,
466
- fill: fillColor
467
- };
468
-
469
- if (config.alwaysShowHandles || (data.active && data.polyBoundingBox)) {
470
- // Render all handles
471
- options.handleRadius = config.activeHandleRadius;
472
- drawHandles(context, eventData, data.handles.points, options);
473
- }
474
-
475
- if (data.canComplete) {
476
- // Draw large handle at the origin if can complete drawing
477
- options.handleRadius = config.completeHandleRadius;
478
- const handle = data.handles.points[0];
479
- drawHandles(context, eventData, [handle], options);
480
- }
481
-
482
- if (data.active && !data.polyBoundingBox) {
483
- // Draw handle at origin and at mouse if actively drawing
484
- options.handleRadius = config.activeHandleRadius;
485
- drawHandles(
486
- context,
487
- eventData,
488
- config.mouseLocation.handles,
489
- options
490
- );
491
-
492
- const firstHandle = data.handles.points[0];
493
- drawHandles(context, eventData, [firstHandle], options);
494
- }
495
-
496
- // Update textbox stats
497
- if (data.invalidated === true && !data.active) {
498
- if (data.meanStdDev && data.meanStdDevSUV && data.area) {
499
- this.throttledUpdateCachedStats(image, element, data);
500
- } else {
501
- this.updateCachedStats(image, element, data);
502
- }
503
- }
504
-
505
- // Only render text if polygon ROI has been completed and freehand 'shiftKey' mode was not used:
506
- if (data.polyBoundingBox && !data.handles.textBox.freehand) {
507
- // If the textbox has not been moved by the user, it should be displayed on the right-most
508
- // Side of the tool.
509
- if (!data.handles.textBox.hasMoved) {
510
- // Find the rightmost side of the polyBoundingBox at its vertical center, and place the textbox here
511
- // Note that this calculates it in image coordinates
512
- data.handles.textBox.x =
513
- data.polyBoundingBox.left + data.polyBoundingBox.width;
514
- data.handles.textBox.y =
515
- data.polyBoundingBox.top + data.polyBoundingBox.height / 2;
516
- }
517
-
518
- const text = textBoxText.call(this, data);
519
-
520
- drawLinkedTextBox(
521
- context,
522
- element,
523
- data.handles.textBox,
524
- text,
525
- data.handles.points,
526
- textBoxAnchorPoints,
527
- color,
528
- lineWidth,
529
- 0,
530
- true
531
- );
532
- }
533
- });
534
- }
535
-
536
- function textBoxText(data) {
537
- const { meanStdDev, meanStdDevSUV, area } = data;
538
- // Define an array to store the rows of text for the textbox
539
- const textLines = [];
540
-
541
- // If the mean and standard deviation values are present, display them
542
- if (meanStdDev && meanStdDev.mean !== undefined) {
543
- // If the modality is CT, add HU to denote Hounsfield Units
544
- let moSuffix = "";
545
-
546
- if (modality === "CT") {
547
- moSuffix = "HU";
548
- }
549
- data.unit = moSuffix;
550
-
551
- // Create a line of text to display the mean and any units that were specified (i.e. HU)
552
- let meanText = `Mean: ${numbersWithCommas(
553
- meanStdDev.mean.toFixed(2)
554
- )} ${moSuffix}`;
555
- // Create a line of text to display the standard deviation and any units that were specified (i.e. HU)
556
- let stdDevText = `StdDev: ${numbersWithCommas(
557
- meanStdDev.stdDev.toFixed(2)
558
- )} ${moSuffix}`;
559
-
560
- // If this image has SUV values to display, concatenate them to the text line
561
- if (meanStdDevSUV && meanStdDevSUV.mean !== undefined) {
562
- const SUVtext = " SUV: ";
563
-
564
- meanText +=
565
- SUVtext + numbersWithCommas(meanStdDevSUV.mean.toFixed(2));
566
- stdDevText +=
567
- SUVtext + numbersWithCommas(meanStdDevSUV.stdDev.toFixed(2));
568
- }
569
-
570
- // Add these text lines to the array to be displayed in the textbox
571
- textLines.push(meanText);
572
- textLines.push(stdDevText);
573
- }
574
-
575
- // If the area is a sane value, display it
576
- if (area) {
577
- // Determine the area suffix based on the pixel spacing in the image.
578
- // If pixel spacing is present, use millimeters. Otherwise, use pixels.
579
- // This uses Char code 178 for a superscript 2
580
- let suffix = ` mm${String.fromCharCode(178)}`;
581
-
582
- if (!image.rowPixelSpacing || !image.columnPixelSpacing) {
583
- suffix = ` pixels${String.fromCharCode(178)}`;
584
- }
585
-
586
- // Create a line of text to display the area and its units
587
- const areaText = `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
588
-
589
- // Add this text line to the array to be displayed in the textbox
590
- textLines.push(areaText);
591
- }
592
-
593
- return textLines;
594
- }
595
-
596
- function textBoxAnchorPoints(handles) {
597
- return handles;
598
- }
599
- }
600
-
601
- addNewMeasurement(evt) {
602
- const eventData = evt.detail;
603
-
604
- this._startDrawing(evt);
605
- this._addPoint(eventData);
606
-
607
- preventPropagation(evt);
608
- }
609
-
610
- preMouseDownCallback(evt) {
611
- const eventData = evt.detail;
612
- const nearby = this._pointNearHandleAllTools(eventData);
613
-
614
- if (eventData.event.ctrlKey) {
615
- if (nearby !== undefined && nearby.handleNearby.hasBoundingBox) {
616
- // Ctrl + clicked textBox, do nothing but still consume event.
617
- } else {
618
- insertOrDelete.call(this, evt, nearby);
619
- }
620
-
621
- preventPropagation(evt);
622
-
623
- return true;
624
- }
625
-
626
- return false;
627
- }
628
-
629
- handleSelectedCallback(evt, toolData, handle, interactionType = "mouse") {
630
- const { element } = evt.detail;
631
- const toolState = getToolState(element, this.name);
632
- console.info(interactionType);
633
- // if (handle.hasBoundingBox) {
634
- // // Use default move handler.
635
- // moveHandleNearImagePoint(evt, this, toolData, handle, interactionType);
636
-
637
- // return;
638
- // }
639
-
640
- const config = this.configuration;
641
-
642
- config.dragOrigin = {
643
- x: handle.x,
644
- y: handle.y
645
- };
646
-
647
- // Iterating over handles of all toolData instances to find the indices of the selected handle
648
- for (let toolIndex = 0; toolIndex < toolState.data.length; toolIndex++) {
649
- const points = toolState.data[toolIndex].handles.points;
650
-
651
- for (let p = 0; p < points.length; p++) {
652
- if (points[p] === handle) {
653
- config.currentHandle = p;
654
- config.currentTool = toolIndex;
655
- }
656
- }
657
- }
658
-
659
- this._modifying = true;
660
-
661
- this._activateModify(element);
662
-
663
- // Interupt eventDispatchers
664
- preventPropagation(evt);
665
- }
666
-
667
- /**
668
- * Event handler for MOUSE_MOVE during drawing event loop.
669
- *
670
- * @event
671
- * @param {Object} evt - The event.
672
- * @returns {undefined}
673
- */
674
- _drawingMouseMoveCallback(evt) {
675
- const eventData = evt.detail;
676
- const { currentPoints, element } = eventData;
677
- const toolState = getToolState(element, this.name);
678
-
679
- const config = this.configuration;
680
- const currentTool = config.currentTool;
681
-
682
- const data = toolState.data[currentTool];
683
- const coords = currentPoints.canvas;
684
-
685
- // Set the mouseLocation handle
686
- this._getMouseLocation(eventData);
687
- this._checkInvalidHandleLocation(data, eventData);
688
-
689
- // Mouse move -> Polygon Mode
690
- const handleNearby = this._pointNearHandle(element, data, coords);
691
- const points = data.handles.points;
692
- // If there is a handle nearby to snap to
693
- // (and it's not the actual mouse handle)
694
-
695
- if (
696
- handleNearby !== undefined &&
697
- !handleNearby.hasBoundingBox &&
698
- handleNearby < points.length - 1
699
- ) {
700
- config.mouseLocation.handles.start.x = points[handleNearby].x;
701
- config.mouseLocation.handles.start.y = points[handleNearby].y;
702
- }
703
-
704
- // Force onImageRendered
705
- external.cornerstone.updateImage(element);
706
- }
707
-
708
- /**
709
- * Event handler for MOUSE_DRAG during drawing event loop.
710
- *
711
- * @event
712
- * @param {Object} evt - The event.
713
- * @returns {undefined}
714
- */
715
- _drawingMouseDragCallback(evt) {
716
- if (!this.options.mouseButtonMask.includes(evt.detail.buttons)) {
717
- return;
718
- }
719
-
720
- this._drawingDrag(evt);
721
- }
722
-
723
- /**
724
- * Event handler for TOUCH_DRAG during drawing event loop.
725
- *
726
- * @event
727
- * @param {Object} evt - The event.
728
- * @returns {undefined}
729
- */
730
- _drawingTouchDragCallback(evt) {
731
- this._drawingDrag(evt);
732
- }
733
-
734
- _drawingDrag(evt) {
735
- const eventData = evt.detail;
736
- const { element } = eventData;
737
-
738
- const toolState = getToolState(element, this.name);
739
-
740
- const config = this.configuration;
741
- const currentTool = config.currentTool;
742
-
743
- const data = toolState.data[currentTool];
744
-
745
- // Set the mouseLocation handle
746
- this._getMouseLocation(eventData);
747
- this._checkInvalidHandleLocation(data, eventData);
748
- this._addPointPencilMode(eventData, data.handles.points);
749
- this._dragging = true;
750
-
751
- // Force onImageRendered
752
- external.cornerstone.updateImage(element);
753
- }
754
-
755
- /**
756
- * Event handler for MOUSE_UP during drawing event loop.
757
- *
758
- * @event
759
- * @param {Object} evt - The event.
760
- * @returns {undefined}
761
- */
762
- _drawingMouseUpCallback(evt) {
763
- const { element } = evt.detail;
764
-
765
- if (!this._dragging) {
766
- return;
767
- }
768
-
769
- this._dragging = false;
770
-
771
- const config = this.configuration;
772
- const currentTool = config.currentTool;
773
- const toolState = getToolState(element, this.name);
774
- const data = toolState.data[currentTool];
775
-
776
- if (!freehandIntersect.end(data.handles.points) && data.canComplete) {
777
- const lastHandlePlaced = config.currentHandle;
778
-
779
- this._endDrawing(element, lastHandlePlaced);
780
- }
781
-
782
- preventPropagation(evt);
783
-
784
- return;
785
- }
786
-
787
- /**
788
- * Event handler for MOUSE_DOWN during drawing event loop.
789
- *
790
- * @event
791
- * @param {Object} evt - The event.
792
- * @returns {undefined}
793
- */
794
- _drawingMouseDownCallback(evt) {
795
- const eventData = evt.detail;
796
- const { buttons, currentPoints, element } = eventData;
797
-
798
- if (!this.options.mouseButtonMask.includes(buttons)) {
799
- return;
800
- }
801
-
802
- const coords = currentPoints.canvas;
803
-
804
- const config = this.configuration;
805
- const currentTool = config.currentTool;
806
- const toolState = getToolState(element, this.name);
807
- const data = toolState.data[currentTool];
808
-
809
- const handleNearby = this._pointNearHandle(element, data, coords);
810
-
811
- if (!freehandIntersect.end(data.handles.points) && data.canComplete) {
812
- const lastHandlePlaced = config.currentHandle;
813
-
814
- this._endDrawing(element, lastHandlePlaced);
815
- } else if (handleNearby === undefined) {
816
- this._addPoint(eventData);
817
- }
818
-
819
- preventPropagation(evt);
820
-
821
- return;
822
- }
823
-
824
- /**
825
- * Event handler for TOUCH_START during drawing event loop.
826
- *
827
- * @event
828
- * @param {Object} evt - The event.
829
- * @returns {undefined}
830
- */
831
- _drawingTouchStartCallback(evt) {
832
- const eventData = evt.detail;
833
- const { currentPoints, element } = eventData;
834
-
835
- const coords = currentPoints.canvas;
836
-
837
- const config = this.configuration;
838
- const currentTool = config.currentTool;
839
- const toolState = getToolState(element, this.name);
840
- const data = toolState.data[currentTool];
841
-
842
- const handleNearby = this._pointNearHandle(element, data, coords);
843
-
844
- if (!freehandIntersect.end(data.handles.points) && data.canComplete) {
845
- const lastHandlePlaced = config.currentHandle;
846
-
847
- this._endDrawing(element, lastHandlePlaced);
848
- } else if (handleNearby === undefined) {
849
- this._addPoint(eventData);
850
- }
851
-
852
- preventPropagation(evt);
853
-
854
- return;
855
- }
856
-
857
- /** Ends the active drawing loop and completes the polygon.
858
- *
859
- * @public
860
- * @param {Object} element - The element on which the roi is being drawn.
861
- * @returns {null}
862
- */
863
- completeDrawing(element) {
864
- if (!this._drawing) {
865
- return;
866
- }
867
- const toolState = getToolState(element, this.name);
868
- const config = this.configuration;
869
- const data = toolState.data[config.currentTool];
870
-
871
- if (
872
- !freehandIntersect.end(data.handles.points) &&
873
- data.handles.points.length >= 2
874
- ) {
875
- const lastHandlePlaced = config.currentHandle;
876
-
877
- data.polyBoundingBox = {};
878
- this._endDrawing(element, lastHandlePlaced);
879
- }
880
- }
881
-
882
- /**
883
- * Event handler for MOUSE_DOUBLE_CLICK during drawing event loop.
884
- *
885
- * @event
886
- * @param {Object} evt - The event.
887
- * @returns {undefined}
888
- */
889
- _drawingMouseDoubleClickCallback(evt) {
890
- const { element } = evt.detail;
891
-
892
- this.completeDrawing(element);
893
-
894
- preventPropagation(evt);
895
- }
896
-
897
- /**
898
- * Event handler for DOUBLE_TAP during drawing event loop.
899
- *
900
- * @event
901
- * @param {Object} evt - The event.
902
- * @returns {undefined}
903
- */
904
- _drawingDoubleTapClickCallback(evt) {
905
- const { element } = evt.detail;
906
-
907
- this.completeDrawing(element);
908
-
909
- preventPropagation(evt);
910
- }
911
-
912
- /**
913
- * Event handler for MOUSE_DRAG during handle drag event loop.
914
- *
915
- * @event
916
- * @param {Object} evt - The event.
917
- * @returns {undefined}
918
- */
919
- _editMouseDragCallback(evt) {
920
- const eventData = evt.detail;
921
- const { element, buttons } = eventData;
922
-
923
- if (!this.options.mouseButtonMask.includes(buttons)) {
924
- return;
925
- }
926
-
927
- const toolState = getToolState(element, this.name);
928
-
929
- const config = this.configuration;
930
- const data = toolState.data[config.currentTool];
931
- const currentHandle = config.currentHandle;
932
- const points = data.handles.points;
933
- let handleIndex = -1;
934
-
935
- // Set the mouseLocation handle
936
- this._getMouseLocation(eventData);
937
-
938
- data.handles.invalidHandlePlacement = freehandIntersect.modify(
939
- points,
940
- currentHandle
941
- );
942
- data.active = true;
943
- data.highlight = true;
944
- points[currentHandle].x = config.mouseLocation.handles.start.x;
945
- points[currentHandle].y = config.mouseLocation.handles.start.y;
946
-
947
- handleIndex = this._getPrevHandleIndex(currentHandle, points);
948
-
949
- if (currentHandle >= 0) {
950
- const lastLineIndex = points[handleIndex].lines.length - 1;
951
- const lastLine = points[handleIndex].lines[lastLineIndex];
952
-
953
- lastLine.x = config.mouseLocation.handles.start.x;
954
- lastLine.y = config.mouseLocation.handles.start.y;
955
- }
956
-
957
- // Update the image
958
- external.cornerstone.updateImage(element);
959
- }
960
-
961
- /**
962
- * Event handler for TOUCH_DRAG during handle drag event loop.
963
- *
964
- * @event
965
- * @param {Object} evt - The event.
966
- * @returns {void}
967
- */
968
- _editTouchDragCallback(evt) {
969
- const eventData = evt.detail;
970
- const { element } = eventData;
971
-
972
- const toolState = getToolState(element, this.name);
973
-
974
- const config = this.configuration;
975
- const data = toolState.data[config.currentTool];
976
- const currentHandle = config.currentHandle;
977
- const points = data.handles.points;
978
- let handleIndex = -1;
979
-
980
- // Set the mouseLocation handle
981
- this._getMouseLocation(eventData);
982
-
983
- data.handles.invalidHandlePlacement = freehandIntersect.modify(
984
- points,
985
- currentHandle
986
- );
987
- data.active = true;
988
- data.highlight = true;
989
- points[currentHandle].x = config.mouseLocation.handles.start.x;
990
- points[currentHandle].y = config.mouseLocation.handles.start.y;
991
-
992
- handleIndex = this._getPrevHandleIndex(currentHandle, points);
993
-
994
- if (currentHandle >= 0) {
995
- const lastLineIndex = points[handleIndex].lines.length - 1;
996
- const lastLine = points[handleIndex].lines[lastLineIndex];
997
-
998
- lastLine.x = config.mouseLocation.handles.start.x;
999
- lastLine.y = config.mouseLocation.handles.start.y;
1000
- }
1001
-
1002
- // Update the image
1003
- external.cornerstone.updateImage(element);
1004
- }
1005
-
1006
- /**
1007
- * Returns the previous handle to the current one.
1008
- * @param {Number} currentHandle - the current handle index
1009
- * @param {Array} points - the handles Array of the freehand data
1010
- * @returns {Number} - The index of the previos handle
1011
- */
1012
- _getPrevHandleIndex(currentHandle, points) {
1013
- if (currentHandle === 0) {
1014
- return points.length - 1;
1015
- }
1016
-
1017
- return currentHandle - 1;
1018
- }
1019
-
1020
- /**
1021
- * Event handler for MOUSE_UP during handle drag event loop.
1022
- *
1023
- * @private
1024
- * @param {Object} evt - The event.
1025
- * @returns {undefined}
1026
- */
1027
- _editMouseUpCallback(evt) {
1028
- const eventData = evt.detail;
1029
- const { element } = eventData;
1030
- const toolState = getToolState(element, this.name);
1031
-
1032
- this._deactivateModify(element);
1033
-
1034
- this._dropHandle(eventData, toolState);
1035
- this._endDrawing(element);
1036
-
1037
- external.cornerstone.updateImage(element);
1038
- }
1039
-
1040
- /**
1041
- * Places a handle of the freehand tool if the new location is valid.
1042
- * If the new location is invalid the handle snaps back to its previous position.
1043
- *
1044
- * @private
1045
- * @param {Object} eventData - Data object associated with the event.
1046
- * @param {Object} toolState - The data associated with the freehand tool.
1047
- * @modifies {toolState}
1048
- * @returns {undefined}
1049
- */
1050
- _dropHandle(eventData, toolState) {
1051
- const config = this.configuration;
1052
- const currentTool = config.currentTool;
1053
- const handles = toolState.data[currentTool].handles;
1054
- const points = handles.points;
1055
-
1056
- // Don't allow the line being modified to intersect other lines
1057
- if (handles.invalidHandlePlacement) {
1058
- const currentHandle = config.currentHandle;
1059
- const currentHandleData = points[currentHandle];
1060
- let previousHandleData;
1061
-
1062
- if (currentHandle === 0) {
1063
- const lastHandleID = points.length - 1;
1064
-
1065
- previousHandleData = points[lastHandleID];
1066
- } else {
1067
- previousHandleData = points[currentHandle - 1];
1068
- }
1069
-
1070
- // Snap back to previous position
1071
- currentHandleData.x = config.dragOrigin.x;
1072
- currentHandleData.y = config.dragOrigin.y;
1073
- previousHandleData.lines[0] = currentHandleData;
1074
-
1075
- handles.invalidHandlePlacement = false;
1076
- }
1077
- }
1078
-
1079
- /**
1080
- * Begining of drawing loop when tool is active and a click event happens far
1081
- * from existing handles.
1082
- *
1083
- * @private
1084
- * @param {Object} evt - The event.
1085
- * @returns {undefined}
1086
- */
1087
- _startDrawing(evt) {
1088
- const eventData = evt.detail;
1089
- const measurementData = this.createNewMeasurement(eventData);
1090
- const { element } = eventData;
1091
- const config = this.configuration;
1092
- let interactionType;
1093
-
1094
- if (evt.type === EVENTS.MOUSE_DOWN_ACTIVATE) {
1095
- interactionType = "Mouse";
1096
- } else if (evt.type === EVENTS.TOUCH_START_ACTIVE) {
1097
- interactionType = "Touch";
1098
- }
1099
- this._activateDraw(element, interactionType);
1100
- this._getMouseLocation(eventData);
1101
-
1102
- addToolState(element, this.name, measurementData);
1103
-
1104
- const toolState = getToolState(element, this.name);
1105
-
1106
- config.currentTool = toolState.data.length - 1;
1107
-
1108
- this._activeDrawingToolReference = toolState.data[config.currentTool];
1109
- }
1110
-
1111
- /**
1112
- * Adds a point on mouse click in polygon mode.
1113
- *
1114
- * @private
1115
- * @param {Object} eventData - data object associated with an event.
1116
- * @returns {undefined}
1117
- */
1118
- _addPoint(eventData) {
1119
- const { currentPoints, element } = eventData;
1120
- const toolState = getToolState(element, this.name);
1121
-
1122
- // Get the toolState from the last-drawn polygon
1123
- const config = this.configuration;
1124
- const data = toolState.data[config.currentTool];
1125
-
1126
- if (data.handles.invalidHandlePlacement) {
1127
- return;
1128
- }
1129
-
1130
- const newHandleData = new FreehandHandleData(currentPoints.image);
1131
-
1132
- // If this is not the first handle
1133
- if (data.handles.points.length) {
1134
- // Add the line from the current handle to the new handle
1135
- data.handles.points[config.currentHandle - 1].lines.push(
1136
- currentPoints.image
1137
- );
1138
- }
1139
-
1140
- // Add the new handle
1141
- data.handles.points.push(newHandleData);
1142
-
1143
- // Increment the current handle value
1144
- config.currentHandle += 1;
1145
-
1146
- // Force onImageRendered to fire
1147
- external.cornerstone.updateImage(element);
1148
- this.fireModifiedEvent(element, data);
1149
- }
1150
-
1151
- /**
1152
- * If in pencilMode, check the mouse position is farther than the minimum
1153
- * distance between points, then add a point.
1154
- *
1155
- * @private
1156
- * @param {Object} eventData - Data object associated with an event.
1157
- * @param {Object} points - Data object associated with the tool.
1158
- * @returns {undefined}
1159
- */
1160
- _addPointPencilMode(eventData, points) {
1161
- const config = this.configuration;
1162
- const { element } = eventData;
1163
- const mousePoint = config.mouseLocation.handles.start;
1164
-
1165
- const handleFurtherThanMinimumSpacing = handle =>
1166
- this._isDistanceLargerThanSpacing(element, handle, mousePoint);
1167
-
1168
- if (points.every(handleFurtherThanMinimumSpacing)) {
1169
- this._addPoint(eventData);
1170
- }
1171
- }
1172
-
1173
- /**
1174
- * Ends the active drawing loop and completes the polygon.
1175
- *
1176
- * @private
1177
- * @param {Object} element - The element on which the roi is being drawn.
1178
- * @param {Object} handleNearby - the handle nearest to the mouse cursor.
1179
- * @returns {undefined}
1180
- */
1181
- _endDrawing(element, handleNearby) {
1182
- const toolState = getToolState(element, this.name);
1183
- const config = this.configuration;
1184
- const data = toolState.data[config.currentTool];
1185
-
1186
- data.active = false;
1187
- data.highlight = false;
1188
- data.handles.invalidHandlePlacement = false;
1189
-
1190
- // Connect the end handle to the origin handle
1191
- if (handleNearby !== undefined) {
1192
- const points = data.handles.points;
1193
-
1194
- points[config.currentHandle - 1].lines.push(points[0]);
1195
- }
1196
-
1197
- if (this._modifying) {
1198
- this._modifying = false;
1199
- data.invalidated = true;
1200
- }
1201
-
1202
- // Reset the current handle
1203
- config.currentHandle = 0;
1204
- config.currentTool = -1;
1205
- data.canComplete = false;
1206
-
1207
- if (this._drawing) {
1208
- this._deactivateDraw(element);
1209
- }
1210
-
1211
- external.cornerstone.updateImage(element);
1212
-
1213
- this.fireModifiedEvent(element, data);
1214
- this.fireCompletedEvent(element, data);
1215
- }
1216
-
1217
- /**
1218
- * Returns a handle of a particular tool if it is close to the mouse cursor
1219
- *
1220
- * @private
1221
- * @param {Object} element - The element on which the roi is being drawn.
1222
- * @param {Object} data Data object associated with the tool.
1223
- * @param {*} coords
1224
- * @returns {Number|Object|Boolean}
1225
- */
1226
- _pointNearHandle(element, data, coords) {
1227
- if (data.handles === undefined || data.handles.points === undefined) {
1228
- return;
1229
- }
1230
-
1231
- if (data.visible === false) {
1232
- return;
1233
- }
1234
-
1235
- for (let i = 0; i < data.handles.points.length; i++) {
1236
- const handleCanvas = external.cornerstone.pixelToCanvas(
1237
- element,
1238
- data.handles.points[i]
1239
- );
1240
-
1241
- if (external.cornerstoneMath.point.distance(handleCanvas, coords) < 6) {
1242
- return i;
1243
- }
1244
- }
1245
-
1246
- // Check to see if mouse in bounding box of textbox
1247
- if (data.handles.textBox) {
1248
- if (pointInsideBoundingBox(data.handles.textBox, coords)) {
1249
- return data.handles.textBox;
1250
- }
1251
- }
1252
- }
1253
-
1254
- /**
1255
- * Returns a handle if it is close to the mouse cursor (all tools)
1256
- *
1257
- * @private
1258
- * @param {Object} eventData - data object associated with an event.
1259
- * @returns {Object}
1260
- */
1261
- _pointNearHandleAllTools(eventData) {
1262
- const { currentPoints, element } = eventData;
1263
- const coords = currentPoints.canvas;
1264
- const toolState = getToolState(element, this.name);
1265
-
1266
- if (!toolState) {
1267
- return;
1268
- }
1269
-
1270
- let handleNearby;
1271
-
1272
- for (let toolIndex = 0; toolIndex < toolState.data.length; toolIndex++) {
1273
- handleNearby = this._pointNearHandle(
1274
- element,
1275
- toolState.data[toolIndex],
1276
- coords
1277
- );
1278
- if (handleNearby !== undefined) {
1279
- return {
1280
- handleNearby,
1281
- toolIndex
1282
- };
1283
- }
1284
- }
1285
- }
1286
-
1287
- /**
1288
- * Gets the current mouse location and stores it in the configuration object.
1289
- *
1290
- * @private
1291
- * @param {Object} eventData The data assoicated with the event.
1292
- * @returns {undefined}
1293
- */
1294
- _getMouseLocation(eventData) {
1295
- const { currentPoints, image } = eventData;
1296
- // Set the mouseLocation handle
1297
- const config = this.configuration;
1298
-
1299
- config.mouseLocation.handles.start.x = currentPoints.image.x;
1300
- config.mouseLocation.handles.start.y = currentPoints.image.y;
1301
- clipToBox(config.mouseLocation.handles.start, image);
1302
- }
1303
-
1304
- /**
1305
- * Returns true if the proposed location of a new handle is invalid.
1306
- *
1307
- * @private
1308
- * @param {Object} data Data object associated with the tool.
1309
- * @param {Object} eventData The data assoicated with the event.
1310
- * @returns {Boolean}
1311
- */
1312
- _checkInvalidHandleLocation(data, eventData) {
1313
- if (data.handles.points.length < 2) {
1314
- return true;
1315
- }
1316
-
1317
- let invalidHandlePlacement;
1318
-
1319
- if (this._dragging) {
1320
- invalidHandlePlacement = this._checkHandlesPencilMode(data, eventData);
1321
- } else {
1322
- invalidHandlePlacement = this._checkHandlesPolygonMode(data, eventData);
1323
- }
1324
-
1325
- data.handles.invalidHandlePlacement = invalidHandlePlacement;
1326
- }
1327
-
1328
- /**
1329
- * Returns true if the proposed location of a new handle is invalid (in polygon mode).
1330
- *
1331
- * @private
1332
- *
1333
- * @param {Object} data - data object associated with the tool.
1334
- * @param {Object} eventData The data assoicated with the event.
1335
- * @returns {Boolean}
1336
- */
1337
- _checkHandlesPolygonMode(data, eventData) {
1338
- const config = this.configuration;
1339
- const { element } = eventData;
1340
- const mousePoint = config.mouseLocation.handles.start;
1341
- const points = data.handles.points;
1342
- let invalidHandlePlacement = false;
1343
-
1344
- data.canComplete = false;
1345
-
1346
- const mouseAtOriginHandle =
1347
- this._isDistanceSmallerThanCompleteSpacingCanvas(
1348
- element,
1349
- points[0],
1350
- mousePoint
1351
- );
1352
-
1353
- if (
1354
- mouseAtOriginHandle &&
1355
- !freehandIntersect.end(points) &&
1356
- points.length > 2
1357
- ) {
1358
- data.canComplete = true;
1359
- invalidHandlePlacement = false;
1360
- } else {
1361
- invalidHandlePlacement = freehandIntersect.newHandle(mousePoint, points);
1362
- }
1363
-
1364
- return invalidHandlePlacement;
1365
- }
1366
-
1367
- /**
1368
- * Returns true if the proposed location of a new handle is invalid (in pencilMode).
1369
- *
1370
- * @private
1371
- * @param {Object} data - data object associated with the tool.
1372
- * @param {Object} eventData The data associated with the event.
1373
- * @returns {Boolean}
1374
- */
1375
- _checkHandlesPencilMode(data, eventData) {
1376
- const config = this.configuration;
1377
- const mousePoint = config.mouseLocation.handles.start;
1378
- const points = data.handles.points;
1379
- let invalidHandlePlacement = freehandIntersect.newHandle(
1380
- mousePoint,
1381
- points
1382
- );
1383
-
1384
- if (invalidHandlePlacement === false) {
1385
- invalidHandlePlacement = this._invalidHandlePencilMode(data, eventData);
1386
- }
1387
-
1388
- return invalidHandlePlacement;
1389
- }
1390
-
1391
- /**
1392
- * Returns true if the mouse position is far enough from previous points (in pencilMode).
1393
- *
1394
- * @private
1395
- * @param {Object} data - data object associated with the tool.
1396
- * @param {Object} eventData The data associated with the event.
1397
- * @returns {Boolean}
1398
- */
1399
- _invalidHandlePencilMode(data, eventData) {
1400
- const config = this.configuration;
1401
- const { element } = eventData;
1402
- const mousePoint = config.mouseLocation.handles.start;
1403
- const points = data.handles.points;
1404
-
1405
- const mouseAtOriginHandle =
1406
- this._isDistanceSmallerThanCompleteSpacingCanvas(
1407
- element,
1408
- points[0],
1409
- mousePoint
1410
- );
1411
-
1412
- if (mouseAtOriginHandle) {
1413
- data.canComplete = true;
1414
-
1415
- return false;
1416
- }
1417
-
1418
- data.canComplete = false;
1419
-
1420
- // Compare with all other handles appart from the last one
1421
- for (let i = 1; i < points.length - 1; i++) {
1422
- if (this._isDistanceSmallerThanSpacing(element, points[i], mousePoint)) {
1423
- return true;
1424
- }
1425
- }
1426
-
1427
- return false;
1428
- }
1429
-
1430
- /**
1431
- * Returns true if two points are closer than this.configuration.spacing.
1432
- *
1433
- * @private
1434
- * @param {Object} element The element on which the roi is being drawn.
1435
- * @param {Object} p1 The first point, in pixel space.
1436
- * @param {Object} p2 The second point, in pixel space.
1437
- * @returns {boolean} True if the distance is smaller than the
1438
- * allowed canvas spacing.
1439
- */
1440
- _isDistanceSmallerThanCompleteSpacingCanvas(element, p1, p2) {
1441
- const p1Canvas = external.cornerstone.pixelToCanvas(element, p1);
1442
- const p2Canvas = external.cornerstone.pixelToCanvas(element, p2);
1443
-
1444
- let completeHandleRadius;
1445
-
1446
- if (this._drawingInteractionType === "Mouse") {
1447
- completeHandleRadius = this.configuration.completeHandleRadius;
1448
- } else if (this._drawingInteractionType === "Touch") {
1449
- completeHandleRadius = this.configuration.completeHandleRadiusTouch;
1450
- }
1451
-
1452
- return this._compareDistanceToSpacing(
1453
- element,
1454
- p1Canvas,
1455
- p2Canvas,
1456
- "<",
1457
- completeHandleRadius
1458
- );
1459
- }
1460
-
1461
- /**
1462
- * Returns true if two points are closer than this.configuration.spacing.
1463
- *
1464
- * @private
1465
- * @param {Object} element The element on which the roi is being drawn.
1466
- * @param {Object} p1 The first point, in pixel space.
1467
- * @param {Object} p2 The second point, in pixel space.
1468
- * @returns {boolean} True if the distance is smaller than the
1469
- * allowed canvas spacing.
1470
- */
1471
- _isDistanceSmallerThanSpacing(element, p1, p2) {
1472
- return this._compareDistanceToSpacing(element, p1, p2, "<");
1473
- }
1474
-
1475
- /**
1476
- * Returns true if two points are farther than this.configuration.spacing.
1477
- *
1478
- * @private
1479
- * @param {Object} element The element on which the roi is being drawn.
1480
- * @param {Object} p1 The first point, in pixel space.
1481
- * @param {Object} p2 The second point, in pixel space.
1482
- * @returns {boolean} True if the distance is smaller than the
1483
- * allowed canvas spacing.
1484
- */
1485
- _isDistanceLargerThanSpacing(element, p1, p2) {
1486
- return this._compareDistanceToSpacing(element, p1, p2, ">");
1487
- }
1488
-
1489
- /**
1490
- * Compares the distance between two points to this.configuration.spacing.
1491
- *
1492
- * @private
1493
- * @param {Object} element The element on which the roi is being drawn.
1494
- * @param {Object} p1 The first point, in pixel space.
1495
- * @param {Object} p2 The second point, in pixel space.
1496
- * @param {string} comparison The comparison to make.
1497
- * @param {number} spacing The allowed canvas spacing
1498
- * @returns {boolean} True if the distance is smaller than the
1499
- * allowed canvas spacing.
1500
- */
1501
- _compareDistanceToSpacing(
1502
- element,
1503
- p1,
1504
- p2,
1505
- comparison = ">",
1506
- spacing = this.configuration.spacing
1507
- ) {
1508
- if (comparison === ">") {
1509
- return external.cornerstoneMath.point.distance(p1, p2) > spacing;
1510
- }
1511
-
1512
- return external.cornerstoneMath.point.distance(p1, p2) < spacing;
1513
- }
1514
-
1515
- /**
1516
- * Adds drawing loop event listeners.
1517
- *
1518
- * @private
1519
- * @param {Object} element - The viewport element to add event listeners to.
1520
- * @param {string} interactionType - The interactionType used for the loop.
1521
- * @modifies {element}
1522
- * @returns {undefined}
1523
- */
1524
- _activateDraw(element, interactionType = "Mouse") {
1525
- this._drawing = true;
1526
- this._drawingInteractionType = interactionType;
1527
-
1528
- state.isMultiPartToolActive = true;
1529
- // hideToolCursor(this.element);
1530
-
1531
- // Polygonal Mode
1532
- element.addEventListener(EVENTS.MOUSE_DOWN, this._drawingMouseDownCallback);
1533
- element.addEventListener(EVENTS.MOUSE_MOVE, this._drawingMouseMoveCallback);
1534
- element.addEventListener(
1535
- EVENTS.MOUSE_DOUBLE_CLICK,
1536
- this._drawingMouseDoubleClickCallback
1537
- );
1538
-
1539
- // Drag/Pencil Mode
1540
- element.addEventListener(EVENTS.MOUSE_DRAG, this._drawingMouseDragCallback);
1541
- element.addEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback);
1542
-
1543
- // Touch
1544
- element.addEventListener(
1545
- EVENTS.TOUCH_START,
1546
- this._drawingMouseMoveCallback
1547
- );
1548
- element.addEventListener(
1549
- EVENTS.TOUCH_START,
1550
- this._drawingTouchStartCallback
1551
- );
1552
-
1553
- element.addEventListener(EVENTS.TOUCH_DRAG, this._drawingTouchDragCallback);
1554
- element.addEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback);
1555
- element.addEventListener(
1556
- EVENTS.DOUBLE_TAP,
1557
- this._drawingDoubleTapClickCallback
1558
- );
1559
-
1560
- external.cornerstone.updateImage(element);
1561
- }
1562
-
1563
- /**
1564
- * Removes drawing loop event listeners.
1565
- *
1566
- * @private
1567
- * @param {Object} element - The viewport element to add event listeners to.
1568
- * @modifies {element}
1569
- * @returns {undefined}
1570
- */
1571
- _deactivateDraw(element) {
1572
- this._drawing = false;
1573
- state.isMultiPartToolActive = false;
1574
- this._activeDrawingToolReference = null;
1575
- this._drawingInteractionType = null;
1576
- // setToolCursor(this.element, this.svgCursor);
1577
-
1578
- element.removeEventListener(
1579
- EVENTS.MOUSE_DOWN,
1580
- this._drawingMouseDownCallback
1581
- );
1582
- element.removeEventListener(
1583
- EVENTS.MOUSE_MOVE,
1584
- this._drawingMouseMoveCallback
1585
- );
1586
- element.removeEventListener(
1587
- EVENTS.MOUSE_DOUBLE_CLICK,
1588
- this._drawingMouseDoubleClickCallback
1589
- );
1590
- element.removeEventListener(
1591
- EVENTS.MOUSE_DRAG,
1592
- this._drawingMouseDragCallback
1593
- );
1594
- element.removeEventListener(EVENTS.MOUSE_UP, this._drawingMouseUpCallback);
1595
-
1596
- // Touch
1597
- element.removeEventListener(
1598
- EVENTS.TOUCH_START,
1599
- this._drawingTouchStartCallback
1600
- );
1601
- element.removeEventListener(
1602
- EVENTS.TOUCH_DRAG,
1603
- this._drawingTouchDragCallback
1604
- );
1605
- element.removeEventListener(
1606
- EVENTS.TOUCH_START,
1607
- this._drawingMouseMoveCallback
1608
- );
1609
- element.removeEventListener(EVENTS.TOUCH_END, this._drawingMouseUpCallback);
1610
-
1611
- external.cornerstone.updateImage(element);
1612
- }
1613
-
1614
- /**
1615
- * Adds modify loop event listeners.
1616
- *
1617
- * @private
1618
- * @param {Object} element - The viewport element to add event listeners to.
1619
- * @modifies {element}
1620
- * @returns {undefined}
1621
- */
1622
- _activateModify(element) {
1623
- state.isToolLocked = true;
1624
-
1625
- element.addEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback);
1626
- element.addEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback);
1627
- element.addEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback);
1628
-
1629
- element.addEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback);
1630
- element.addEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback);
1631
-
1632
- external.cornerstone.updateImage(element);
1633
- }
1634
-
1635
- /**
1636
- * Removes modify loop event listeners.
1637
- *
1638
- * @private
1639
- * @param {Object} element - The viewport element to add event listeners to.
1640
- * @modifies {element}
1641
- * @returns {undefined}
1642
- */
1643
- _deactivateModify(element) {
1644
- state.isToolLocked = false;
1645
-
1646
- element.removeEventListener(EVENTS.MOUSE_UP, this._editMouseUpCallback);
1647
- element.removeEventListener(EVENTS.MOUSE_DRAG, this._editMouseDragCallback);
1648
- element.removeEventListener(EVENTS.MOUSE_CLICK, this._editMouseUpCallback);
1649
-
1650
- element.removeEventListener(EVENTS.TOUCH_END, this._editMouseUpCallback);
1651
- element.removeEventListener(EVENTS.TOUCH_DRAG, this._editTouchDragCallback);
1652
-
1653
- external.cornerstone.updateImage(element);
1654
- }
1655
-
1656
- passiveCallback(element) {
1657
- this._closeToolIfDrawing(element);
1658
- }
1659
-
1660
- enabledCallback(element) {
1661
- this._closeToolIfDrawing(element);
1662
- }
1663
-
1664
- disabledCallback(element) {
1665
- this._closeToolIfDrawing(element);
1666
- }
1667
-
1668
- _closeToolIfDrawing(element) {
1669
- if (this._drawing) {
1670
- // Actively drawing but changed mode.
1671
- const config = this.configuration;
1672
- const lastHandlePlaced = config.currentHandle;
1673
-
1674
- this._endDrawing(element, lastHandlePlaced);
1675
- external.cornerstone.updateImage(element);
1676
- }
1677
- }
1678
-
1679
- /**
1680
- * Fire MEASUREMENT_MODIFIED event on provided element
1681
- * @param {any} element which freehand data has been modified
1682
- * @param {any} measurementData the measurment data
1683
- * @returns {void}
1684
- */
1685
- fireModifiedEvent(element, measurementData) {
1686
- const eventType = EVENTS.MEASUREMENT_MODIFIED;
1687
- const eventData = {
1688
- toolName: this.name,
1689
- element,
1690
- measurementData
1691
- };
1692
-
1693
- triggerEvent(element, eventType, eventData);
1694
- }
1695
-
1696
- fireCompletedEvent(element, measurementData) {
1697
- const eventType = EVENTS.MEASUREMENT_COMPLETED;
1698
- const eventData = {
1699
- toolName: this.name,
1700
- element,
1701
- measurementData
1702
- };
1703
-
1704
- triggerEvent(element, eventType, eventData);
1705
- }
1706
-
1707
- // ===================================================================
1708
- // Public Configuration API. .
1709
- // ===================================================================
1710
-
1711
- get spacing() {
1712
- return this.configuration.spacing;
1713
- }
1714
-
1715
- set spacing(value) {
1716
- if (typeof value !== "number") {
1717
- throw new Error(
1718
- "Attempting to set freehand spacing to a value other than a number."
1719
- );
1720
- }
1721
-
1722
- this.configuration.spacing = value;
1723
- external.cornerstone.updateImage(this.element);
1724
- }
1725
-
1726
- get activeHandleRadius() {
1727
- return this.configuration.activeHandleRadius;
1728
- }
1729
-
1730
- set activeHandleRadius(value) {
1731
- if (typeof value !== "number") {
1732
- throw new Error(
1733
- "Attempting to set freehand activeHandleRadius to a value other than a number."
1734
- );
1735
- }
1736
-
1737
- this.configuration.activeHandleRadius = value;
1738
- external.cornerstone.updateImage(this.element);
1739
- }
1740
-
1741
- get completeHandleRadius() {
1742
- return this.configuration.completeHandleRadius;
1743
- }
1744
-
1745
- set completeHandleRadius(value) {
1746
- if (typeof value !== "number") {
1747
- throw new Error(
1748
- "Attempting to set freehand completeHandleRadius to a value other than a number."
1749
- );
1750
- }
1751
-
1752
- this.configuration.completeHandleRadius = value;
1753
- external.cornerstone.updateImage(this.element);
1754
- }
1755
-
1756
- get alwaysShowHandles() {
1757
- return this.configuration.alwaysShowHandles;
1758
- }
1759
-
1760
- set alwaysShowHandles(value) {
1761
- if (typeof value !== "boolean") {
1762
- throw new Error(
1763
- "Attempting to set freehand alwaysShowHandles to a value other than a boolean."
1764
- );
1765
- }
1766
-
1767
- this.configuration.alwaysShowHandles = value;
1768
- external.cornerstone.updateImage(this.element);
1769
- }
1770
-
1771
- get invalidColor() {
1772
- return this.configuration.invalidColor;
1773
- }
1774
-
1775
- set invalidColor(value) {
1776
- /*
1777
- It'd be easy to check if the color was e.g. a valid rgba color. However
1778
- it'd be difficult to check if the color was a named CSS color without
1779
- bloating the library, so we don't. If the canvas can't intepret the color
1780
- it'll show up grey.
1781
- */
1782
-
1783
- this.configuration.invalidColor = value;
1784
- external.cornerstone.updateImage(this.element);
1785
- }
1786
-
1787
- /**
1788
- * Ends the active drawing loop and removes the polygon.
1789
- *
1790
- * @public
1791
- * @param {Object} element - The element on which the roi is being drawn.
1792
- * @returns {null}
1793
- */
1794
- cancelDrawing(element) {
1795
- if (!this._drawing) {
1796
- return;
1797
- }
1798
- const toolState = getToolState(element, this.name);
1799
-
1800
- const config = this.configuration;
1801
-
1802
- const data = toolState.data[config.currentTool];
1803
-
1804
- data.active = false;
1805
- data.highlight = false;
1806
- data.handles.invalidHandlePlacement = false;
1807
-
1808
- // Reset the current handle
1809
- config.currentHandle = 0;
1810
- config.currentTool = -1;
1811
- data.canComplete = false;
1812
-
1813
- removeToolState(element, this.name, data);
1814
-
1815
- this._deactivateDraw(element);
1816
-
1817
- external.cornerstone.updateImage(element);
1818
- }
1819
-
1820
- /**
1821
- * New image event handler.
1822
- *
1823
- * @public
1824
- * @param {Object} evt The event.
1825
- * @returns {null}
1826
- */
1827
- newImageCallback(evt) {
1828
- const config = this.configuration;
1829
-
1830
- if (!(this._drawing && this._activeDrawingToolReference)) {
1831
- return;
1832
- }
1833
-
1834
- // Actively drawing but scrolled to different image.
1835
-
1836
- const element = evt.detail.element;
1837
- const data = this._activeDrawingToolReference;
1838
-
1839
- data.active = false;
1840
- data.highlight = false;
1841
- data.handles.invalidHandlePlacement = false;
1842
-
1843
- // Connect the end handle to the origin handle
1844
- const points = data.handles.points;
1845
-
1846
- points[config.currentHandle - 1].lines.push(points[0]);
1847
-
1848
- // Reset the current handle
1849
- config.currentHandle = 0;
1850
- config.currentTool = -1;
1851
- data.canComplete = false;
1852
-
1853
- this._deactivateDraw(element);
1854
-
1855
- external.cornerstone.updateImage(element);
1856
- }
1857
- }
1858
-
1859
- function defaultFreehandConfiguration() {
1860
- return {
1861
- mouseLocation: {
1862
- handles: {
1863
- start: {
1864
- highlight: false,
1865
- active: false
1866
- }
1867
- }
1868
- },
1869
- spacing: 1,
1870
- activeHandleRadius: 3,
1871
- completeHandleRadius: 6,
1872
- completeHandleRadiusTouch: 28,
1873
- alwaysShowHandles: false,
1874
- invalidColor: "#FFFF00",
1875
- currentHandle: 0,
1876
- currentTool: -1
1877
- };
1878
- }
1879
-
1880
- function preventPropagation(evt) {
1881
- evt.stopImmediatePropagation();
1882
- evt.stopPropagation();
1883
- evt.preventDefault();
1884
- }