@cornerstonejs/tools 1.55.0 → 1.56.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 (85) hide show
  1. package/dist/cjs/eventListeners/segmentation/imageChangeEventListener.js +15 -7
  2. package/dist/cjs/eventListeners/segmentation/imageChangeEventListener.js.map +1 -1
  3. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js +1 -1
  4. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js.map +1 -1
  5. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js +2 -1
  6. package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js.map +1 -1
  7. package/dist/cjs/tools/segmentation/BrushTool.js +1 -1
  8. package/dist/cjs/tools/segmentation/BrushTool.js.map +1 -1
  9. package/dist/cjs/tools/segmentation/strategies/BrushStrategy.d.ts +3 -2
  10. package/dist/cjs/tools/segmentation/strategies/BrushStrategy.js +11 -4
  11. package/dist/cjs/tools/segmentation/strategies/BrushStrategy.js.map +1 -1
  12. package/dist/cjs/tools/segmentation/strategies/compositions/dynamicThreshold.js +9 -3
  13. package/dist/cjs/tools/segmentation/strategies/compositions/dynamicThreshold.js.map +1 -1
  14. package/dist/cjs/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
  15. package/dist/cjs/tools/segmentation/strategies/compositions/islandRemoval.js.map +1 -1
  16. package/dist/cjs/tools/segmentation/strategies/compositions/threshold.js +6 -2
  17. package/dist/cjs/tools/segmentation/strategies/compositions/threshold.js.map +1 -1
  18. package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.d.ts +2 -3
  19. package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.js +21 -6
  20. package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.js.map +1 -1
  21. package/dist/cjs/utilities/pointInShapeCallback.js +3 -1
  22. package/dist/cjs/utilities/pointInShapeCallback.js.map +1 -1
  23. package/dist/cjs/utilities/segmentation/floodFill.js +16 -22
  24. package/dist/cjs/utilities/segmentation/floodFill.js.map +1 -1
  25. package/dist/cjs/utilities/segmentation/getSegmentAtLabelmapBorder.js +3 -2
  26. package/dist/cjs/utilities/segmentation/getSegmentAtLabelmapBorder.js.map +1 -1
  27. package/dist/cjs/utilities/segmentation/getSegmentAtWorldPoint.js +3 -2
  28. package/dist/cjs/utilities/segmentation/getSegmentAtWorldPoint.js.map +1 -1
  29. package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +16 -8
  30. package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js.map +1 -1
  31. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js +1 -1
  32. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js.map +1 -1
  33. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js +2 -1
  34. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js.map +1 -1
  35. package/dist/esm/tools/segmentation/BrushTool.js +1 -1
  36. package/dist/esm/tools/segmentation/BrushTool.js.map +1 -1
  37. package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +10 -3
  38. package/dist/esm/tools/segmentation/strategies/BrushStrategy.js.map +1 -1
  39. package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js +9 -3
  40. package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js.map +1 -1
  41. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
  42. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js.map +1 -1
  43. package/dist/esm/tools/segmentation/strategies/compositions/threshold.js +6 -2
  44. package/dist/esm/tools/segmentation/strategies/compositions/threshold.js.map +1 -1
  45. package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +21 -6
  46. package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js.map +1 -1
  47. package/dist/esm/utilities/pointInShapeCallback.js +3 -1
  48. package/dist/esm/utilities/pointInShapeCallback.js.map +1 -1
  49. package/dist/esm/utilities/segmentation/floodFill.js +16 -22
  50. package/dist/esm/utilities/segmentation/floodFill.js.map +1 -1
  51. package/dist/esm/utilities/segmentation/getSegmentAtLabelmapBorder.js +3 -2
  52. package/dist/esm/utilities/segmentation/getSegmentAtLabelmapBorder.js.map +1 -1
  53. package/dist/esm/utilities/segmentation/getSegmentAtWorldPoint.js +3 -2
  54. package/dist/esm/utilities/segmentation/getSegmentAtWorldPoint.js.map +1 -1
  55. package/dist/types/eventListeners/segmentation/imageChangeEventListener.d.ts.map +1 -1
  56. package/dist/types/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.d.ts.map +1 -1
  57. package/dist/types/tools/segmentation/strategies/BrushStrategy.d.ts +3 -2
  58. package/dist/types/tools/segmentation/strategies/BrushStrategy.d.ts.map +1 -1
  59. package/dist/types/tools/segmentation/strategies/compositions/dynamicThreshold.d.ts.map +1 -1
  60. package/dist/types/tools/segmentation/strategies/compositions/islandRemoval.d.ts.map +1 -1
  61. package/dist/types/tools/segmentation/strategies/compositions/threshold.d.ts.map +1 -1
  62. package/dist/types/tools/segmentation/strategies/utils/getStrategyData.d.ts +2 -3
  63. package/dist/types/tools/segmentation/strategies/utils/getStrategyData.d.ts.map +1 -1
  64. package/dist/types/utilities/pointInShapeCallback.d.ts.map +1 -1
  65. package/dist/types/utilities/segmentation/floodFill.d.ts.map +1 -1
  66. package/dist/types/utilities/segmentation/getSegmentAtLabelmapBorder.d.ts.map +1 -1
  67. package/dist/types/utilities/segmentation/getSegmentAtWorldPoint.d.ts.map +1 -1
  68. package/dist/umd/985.index.js +1 -1
  69. package/dist/umd/985.index.js.map +1 -1
  70. package/dist/umd/index.js +1 -1
  71. package/dist/umd/index.js.map +1 -1
  72. package/package.json +7 -3
  73. package/src/eventListeners/segmentation/imageChangeEventListener.ts +28 -13
  74. package/src/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.ts +1 -1
  75. package/src/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.ts +3 -1
  76. package/src/tools/segmentation/BrushTool.ts +1 -1
  77. package/src/tools/segmentation/strategies/BrushStrategy.ts +25 -4
  78. package/src/tools/segmentation/strategies/compositions/dynamicThreshold.ts +12 -2
  79. package/src/tools/segmentation/strategies/compositions/islandRemoval.ts +1 -2
  80. package/src/tools/segmentation/strategies/compositions/threshold.ts +8 -6
  81. package/src/tools/segmentation/strategies/utils/getStrategyData.ts +20 -5
  82. package/src/utilities/pointInShapeCallback.ts +2 -0
  83. package/src/utilities/segmentation/floodFill.ts +44 -31
  84. package/src/utilities/segmentation/getSegmentAtLabelmapBorder.ts +6 -5
  85. package/src/utilities/segmentation/getSegmentAtWorldPoint.ts +6 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -29,12 +29,16 @@
29
29
  "webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
30
30
  },
31
31
  "dependencies": {
32
- "@cornerstonejs/core": "^1.55.0",
32
+ "@cornerstonejs/core": "^1.56.0",
33
33
  "@icr/polyseg-wasm": "0.4.0",
34
+ "@types/offscreencanvas": "2019.7.3",
34
35
  "comlink": "^4.4.1",
35
36
  "lodash.clonedeep": "4.5.0",
36
37
  "lodash.get": "^4.4.2"
37
38
  },
39
+ "devDependencies": {
40
+ "canvas": "^2.11.2"
41
+ },
38
42
  "peerDependencies": {
39
43
  "@icr/polyseg-wasm": "0.4.0",
40
44
  "@kitware/vtk.js": "29.3.0",
@@ -55,5 +59,5 @@
55
59
  "type": "individual",
56
60
  "url": "https://ohif.org/donate"
57
61
  },
58
- "gitHead": "78d3c2fd9e2cd07d1ec5b84201f9f909ff6ce7cf"
62
+ "gitHead": "73863e2aac88c70974cf966ca4e0eae11c1e67dc"
59
63
  }
@@ -1,7 +1,7 @@
1
1
  import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
2
2
  import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
3
3
  import {
4
- StackViewport,
4
+ BaseVolumeViewport,
5
5
  getEnabledElement,
6
6
  Enums,
7
7
  getEnabledElementByIds,
@@ -19,7 +19,7 @@ import triggerSegmentationRender from '../../utilities/segmentation/triggerSegme
19
19
  const enable = function (element: HTMLDivElement): void {
20
20
  const { viewport } = getEnabledElement(element);
21
21
 
22
- if (!(viewport instanceof StackViewport)) {
22
+ if (viewport instanceof BaseVolumeViewport) {
23
23
  return;
24
24
  }
25
25
 
@@ -35,12 +35,6 @@ const enable = function (element: HTMLDivElement): void {
35
35
  };
36
36
 
37
37
  const disable = function (element: HTMLDivElement): void {
38
- const { viewport } = getEnabledElement(element);
39
-
40
- if (!(viewport instanceof StackViewport)) {
41
- return;
42
- }
43
-
44
38
  element.removeEventListener(
45
39
  Enums.Events.STACK_NEW_IMAGE,
46
40
  _imageChangeEventListener as EventListener
@@ -152,6 +146,16 @@ function _imageChangeEventListener(evt) {
152
146
  // this means that this slice doesn't have a segmentation for this representation
153
147
  // this can be a case where the segmentation was added to certain slices only
154
148
  // so we can keep the actor but empty out the imageData
149
+ if (segmentationImageData.setDerivedImage) {
150
+ // If the image data has a set derived image, then it should be called
151
+ // to update any vtk or actor data associated with it. In this case, null
152
+ // is used to clear the data. THis allows intercepting/alternative
153
+ // to vtk calls. Eventually the vtk version should also use this.
154
+ segmentationImageData.setDerivedImage(null);
155
+ return;
156
+ }
157
+ // This is the vtk version of the clearing out the image data, and fails
158
+ // to work for non scalar image data.
155
159
  const scalarArray = vtkDataArray.newInstance({
156
160
  name: 'Pixels',
157
161
  numberOfComponents: 1,
@@ -169,7 +173,11 @@ function _imageChangeEventListener(evt) {
169
173
  const { dimensions, spacing, direction } =
170
174
  viewport.getImageDataMetadata(derivedImage);
171
175
 
172
- const currentImage = cache.getImage(currentImageId);
176
+ const currentImage =
177
+ cache.getImage(currentImageId) ||
178
+ ({
179
+ imageId: currentImageId,
180
+ } as Types.IImage);
173
181
  const { origin: currentOrigin } =
174
182
  viewport.getImageDataMetadata(currentImage);
175
183
 
@@ -230,10 +238,17 @@ function _imageChangeEventListener(evt) {
230
238
  return;
231
239
  }
232
240
 
233
- utilities.updateVTKImageDataWithCornerstoneImage(
234
- segmentationImageData,
235
- derivedImage
236
- );
241
+ if (segmentationImageData.setDerivedImage) {
242
+ // Update the derived image data, whether vtk or other as appropriate
243
+ // to the actor(s) displaying the data.
244
+ segmentationImageData.setDerivedImage(derivedImage);
245
+ } else {
246
+ // TODO - use setDerivedImage for this functionality
247
+ utilities.updateVTKImageDataWithCornerstoneImage(
248
+ segmentationImageData,
249
+ derivedImage
250
+ );
251
+ }
237
252
  viewport.render();
238
253
 
239
254
  // This is put here to make sure that the segmentation is rendered
@@ -20,6 +20,6 @@ export function computeAndAddLabelmapRepresentation(
20
20
  segmentationId,
21
21
  SegmentationRepresentations.Labelmap,
22
22
  () => computeLabelmapData(segmentationId, options),
23
- () => {}
23
+ () => undefined
24
24
  );
25
25
  }
@@ -86,4 +86,6 @@ export async function convertSurfaceToVolumeLabelmap(
86
86
  };
87
87
  }
88
88
 
89
- export async function convertSurfaceToStackLabelmap() {}
89
+ export async function convertSurfaceToStackLabelmap() {
90
+ // TODO
91
+ }
@@ -484,7 +484,7 @@ class BrushTool extends BaseTool {
484
484
 
485
485
  this._previewData.preview = this.applyActiveStrategy(
486
486
  enabledElement,
487
- this.getOperationData()
487
+ this.getOperationData(element)
488
488
  );
489
489
  this._previewData.element = element;
490
490
  // Add a bit of time to the timer start so small accidental movements dont
@@ -15,12 +15,17 @@ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
15
15
  const { VoxelManager } = csUtils;
16
16
 
17
17
  export type InitializedOperationData = LabelmapToolOperationDataAny & {
18
+ // Allow initialization that is operation specific by keying on the name
19
+ operationName?: string;
20
+
18
21
  // Additional data for performing the strategy
19
22
  enabledElement: Types.IEnabledElement;
20
23
  centerIJK?: Types.Point3;
21
24
  centerWorld: Types.Point3;
22
25
  viewport: Types.IViewport;
23
- imageVoxelManager: csUtils.VoxelManager<number>;
26
+ imageVoxelManager:
27
+ | csUtils.VoxelManager<number>
28
+ | csUtils.VoxelManager<Types.RGB>;
24
29
  segmentationVoxelManager: csUtils.VoxelManager<number>;
25
30
  segmentationImageData: vtkImageData;
26
31
  previewVoxelManager: csUtils.VoxelManager<number>;
@@ -152,7 +157,16 @@ export default class BrushStrategy {
152
157
  enabledElement: Types.IEnabledElement,
153
158
  operationData: LabelmapToolOperationDataAny
154
159
  ) => {
155
- const initializedData = this.initialize(enabledElement, operationData);
160
+ const initializedData = this.initialize(
161
+ enabledElement,
162
+ operationData,
163
+ StrategyCallbacks.Fill
164
+ );
165
+
166
+ if (!initializedData) {
167
+ // Happens when there is no label map
168
+ return;
169
+ }
156
170
 
157
171
  const { strategySpecificConfiguration = {}, centerIJK } = initializedData;
158
172
  // Store the center IJK location so that we can skip an immediate same-point update
@@ -186,7 +200,8 @@ export default class BrushStrategy {
186
200
 
187
201
  protected initialize(
188
202
  enabledElement: Types.IEnabledElement,
189
- operationData: LabelmapToolOperationDataAny
203
+ operationData: LabelmapToolOperationDataAny,
204
+ operationName?: string
190
205
  ): InitializedOperationData {
191
206
  const { viewport } = enabledElement;
192
207
  const data = getStrategyData({ operationData, viewport });
@@ -228,6 +243,7 @@ export default class BrushStrategy {
228
243
  const previewSegmentIndex = previewEnabled ? 255 : undefined;
229
244
 
230
245
  const initializedData: InitializedOperationData = {
246
+ operationName,
231
247
  previewSegmentIndex,
232
248
  ...operationData,
233
249
  enabledElement,
@@ -263,6 +279,10 @@ export default class BrushStrategy {
263
279
  return;
264
280
  }
265
281
  const initializedData = this.initialize(enabledElement, operationData);
282
+ if (!initializedData) {
283
+ // Happens if there isn't a labelmap to apply to
284
+ return;
285
+ }
266
286
  this._onInteractionStart.forEach((func) =>
267
287
  func.call(this, initializedData)
268
288
  );
@@ -334,7 +354,8 @@ function addListMethod(name: string, createInitialized?: string) {
334
354
  ? (enabledElement, operationData) => {
335
355
  const initializedData = brushStrategy[createInitialized](
336
356
  enabledElement,
337
- operationData
357
+ operationData,
358
+ name
338
359
  );
339
360
  brushStrategy[listName].forEach((func) =>
340
361
  func.call(brushStrategy, initializedData)
@@ -1,3 +1,4 @@
1
+ import { vec3 } from 'gl-matrix';
1
2
  import type { InitializedOperationData } from '../BrushStrategy';
2
3
  import type BoundsIJK from '../../../../types/BoundsIJK';
3
4
  import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
@@ -13,6 +14,7 @@ import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
13
14
  export default {
14
15
  [StrategyCallbacks.Initialize]: (operationData: InitializedOperationData) => {
15
16
  const {
17
+ operationName,
16
18
  centerIJK,
17
19
  strategySpecificConfiguration,
18
20
  segmentationVoxelManager: segmentationVoxelManager,
@@ -24,6 +26,12 @@ export default {
24
26
  if (!THRESHOLD?.isDynamic || !centerIJK || !segmentIndex) {
25
27
  return;
26
28
  }
29
+ if (
30
+ operationName === StrategyCallbacks.RejectPreview ||
31
+ operationName === StrategyCallbacks.OnInteractionEnd
32
+ ) {
33
+ return;
34
+ }
27
35
 
28
36
  const { boundsIJK } = segmentationVoxelManager;
29
37
  const { threshold: oldThreshold, dynamicRadius = 0 } = THRESHOLD;
@@ -37,9 +45,11 @@ export default {
37
45
  }) as BoundsIJK;
38
46
 
39
47
  const threshold = oldThreshold || [Infinity, -Infinity];
48
+ // TODO - threshold on all three values separately
40
49
  const callback = ({ value }) => {
41
- threshold[0] = Math.min(value, threshold[0]);
42
- threshold[1] = Math.max(value, threshold[1]);
50
+ const gray = Array.isArray(value) ? vec3.len(value as any) : value;
51
+ threshold[0] = Math.min(gray, threshold[0]);
52
+ threshold[1] = Math.max(gray, threshold[1]);
43
53
  };
44
54
  imageVoxelManager.forEach(callback, { boundsIJK: nestedBounds });
45
55
 
@@ -89,8 +89,7 @@ export default {
89
89
  floodedSet.add(index);
90
90
  floodedCount++;
91
91
  };
92
-
93
- clickedPoints.forEach((clickedPoint, index) => {
92
+ clickedPoints.forEach((clickedPoint) => {
94
93
  // @ts-ignore - need to ignore the spread appication to array params
95
94
  if (getter(...clickedPoint) === 1) {
96
95
  floodFill(getter, clickedPoint, {
@@ -1,3 +1,5 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ import type { Types } from '@cornerstonejs/core';
1
3
  import type { InitializedOperationData } from '../BrushStrategy';
2
4
  import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
3
5
 
@@ -10,11 +12,8 @@ export default {
10
12
  [StrategyCallbacks.CreateIsInThreshold]: (
11
13
  operationData: InitializedOperationData
12
14
  ) => {
13
- const {
14
- imageVoxelManager: imageVoxelManager,
15
- strategySpecificConfiguration,
16
- segmentIndex,
17
- } = operationData;
15
+ const { imageVoxelManager, strategySpecificConfiguration, segmentIndex } =
16
+ operationData;
18
17
  if (!strategySpecificConfiguration || !segmentIndex) {
19
18
  return;
20
19
  }
@@ -23,13 +22,16 @@ export default {
23
22
  strategySpecificConfiguration;
24
23
 
25
24
  const voxelValue = imageVoxelManager.getAtIndex(index);
25
+ const gray = Array.isArray(voxelValue)
26
+ ? vec3.length(voxelValue as Types.Point3)
27
+ : voxelValue;
26
28
  // Prefer the generic version of the THRESHOLD configuration, but fallback
27
29
  // to the older THRESHOLD_INSIDE_CIRCLE version.
28
30
  const { threshold } = THRESHOLD || THRESHOLD_INSIDE_CIRCLE || {};
29
31
  if (!threshold?.length) {
30
32
  return true;
31
33
  }
32
- return threshold[0] <= voxelValue && voxelValue <= threshold[1];
34
+ return threshold[0] <= gray && gray <= threshold[1];
33
35
  };
34
36
  },
35
37
  };
@@ -9,6 +9,9 @@ function getStrategyData({ operationData, viewport }) {
9
9
  let segmentationImageData, segmentationScalarData, imageScalarData;
10
10
  let imageDimensions: Types.Point3;
11
11
  let segmentationDimensions: Types.Point3;
12
+ let imageVoxelManager;
13
+ let segmentationVoxelManager;
14
+
12
15
  if (isVolumeSegmentation(operationData, viewport)) {
13
16
  const { volumeId, referencedVolumeId } = operationData;
14
17
 
@@ -17,6 +20,7 @@ function getStrategyData({ operationData, viewport }) {
17
20
  if (!segmentationVolume) {
18
21
  return;
19
22
  }
23
+ segmentationVoxelManager = segmentationVolume.voxelManager;
20
24
 
21
25
  // we only need the referenceVolumeId if we do thresholding
22
26
  // but for other operations we don't need it so make it optional
@@ -46,32 +50,43 @@ function getStrategyData({ operationData, viewport }) {
46
50
  // and always circle modifies the current imageId which in fact is the imageData
47
51
  // of that actor at that moment so we have the imageData already
48
52
  const actor = viewport.getActor(segmentationRepresentationUID);
53
+ if (!actor) {
54
+ return;
55
+ }
49
56
  segmentationImageData = actor.actor.getMapper().getInputData();
57
+ segmentationVoxelManager = segmentationImageData.voxelManager;
50
58
  const currentSegmentationImageId = imageIdReferenceMap.get(currentImageId);
51
59
 
52
60
  const segmentationImage = cache.getImage(currentSegmentationImageId);
53
- segmentationScalarData = segmentationImage.getPixelData();
61
+ if (!segmentationImage) {
62
+ return;
63
+ }
64
+ segmentationScalarData = segmentationImage.getPixelData?.();
54
65
 
55
66
  const image = cache.getImage(currentImageId);
67
+ const imageData = image ? null : viewport.getImageData();
56
68
 
57
69
  // VERY IMPORTANT
58
70
  // This is the pixel data of the image that is being segmented in the cache
59
71
  // and we need to use this to for the modification
60
- imageScalarData = image.getPixelData();
61
- imageDimensions = [image.columns, image.rows, 1];
72
+ imageScalarData = image?.getPixelData() || imageData.getScalarData();
73
+ imageDimensions = image
74
+ ? [image.columns, image.rows, 1]
75
+ : imageData.dimensions;
62
76
  segmentationDimensions = [
63
77
  segmentationImage.columns,
64
78
  segmentationImage.rows,
65
79
  1,
66
80
  ];
81
+ imageVoxelManager = image?.voxelManager;
67
82
  }
68
83
 
69
- const segmentationVoxelManager = VoxelManager.createVolumeVoxelManager(
84
+ segmentationVoxelManager ||= VoxelManager.createVolumeVoxelManager(
70
85
  segmentationDimensions,
71
86
  segmentationScalarData
72
87
  );
73
88
 
74
- const imageVoxelManager =
89
+ imageVoxelManager ||=
75
90
  imageDimensions &&
76
91
  VoxelManager.createVolumeVoxelManager(imageDimensions, imageScalarData);
77
92
 
@@ -47,6 +47,7 @@ export default function pointInShapeCallback(
47
47
  let iMin, iMax, jMin, jMax, kMin, kMax;
48
48
 
49
49
  let scalarData;
50
+ const { numComps } = imageData as any;
50
51
 
51
52
  // if getScalarData is a method on imageData
52
53
  if ((imageData as Types.CPUImageData).getScalarData) {
@@ -103,6 +104,7 @@ export default function pointInShapeCallback(
103
104
  );
104
105
 
105
106
  const xMultiple =
107
+ numComps ||
106
108
  scalarData.length / dimensions[2] / dimensions[1] / dimensions[0];
107
109
  const yMultiple = dimensions[0] * xMultiple;
108
110
  const zMultiple = dimensions[1] * yMultiple;
@@ -7,22 +7,25 @@ import { Types } from '@cornerstonejs/core';
7
7
 
8
8
  /**
9
9
  * floodFill.js - Taken from MIT OSS lib - https://github.com/tuzz/n-dimensional-flood-fill
10
- * Refactored to ES6.
10
+ * Refactored to ES6. Fixed the bounds/visits checks to use integer keys, restricting the
11
+ * total search spacing to +/- 32k in each dimension, but resulting in about a hundred time
12
+ * performance gain for larger regions since JavaScript does not have a hash map to allow the
13
+ * map to work on keys.
11
14
  *
12
- * @param {function} getter The getter to the elements of your data structure,
15
+ * @param getter The getter to the elements of your data structure,
13
16
  * e.g. getter(x,y) for a 2D interprettation of your structure.
14
- * @param {number[]} seed The seed for your fill. The dimensionality is infered
17
+ * @param seed The seed for your fill. The dimensionality is infered
15
18
  * by the number of dimensions of the seed.
16
- * @param {function} [options.onFlood] An optional callback to execute when each pixel is flooded.
19
+ * @param options.onFlood - An optional callback to execute when each pixel is flooded.
17
20
  * e.g. onFlood(x,y).
18
- * @param {function} [options.onBoundary] An optional callback to execute whenever a boundary is reached.
21
+ * @param options.onBoundary - An optional callback to execute whenever a boundary is reached.
19
22
  * a boundary could be another segmentIndex, or the edge of your
20
23
  * data structure (i.e. when your getter returns undefined).
21
- * @param {function} [options.equals] An optional equality method for your datastructure.
24
+ * @param options.equals - An optional equality method for your datastructure.
22
25
  * Default is simply value1 = value2.
23
- * @param {boolean} [options.diagonals] Whether you allow flooding through diagonals. Defaults to false.
26
+ * @param options.diagonals - Whether you allow flooding through diagonals. Defaults to false.
24
27
  *
25
- * @returns {Object}
28
+ * @returns Flood fill results
26
29
  */
27
30
  function floodFill(
28
31
  getter: FloodFillGetter,
@@ -31,14 +34,14 @@ function floodFill(
31
34
  ): FloodFillResult {
32
35
  const onFlood = options.onFlood;
33
36
  const onBoundary = options.onBoundary;
34
- const equals = options.equals || defaultEquals;
37
+ const equals = options.equals;
35
38
  const diagonals = options.diagonals || false;
36
39
  const startNode = get(seed);
37
40
  const permutations = prunedPermutations();
38
41
  const stack = [];
39
42
  const flooded = [];
40
- const visits = {};
41
- const bounds = {};
43
+ const visits = new Set();
44
+ const bounds = new Map();
42
45
 
43
46
  stack.push({ currentArgs: seed });
44
47
 
@@ -68,18 +71,28 @@ function floodFill(
68
71
  }
69
72
  }
70
73
 
74
+ /**
75
+ * Indicates if the key has been visited.
76
+ * @param key is a 2 or 3 element vector with values -32768...32767
77
+ */
71
78
  function visited(key) {
72
- return visits[key] === true;
79
+ const [x, y, z = 0] = key;
80
+ // Use an integer key value for checking visited, since JavaScript does not
81
+ // provide a generic hash key indexed hash map.
82
+ const iKey = x + 32768 + 65536 * (y + 32768 + 65536 * (z + 32768));
83
+ return visits.has(iKey);
73
84
  }
74
85
 
75
86
  function markAsVisited(key) {
76
- visits[key] = true;
87
+ const [x, y, z = 0] = key;
88
+ const iKey = x + 32768 + 65536 * (y + 32768 + 65536 * (z + 32768));
89
+ visits.add(iKey);
77
90
  }
78
91
 
79
92
  function member(getArgs) {
80
- const node = safely(get, [getArgs]);
93
+ const node = get(getArgs);
81
94
 
82
- return safely(equals, [node, startNode]);
95
+ return equals ? equals(node, startNode) : node === startNode;
83
96
  }
84
97
 
85
98
  function markAsFlooded(getArgs) {
@@ -91,7 +104,11 @@ function floodFill(
91
104
  }
92
105
 
93
106
  function markAsBoundary(prevArgs) {
94
- bounds[prevArgs] = prevArgs;
107
+ const [x, y, z = 0] = prevArgs;
108
+ // Use an integer key value for checking visited, since JavaScript does not
109
+ // provide a generic hash key indexed hash map.
110
+ const iKey = x + 32768 + 65536 * (y + 32768 + 65536 * (z + 32768));
111
+ bounds.set(iKey, prevArgs);
95
112
  if (onBoundary) {
96
113
  //@ts-ignore
97
114
  onBoundary(...prevArgs);
@@ -119,13 +136,15 @@ function floodFill(
119
136
  return getter(...getArgs);
120
137
  }
121
138
 
122
- function safely(f, args) {
123
- try {
124
- return f(...args);
125
- } catch (error) {
126
- return;
127
- }
128
- }
139
+ // This is a significant performance hit - should be done as a wrapper
140
+ // only when needed.
141
+ // function safely(f, args) {
142
+ // try {
143
+ // return f(...args);
144
+ // } catch (error) {
145
+ // return;
146
+ // }
147
+ // }
129
148
 
130
149
  function prunedPermutations() {
131
150
  const permutations = permute(seed.length);
@@ -156,14 +175,8 @@ function floodFill(
156
175
  }
157
176
 
158
177
  function boundaries() {
159
- const array = [];
160
-
161
- for (const key in bounds) {
162
- if (bounds[key] !== undefined) {
163
- array.unshift(bounds[key]);
164
- }
165
- }
166
-
178
+ const array = Array.from(bounds.values());
179
+ array.reverse();
167
180
  return array;
168
181
  }
169
182
  }
@@ -84,12 +84,13 @@ export function getSegmentAtLabelmapBorder(
84
84
  const indexIJK = utilities.transformWorldToIndex(imageData, worldPoint);
85
85
 
86
86
  const dimensions = imageData.getDimensions();
87
- const voxelManager = utilities.VoxelManager.createVolumeVoxelManager(
88
- dimensions,
89
- imageData.getPointData().getScalars().getData()
90
- );
87
+ const voxelManager = (imageData.voxelManager ||
88
+ utilities.VoxelManager.createVolumeVoxelManager(
89
+ dimensions,
90
+ imageData.getPointData().getScalars().getData()
91
+ )) as utilities.VoxelManager<number>;
91
92
 
92
- const segmentIndex = voxelManager.getAtIJK(...(indexIJK as Types.Point3));
93
+ const segmentIndex = voxelManager.getAtIJKPoint(indexIJK as Types.Point3);
93
94
 
94
95
  const onEdge = isSegmentOnEdgeIJK(
95
96
  indexIJK as Types.Point3,
@@ -110,12 +110,13 @@ export function getSegmentAtWorldForLabelmap(
110
110
  const indexIJK = utilities.transformWorldToIndex(imageData, worldPoint);
111
111
 
112
112
  const dimensions = imageData.getDimensions();
113
- const voxelManager = utilities.VoxelManager.createVolumeVoxelManager(
114
- dimensions,
115
- imageData.getPointData().getScalars().getData()
116
- );
113
+ const voxelManager = (imageData.voxelManager ||
114
+ utilities.VoxelManager.createVolumeVoxelManager(
115
+ dimensions,
116
+ imageData.getPointData().getScalars().getData()
117
+ )) as utilities.VoxelManager<number>;
117
118
 
118
- const segmentIndex = voxelManager.getAtIJK(...(indexIJK as Types.Point3));
119
+ const segmentIndex = voxelManager.getAtIJKPoint(indexIJK as Types.Point3);
119
120
 
120
121
  return segmentIndex;
121
122
  }