@cornerstonejs/tools 1.38.1 → 1.40.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.
Files changed (63) hide show
  1. package/dist/cjs/index.d.ts +2 -2
  2. package/dist/cjs/index.js +3 -2
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/tools/VolumeRotateMouseWheelTool.js +2 -2
  5. package/dist/cjs/tools/VolumeRotateMouseWheelTool.js.map +1 -1
  6. package/dist/cjs/tools/annotation/LivewireContourTool.d.ts +44 -0
  7. package/dist/cjs/tools/annotation/LivewireContourTool.js +442 -0
  8. package/dist/cjs/tools/annotation/LivewireContourTool.js.map +1 -0
  9. package/dist/cjs/tools/index.d.ts +2 -1
  10. package/dist/cjs/tools/index.js +3 -1
  11. package/dist/cjs/tools/index.js.map +1 -1
  12. package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +10 -0
  13. package/dist/cjs/utilities/BucketQueue.d.ts +20 -0
  14. package/dist/cjs/utilities/BucketQueue.js +83 -0
  15. package/dist/cjs/utilities/BucketQueue.js.map +1 -0
  16. package/dist/cjs/utilities/livewire/LiveWirePath.d.ts +16 -0
  17. package/dist/cjs/utilities/livewire/LiveWirePath.js +64 -0
  18. package/dist/cjs/utilities/livewire/LiveWirePath.js.map +1 -0
  19. package/dist/cjs/utilities/livewire/LivewireScissors.d.ts +37 -0
  20. package/dist/cjs/utilities/livewire/LivewireScissors.js +281 -0
  21. package/dist/cjs/utilities/livewire/LivewireScissors.js.map +1 -0
  22. package/dist/cjs/utilities/math/vec2/liangBarksyClip.d.ts +1 -1
  23. package/dist/esm/index.js +2 -2
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/tools/VolumeRotateMouseWheelTool.js +2 -2
  26. package/dist/esm/tools/VolumeRotateMouseWheelTool.js.map +1 -1
  27. package/dist/esm/tools/annotation/LivewireContourTool.js +439 -0
  28. package/dist/esm/tools/annotation/LivewireContourTool.js.map +1 -0
  29. package/dist/esm/tools/index.js +2 -1
  30. package/dist/esm/tools/index.js.map +1 -1
  31. package/dist/esm/utilities/BucketQueue.js +79 -0
  32. package/dist/esm/utilities/BucketQueue.js.map +1 -0
  33. package/dist/esm/utilities/livewire/LiveWirePath.js +60 -0
  34. package/dist/esm/utilities/livewire/LiveWirePath.js.map +1 -0
  35. package/dist/esm/utilities/livewire/LivewireScissors.js +277 -0
  36. package/dist/esm/utilities/livewire/LivewireScissors.js.map +1 -0
  37. package/dist/types/index.d.ts +2 -2
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/tools/VolumeRotateMouseWheelTool.d.ts.map +1 -1
  40. package/dist/types/tools/annotation/LivewireContourTool.d.ts +45 -0
  41. package/dist/types/tools/annotation/LivewireContourTool.d.ts.map +1 -0
  42. package/dist/types/tools/index.d.ts +2 -1
  43. package/dist/types/tools/index.d.ts.map +1 -1
  44. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +10 -0
  45. package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
  46. package/dist/types/utilities/BucketQueue.d.ts +21 -0
  47. package/dist/types/utilities/BucketQueue.d.ts.map +1 -0
  48. package/dist/types/utilities/livewire/LiveWirePath.d.ts +17 -0
  49. package/dist/types/utilities/livewire/LiveWirePath.d.ts.map +1 -0
  50. package/dist/types/utilities/livewire/LivewireScissors.d.ts +38 -0
  51. package/dist/types/utilities/livewire/LivewireScissors.d.ts.map +1 -0
  52. package/dist/types/utilities/math/vec2/liangBarksyClip.d.ts +1 -1
  53. package/dist/umd/index.js +1 -1
  54. package/dist/umd/index.js.map +1 -1
  55. package/package.json +3 -3
  56. package/src/index.ts +2 -0
  57. package/src/tools/VolumeRotateMouseWheelTool.ts +3 -2
  58. package/src/tools/annotation/LivewireContourTool.ts +799 -0
  59. package/src/tools/index.ts +2 -0
  60. package/src/types/ToolSpecificAnnotationTypes.ts +12 -0
  61. package/src/utilities/BucketQueue.ts +154 -0
  62. package/src/utilities/livewire/LiveWirePath.ts +131 -0
  63. package/src/utilities/livewire/LivewireScissors.ts +582 -0
@@ -0,0 +1,582 @@
1
+ import { Types } from '@cornerstonejs/core';
2
+ import { BucketQueue } from '../BucketQueue';
3
+
4
+ const MAX_UINT32 = 4294967295;
5
+ const TWO_THIRD_PI = 2 / (3 * Math.PI);
6
+
7
+ /**
8
+ * Scissors
9
+ *
10
+ * Ref: Eric N. Mortensen, William A. Barrett, Interactive Segmentation with
11
+ * Intelligent Scissors, Graphical Models and Image Processing, Volume 60,
12
+ * Issue 5, September 1998, Pages 349-384, ISSN 1077-3169,
13
+ * DOI: 10.1006/gmip.1998.0480.
14
+ *
15
+ * {@link http://www.sciencedirect.com/science/article/B6WG4-45JB8WN-9/2/6fe59d8089fd1892c2bfb82283065579}
16
+ *
17
+ * Implementation based on
18
+ * {@link http://code.google.com/p/livewire-javascript/}
19
+ */
20
+ export class LivewireScissors {
21
+ private searchGranularityBits: number;
22
+ private searchGranularity: number;
23
+
24
+ /** Width of the image */
25
+ public readonly width: number;
26
+
27
+ /** Height of the image */
28
+ public readonly height: number;
29
+
30
+ /** Grayscale image */
31
+ private grayscalePixelData: Float32Array;
32
+
33
+ // Laplace zero-crossings (either 0 or 1).
34
+ private laplace: Float32Array;
35
+
36
+ /** Gradient vector magnitude for each pixel */
37
+ private gradMagnitude: Float32Array;
38
+
39
+ /** Gradient of each pixel in the x-direction */
40
+ private gradXNew: Float32Array;
41
+
42
+ /** Gradient of each pixel in the y-direction */
43
+ private gradYNew: Float32Array;
44
+
45
+ /** Dijkstra - start point */
46
+ private startPoint: Types.Point2;
47
+
48
+ /** Dijkstra - store the state of a pixel (visited/unvisited) */
49
+ private visited: boolean[];
50
+
51
+ /** Dijkstra - map a point to its parent along the shortest path to root (start point) */
52
+ private parents: Uint32Array;
53
+
54
+ /** Dijkstra - store the cost to go from the start point to each node */
55
+ private costs: Float32Array;
56
+
57
+ /** Dijkstra - BucketQueue to sort items by priority */
58
+ private priorityQueueNew: BucketQueue<number>;
59
+
60
+ constructor(grayscalePixelData: Float32Array, width: number, height: number) {
61
+ const numPixels = grayscalePixelData.length;
62
+
63
+ this.searchGranularityBits = 8; // Bits of resolution for BucketQueue.
64
+ this.searchGranularity = 1 << this.searchGranularityBits; //bits.
65
+
66
+ this.width = width;
67
+ this.height = height;
68
+
69
+ this.grayscalePixelData = grayscalePixelData;
70
+ this.laplace = null;
71
+ this.gradXNew = null;
72
+ this.gradYNew = null;
73
+
74
+ this.laplace = this._computeLaplace();
75
+ this.gradMagnitude = this._computeGradient();
76
+ this.gradXNew = this._computeGradientX();
77
+ this.gradYNew = this._computeGradientY();
78
+
79
+ this.visited = new Array(numPixels);
80
+ this.parents = new Uint32Array(numPixels);
81
+ this.costs = new Float32Array(numPixels);
82
+ }
83
+
84
+ public startSearch(startPoint: Types.Point2): void {
85
+ const startPointIndex = this._getPointIndex(startPoint[1], startPoint[0]);
86
+
87
+ this.startPoint = null;
88
+ this.visited.fill(false);
89
+ this.parents.fill(MAX_UINT32);
90
+ this.costs.fill(Infinity);
91
+ this.priorityQueueNew = new BucketQueue<number>({
92
+ numBits: this.searchGranularityBits,
93
+ getPriority: this._getPointCost,
94
+ });
95
+
96
+ this.startPoint = startPoint;
97
+ this.costs[startPointIndex] = 0;
98
+ this.priorityQueueNew.push(startPointIndex);
99
+ }
100
+
101
+ /**
102
+ * Runs Dijsktra until it finds a path from the start point to the target
103
+ * point. Once it reaches the target point all the state is preserved in order
104
+ * to save processing time the next time the method is called for a new target
105
+ * point. The search is restarted whenever `startSearch` is called.
106
+ * @param targetPoint - Target point
107
+ * @returns An array with all points for the shortest path found that goes
108
+ * from startPoint to targetPoint.
109
+ */
110
+ public findPathToPoint(targetPoint: Types.Point2): Types.Point2[] {
111
+ if (!this.startPoint) {
112
+ throw new Error('There is no search in progress');
113
+ }
114
+
115
+ const {
116
+ startPoint,
117
+ _getPointIndex: index,
118
+ _getPointCoordinate: coord,
119
+ } = this;
120
+ const startPointIndex = index(startPoint[1], startPoint[0]);
121
+ const targetPointIndex = index(targetPoint[1], targetPoint[0]);
122
+ const {
123
+ visited: visited,
124
+ parents: parents,
125
+ costs: cost,
126
+ priorityQueueNew: priorityQueue,
127
+ } = this;
128
+
129
+ if (targetPointIndex === startPointIndex) {
130
+ return [];
131
+ }
132
+
133
+ // Stop searching until there are no more items in the queue or it has
134
+ // reached the target point. In case it reaches the target all the remaining
135
+ // items will stay in the queue then once the user moves the mouse to a new
136
+ // location the search can continue from where it left off.
137
+ while (
138
+ !priorityQueue.isEmpty() &&
139
+ parents[targetPointIndex] === MAX_UINT32
140
+ ) {
141
+ const pointIndex = priorityQueue.pop();
142
+
143
+ if (visited[pointIndex]) {
144
+ continue;
145
+ }
146
+
147
+ const point = coord(pointIndex);
148
+ const neighborsPoints = this._getNeighborPoints(point);
149
+
150
+ visited[pointIndex] = true;
151
+
152
+ // Update the cost of all neighbors that have higher costs
153
+ for (let i = 0, len = neighborsPoints.length; i < len; i++) {
154
+ const neighborPoint = neighborsPoints[i];
155
+ const neighbordPointIndex = index(neighborPoint[1], neighborPoint[0]);
156
+ const dist = this._getWeightedDistance(point, neighborPoint);
157
+ const neighborCost = cost[pointIndex] + dist;
158
+
159
+ if (neighborCost < cost[neighbordPointIndex]) {
160
+ if (cost[neighbordPointIndex] !== Infinity) {
161
+ // The item needs to be removed from the priority queue and
162
+ // re-added in order to be moved to the right bucket.
163
+ priorityQueue.remove(neighbordPointIndex);
164
+ }
165
+
166
+ cost[neighbordPointIndex] = neighborCost;
167
+ parents[neighbordPointIndex] = pointIndex;
168
+ priorityQueue.push(neighbordPointIndex);
169
+ }
170
+ }
171
+ }
172
+
173
+ const pathPoints = [];
174
+ let pathPointIndex = targetPointIndex;
175
+
176
+ while (pathPointIndex !== MAX_UINT32) {
177
+ pathPoints.push(coord(pathPointIndex));
178
+ pathPointIndex = parents[pathPointIndex];
179
+ }
180
+
181
+ return pathPoints.reverse();
182
+ }
183
+
184
+ /**
185
+ * Convert a point coordinate (x,y) into a point index
186
+ * @param index - Point index
187
+ * @returns Point coordinate (x,y)
188
+ */
189
+ private _getPointIndex = (row: number, col: number) => {
190
+ const { width } = this;
191
+ return row * width + col;
192
+ };
193
+
194
+ /**
195
+ * Convert a point index into a point coordinate (x,y)
196
+ * @param index - Point index
197
+ * @returns Point coordinate (x,y)
198
+ */
199
+ private _getPointCoordinate = (index: number): Types.Point2 => {
200
+ const x = index % this.width;
201
+ const y = Math.floor(index / this.width);
202
+
203
+ return [x, y];
204
+ };
205
+
206
+ /**
207
+ * Calculate the delta X between a given point and its neighbor at the right
208
+ * @param x - Point x-coordinate
209
+ * @param y - Point y-coordinate
210
+ * @returns Delta Y between the given point and its neighbor at the right
211
+ */
212
+ private _getDeltaX(x: number, y: number) {
213
+ const { grayscalePixelData: data, width } = this;
214
+ let index = this._getPointIndex(y, x);
215
+
216
+ // If it is at the end, back up one
217
+ if (x + 1 === width) {
218
+ index--;
219
+ }
220
+
221
+ return data[index + 1] - data[index];
222
+ }
223
+
224
+ /**
225
+ * Calculate the delta Y between a given point and its neighbor at the bottom
226
+ * @param x - Point x-coordinate
227
+ * @param y - Point y-coordinate
228
+ * @returns Delta Y between the given point and its neighbor at the bottom
229
+ */
230
+ private _getDeltaY(x: number, y: number) {
231
+ const { grayscalePixelData: data, width, height } = this;
232
+ let index = this._getPointIndex(y, x);
233
+
234
+ // If it is at the end, back up one
235
+ if (y + 1 === height) {
236
+ index -= height;
237
+ }
238
+
239
+ return data[index] - data[index + width];
240
+ }
241
+
242
+ private _getGradientMagnitude(x: number, y: number): number {
243
+ const dx = this._getDeltaX(x, y);
244
+ const dy = this._getDeltaY(x, y);
245
+
246
+ return Math.sqrt(dx * dx + dy * dy);
247
+ }
248
+
249
+ /**
250
+ * Calculate the Laplacian of Gaussian (LoG) value for a given pixel
251
+ *
252
+ * Kernel Indexes Laplacian of Gaussian Kernel
253
+ * __ __ 02 __ __ 0 0 1 0 0
254
+ * __ 11 12 13 __ 0 1 2 1 0
255
+ * 20 21 22 23 24 1 2 -16 2 1
256
+ * __ 31 32 33 __ 0 1 2 1 0
257
+ * __ __ 42 __ __ 0 0 1 0 0
258
+ */
259
+ private _getLaplace(x: number, y: number): number {
260
+ const { grayscalePixelData: data, _getPointIndex: index } = this;
261
+
262
+ // Points related to the kernel indexes
263
+ const p02 = data[index(y - 2, x)];
264
+ const p11 = data[index(y - 1, x - 1)];
265
+ const p12 = data[index(y - 1, x)];
266
+ const p13 = data[index(y - 1, x + 1)];
267
+ const p20 = data[index(y, x - 2)];
268
+ const p21 = data[index(y, x - 1)];
269
+ const p22 = data[index(y, x)];
270
+ const p23 = data[index(y, x + 1)];
271
+ const p24 = data[index(y, x + 2)];
272
+ const p31 = data[index(y + 1, x - 1)];
273
+ const p32 = data[index(y + 1, x)];
274
+ const p33 = data[index(y + 1, x + 1)];
275
+ const p42 = data[index(y + 2, x)];
276
+
277
+ // Laplacian of Gaussian
278
+ let lap = p02;
279
+ lap += p11 + 2 * p12 + p13;
280
+ lap += p20 + 2 * p21 - 16 * p22 + 2 * p23 + p24;
281
+ lap += p31 + 2 * p32 + p33;
282
+ lap += p42;
283
+
284
+ return lap;
285
+ }
286
+
287
+ /**
288
+ * Returns a 2D array of gradient magnitude values for grayscale. The values
289
+ * are scaled between 0 and 1, and then flipped, so that it works as a cost
290
+ * function.
291
+ * @returns A gradient object
292
+ */
293
+ private _computeGradient(): Float32Array {
294
+ const { width, height } = this;
295
+ const gradient = new Float32Array(width * height);
296
+
297
+ let pixelIndex = 0;
298
+ let max = 0;
299
+ let x = 0;
300
+ let y = 0;
301
+
302
+ for (y = 0; y < height - 1; y++) {
303
+ for (x = 0; x < width - 1; x++) {
304
+ gradient[pixelIndex] = this._getGradientMagnitude(x, y);
305
+ max = Math.max(gradient[pixelIndex], max);
306
+ pixelIndex++;
307
+ }
308
+
309
+ // Make the last column the same as the previous one because there is
310
+ // no way to calculate `dx` since x+1 gets out of bounds
311
+ gradient[pixelIndex] = gradient[pixelIndex - 1];
312
+ pixelIndex++;
313
+ }
314
+
315
+ // Make the last row the same as the previous one because there is
316
+ // no way to calculate `dy` since y+1 gets out of bounds
317
+ for (let len = gradient.length; pixelIndex < len; pixelIndex++) {
318
+ gradient[pixelIndex] = gradient[pixelIndex - width];
319
+ }
320
+
321
+ // Flip and scale
322
+ for (let i = 0, len = gradient.length; i < len; i++) {
323
+ gradient[i] = 1 - gradient[i] / max;
324
+ }
325
+
326
+ return gradient;
327
+ }
328
+
329
+ /**
330
+ * Returns a 2D array of Laplacian of Gaussian values
331
+ *
332
+ * @param grayscale - The input grayscale
333
+ * @returns A laplace object
334
+ */
335
+ private _computeLaplace(): Float32Array {
336
+ const { width, height, _getPointIndex: index } = this;
337
+ const laplace = new Float32Array(width * height);
338
+
339
+ // Make the first two rows low cost
340
+ laplace.fill(1, 0, index(2, 0));
341
+
342
+ for (let y = 2; y < height - 2; y++) {
343
+ // Make the first two columns low cost
344
+ laplace[index(y, 0)] = 1;
345
+ laplace[index(y, 1)] = 1;
346
+
347
+ for (let x = 2; x < width - 2; x++) {
348
+ // Threshold needed to get rid of clutter.
349
+ laplace[index(y, x)] = this._getLaplace(x, y) > 0.33 ? 0 : 1;
350
+ }
351
+
352
+ // Make the last two columns low cost
353
+ laplace[index(y, width - 2)] = 1;
354
+ laplace[index(y, width - 1)] = 1;
355
+ }
356
+
357
+ // Make the last two rows low cost
358
+ laplace.fill(1, index(height - 2, 0));
359
+
360
+ return laplace;
361
+ }
362
+
363
+ /**
364
+ * Returns 2D array of x-gradient values for grayscale
365
+ *
366
+ * @param grayscale - Grayscale pixel data
367
+ * @returns 2D x-gradient array
368
+ */
369
+ private _computeGradientX(): Float32Array {
370
+ const { width, height } = this;
371
+ const gradX = new Float32Array(width * height);
372
+ let pixelIndex = 0;
373
+
374
+ for (let y = 0; y < height; y++) {
375
+ for (let x = 0; x < width - 1; x++) {
376
+ gradX[pixelIndex++] = this._getDeltaX(x, y);
377
+ }
378
+
379
+ // Make the last column the same as the previous one because there is
380
+ // no way to calculate `dx` since x+1 gets out of bounds
381
+ gradX[pixelIndex] = gradX[pixelIndex - 1];
382
+ pixelIndex++;
383
+ }
384
+
385
+ return gradX;
386
+ }
387
+
388
+ /**
389
+ * Compute the Y gradient.
390
+ *
391
+ * @param grayscale - Grayscale pixel data
392
+ * @returns 2D array of y-gradient values for grayscale
393
+ */
394
+ private _computeGradientY(): Float32Array {
395
+ const { width, height } = this;
396
+ const gradY = new Float32Array(width * height);
397
+ let pixelIndex = 0;
398
+
399
+ for (let y = 0; y < height - 1; y++) {
400
+ for (let x = 0; x < width; x++) {
401
+ gradY[pixelIndex++] = this._getDeltaY(x, y);
402
+ }
403
+ }
404
+
405
+ // Make the last row the same as the previous one because there is
406
+ // no way to calculate `dy` since y+1 gets out of bounds
407
+ for (let len = gradY.length; pixelIndex < len; pixelIndex++) {
408
+ gradY[pixelIndex] = gradY[pixelIndex - width];
409
+ }
410
+
411
+ return gradY;
412
+ }
413
+
414
+ /**
415
+ * Compute the gradient unit vector.
416
+ * @param px - Point x-coordinate
417
+ * @param py - Point y-coordinate
418
+ * @returns Gradient vector at (px, py), scaled to a magnitude of 1
419
+ */
420
+ private _getGradientUnitVector(px: number, py: number) {
421
+ const { gradXNew, gradYNew, _getPointIndex: index } = this;
422
+
423
+ const pointGradX = gradXNew[index(py, px)];
424
+ const pointGradY = gradYNew[index(py, px)];
425
+ let gradVecLen = Math.sqrt(
426
+ pointGradX * pointGradX + pointGradY * pointGradY
427
+ );
428
+
429
+ // To avoid possible divide-by-0 errors
430
+ gradVecLen = Math.max(gradVecLen, 1e-100);
431
+
432
+ return [pointGradX / gradVecLen, pointGradY / gradVecLen];
433
+ }
434
+
435
+ /**
436
+ * Compute the gradiant direction, in radians, between to points
437
+ *
438
+ * @param px - Point `p` x-coordinate of point p.
439
+ * @param py - Point `p` y-coordinate of point p.
440
+ * @param qx - Point `q` x-coordinate of point q.
441
+ * @param qy - Point `q` y-coordinate of point q.
442
+ * @returns Gradient direction
443
+ */
444
+ private _getGradientDirection(
445
+ px: number,
446
+ py: number,
447
+ qx: number,
448
+ qy: number
449
+ ): number {
450
+ const dgpUnitVec = this._getGradientUnitVector(px, py);
451
+ const gdqUnitVec = this._getGradientUnitVector(qx, qy);
452
+
453
+ let dp = dgpUnitVec[1] * (qx - px) - dgpUnitVec[0] * (qy - py);
454
+ let dq = gdqUnitVec[1] * (qx - px) - gdqUnitVec[0] * (qy - py);
455
+
456
+ // Make sure dp is positive, to keep things consistent
457
+ if (dp < 0) {
458
+ dp = -dp;
459
+ dq = -dq;
460
+ }
461
+
462
+ if (px !== qx && py !== qy) {
463
+ // It's going diagonally between pixels
464
+ dp *= Math.SQRT1_2;
465
+ dq *= Math.SQRT1_2;
466
+ }
467
+
468
+ return TWO_THIRD_PI * (Math.acos(dp) + Math.acos(dq));
469
+ }
470
+
471
+ /**
472
+ * Return a weighted distance between two points
473
+ */
474
+ private _getWeightedDistance(pointA: Types.Point2, pointB: Types.Point2) {
475
+ const { _getPointIndex: index } = this;
476
+ const [aX, aY] = pointA;
477
+ const [bX, bY] = pointB;
478
+ const bIndex = index(bY, bX);
479
+
480
+ // Weighted distance function
481
+ let gradient = this.gradMagnitude[bIndex];
482
+
483
+ if (aX === bX || aY === bY) {
484
+ // The distance is Euclidean-ish; non-diagonal edges should be shorter
485
+ gradient *= Math.SQRT1_2;
486
+ }
487
+
488
+ const laplace = this.laplace[bIndex];
489
+ const direction = this._getGradientDirection(aX, aY, bX, bY);
490
+
491
+ return 0.43 * gradient + 0.43 * laplace + 0.11 * direction;
492
+ }
493
+
494
+ /**
495
+ * Get up to 8 neighbors points
496
+ * @param point - Reference point
497
+ * @returns Up to eight neighbor points
498
+ */
499
+ private _getNeighborPoints(point: Types.Point2): Types.Point2[] {
500
+ const { width, height } = this;
501
+ const list: Types.Point2[] = [];
502
+
503
+ const sx = Math.max(point[0] - 1, 0);
504
+ const sy = Math.max(point[1] - 1, 0);
505
+ const ex = Math.min(point[0] + 1, width - 1);
506
+ const ey = Math.min(point[1] + 1, height - 1);
507
+
508
+ for (let y = sy; y <= ey; y++) {
509
+ for (let x = sx; x <= ex; x++) {
510
+ if (x !== point[0] || y !== point[1]) {
511
+ list.push([x, y]);
512
+ }
513
+ }
514
+ }
515
+
516
+ return list;
517
+ }
518
+
519
+ private _getPointCost = (pointIndex: number): number => {
520
+ return Math.round(this.searchGranularity * this.costs[pointIndex]);
521
+ };
522
+
523
+ /**
524
+ * Create a livewire scissor instance from RAW pixel data
525
+ * @param pixelData - Raw pixel data
526
+ * @param width - Width of the image
527
+ * @param height - Height of the image
528
+ * @param voiRange - VOI Range
529
+ * @returns A LivewireScissors instance
530
+ */
531
+ public static createInstanceFromRawPixelData(
532
+ pixelData: Float32Array,
533
+ width: number,
534
+ height: number,
535
+ voiRange: Types.VOIRange
536
+ ) {
537
+ const numPixels = pixelData.length;
538
+ const grayscalePixelData = new Float32Array(numPixels);
539
+ const { lower: minPixelValue, upper: maxPixelValue } = voiRange;
540
+ const pixelRange = maxPixelValue - minPixelValue;
541
+
542
+ for (let i = 0, len = pixelData.length; i < len; i++) {
543
+ // Grayscale values must be between 0 and 1
544
+ grayscalePixelData[i] = Math.max(
545
+ 0,
546
+ Math.min(1, (pixelData[i] - minPixelValue) / pixelRange)
547
+ );
548
+ }
549
+
550
+ return new LivewireScissors(grayscalePixelData, width, height);
551
+ }
552
+
553
+ /**
554
+ * Create a livewire scissor instance from a RGBA image
555
+ * @param rgbaPixelData - RGBA pixel data
556
+ * @param width - Width of the image
557
+ * @param height - Height of the image
558
+ * @returns A LivewireScissors instance
559
+ */
560
+ public static createInstanceFromRGBAPixelData(
561
+ rgbaPixelData: Uint8ClampedArray,
562
+ width: number,
563
+ height: number
564
+ ): LivewireScissors {
565
+ const numPixels = rgbaPixelData.length / 4;
566
+ const grayscalePixelData = new Float32Array(numPixels);
567
+
568
+ // Multiplier to average an RGB sum and convert it to 0-1 range.
569
+ // 1/x because multiplication is faster than division.
570
+ const avgMultiplier = 1 / (3 * 255);
571
+
572
+ for (let i = 0, offset = 0; i < numPixels; i++, offset += 4) {
573
+ const red = rgbaPixelData[offset];
574
+ const green = rgbaPixelData[offset];
575
+ const blue = rgbaPixelData[offset];
576
+
577
+ grayscalePixelData[i] = (red + green + blue) * avgMultiplier;
578
+ }
579
+
580
+ return new LivewireScissors(grayscalePixelData, width, height);
581
+ }
582
+ }