@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.
- package/dist/cjs/eventListeners/segmentation/imageChangeEventListener.js +15 -7
- package/dist/cjs/eventListeners/segmentation/imageChangeEventListener.js.map +1 -1
- package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js +1 -1
- package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js.map +1 -1
- package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js +2 -1
- package/dist/cjs/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js.map +1 -1
- package/dist/cjs/tools/segmentation/BrushTool.js +1 -1
- package/dist/cjs/tools/segmentation/BrushTool.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/BrushStrategy.d.ts +3 -2
- package/dist/cjs/tools/segmentation/strategies/BrushStrategy.js +11 -4
- package/dist/cjs/tools/segmentation/strategies/BrushStrategy.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/compositions/dynamicThreshold.js +9 -3
- package/dist/cjs/tools/segmentation/strategies/compositions/dynamicThreshold.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
- package/dist/cjs/tools/segmentation/strategies/compositions/islandRemoval.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/compositions/threshold.js +6 -2
- package/dist/cjs/tools/segmentation/strategies/compositions/threshold.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.d.ts +2 -3
- package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.js +21 -6
- package/dist/cjs/tools/segmentation/strategies/utils/getStrategyData.js.map +1 -1
- package/dist/cjs/utilities/pointInShapeCallback.js +3 -1
- package/dist/cjs/utilities/pointInShapeCallback.js.map +1 -1
- package/dist/cjs/utilities/segmentation/floodFill.js +16 -22
- package/dist/cjs/utilities/segmentation/floodFill.js.map +1 -1
- package/dist/cjs/utilities/segmentation/getSegmentAtLabelmapBorder.js +3 -2
- package/dist/cjs/utilities/segmentation/getSegmentAtLabelmapBorder.js.map +1 -1
- package/dist/cjs/utilities/segmentation/getSegmentAtWorldPoint.js +3 -2
- package/dist/cjs/utilities/segmentation/getSegmentAtWorldPoint.js.map +1 -1
- package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +16 -8
- package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js.map +1 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js +1 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.js.map +1 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js +2 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.js.map +1 -1
- package/dist/esm/tools/segmentation/BrushTool.js +1 -1
- package/dist/esm/tools/segmentation/BrushTool.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +10 -3
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js +9 -3
- package/dist/esm/tools/segmentation/strategies/compositions/dynamicThreshold.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/threshold.js +6 -2
- package/dist/esm/tools/segmentation/strategies/compositions/threshold.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +21 -6
- package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js.map +1 -1
- package/dist/esm/utilities/pointInShapeCallback.js +3 -1
- package/dist/esm/utilities/pointInShapeCallback.js.map +1 -1
- package/dist/esm/utilities/segmentation/floodFill.js +16 -22
- package/dist/esm/utilities/segmentation/floodFill.js.map +1 -1
- package/dist/esm/utilities/segmentation/getSegmentAtLabelmapBorder.js +3 -2
- package/dist/esm/utilities/segmentation/getSegmentAtLabelmapBorder.js.map +1 -1
- package/dist/esm/utilities/segmentation/getSegmentAtWorldPoint.js +3 -2
- package/dist/esm/utilities/segmentation/getSegmentAtWorldPoint.js.map +1 -1
- package/dist/types/eventListeners/segmentation/imageChangeEventListener.d.ts.map +1 -1
- package/dist/types/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.d.ts.map +1 -1
- package/dist/types/tools/segmentation/strategies/BrushStrategy.d.ts +3 -2
- package/dist/types/tools/segmentation/strategies/BrushStrategy.d.ts.map +1 -1
- package/dist/types/tools/segmentation/strategies/compositions/dynamicThreshold.d.ts.map +1 -1
- package/dist/types/tools/segmentation/strategies/compositions/islandRemoval.d.ts.map +1 -1
- package/dist/types/tools/segmentation/strategies/compositions/threshold.d.ts.map +1 -1
- package/dist/types/tools/segmentation/strategies/utils/getStrategyData.d.ts +2 -3
- package/dist/types/tools/segmentation/strategies/utils/getStrategyData.d.ts.map +1 -1
- package/dist/types/utilities/pointInShapeCallback.d.ts.map +1 -1
- package/dist/types/utilities/segmentation/floodFill.d.ts.map +1 -1
- package/dist/types/utilities/segmentation/getSegmentAtLabelmapBorder.d.ts.map +1 -1
- package/dist/types/utilities/segmentation/getSegmentAtWorldPoint.d.ts.map +1 -1
- package/dist/umd/985.index.js +1 -1
- package/dist/umd/985.index.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +7 -3
- package/src/eventListeners/segmentation/imageChangeEventListener.ts +28 -13
- package/src/stateManagement/segmentation/polySeg/Labelmap/computeAndAddLabelmapRepresentation.ts +1 -1
- package/src/stateManagement/segmentation/polySeg/Labelmap/convertSurfaceToLabelmap.ts +3 -1
- package/src/tools/segmentation/BrushTool.ts +1 -1
- package/src/tools/segmentation/strategies/BrushStrategy.ts +25 -4
- package/src/tools/segmentation/strategies/compositions/dynamicThreshold.ts +12 -2
- package/src/tools/segmentation/strategies/compositions/islandRemoval.ts +1 -2
- package/src/tools/segmentation/strategies/compositions/threshold.ts +8 -6
- package/src/tools/segmentation/strategies/utils/getStrategyData.ts +20 -5
- package/src/utilities/pointInShapeCallback.ts +2 -0
- package/src/utilities/segmentation/floodFill.ts +44 -31
- package/src/utilities/segmentation/getSegmentAtLabelmapBorder.ts +6 -5
- 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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
@@ -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:
|
|
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(
|
|
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
|
-
|
|
42
|
-
threshold[
|
|
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
|
-
|
|
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] <=
|
|
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
|
-
|
|
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
|
|
61
|
-
imageDimensions =
|
|
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
|
-
|
|
84
|
+
segmentationVoxelManager ||= VoxelManager.createVolumeVoxelManager(
|
|
70
85
|
segmentationDimensions,
|
|
71
86
|
segmentationScalarData
|
|
72
87
|
);
|
|
73
88
|
|
|
74
|
-
|
|
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
|
|
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
|
|
17
|
+
* @param seed The seed for your fill. The dimensionality is infered
|
|
15
18
|
* by the number of dimensions of the seed.
|
|
16
|
-
* @param
|
|
19
|
+
* @param options.onFlood - An optional callback to execute when each pixel is flooded.
|
|
17
20
|
* e.g. onFlood(x,y).
|
|
18
|
-
* @param
|
|
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
|
|
24
|
+
* @param options.equals - An optional equality method for your datastructure.
|
|
22
25
|
* Default is simply value1 = value2.
|
|
23
|
-
* @param
|
|
26
|
+
* @param options.diagonals - Whether you allow flooding through diagonals. Defaults to false.
|
|
24
27
|
*
|
|
25
|
-
* @returns
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
93
|
+
const node = get(getArgs);
|
|
81
94
|
|
|
82
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 =
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
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 =
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
119
|
+
const segmentIndex = voxelManager.getAtIJKPoint(indexIJK as Types.Point3);
|
|
119
120
|
|
|
120
121
|
return segmentIndex;
|
|
121
122
|
}
|