@cornerstonejs/tools 2.6.5 → 2.7.1
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/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +2 -1
- package/dist/esm/tools/annotation/LengthTool.js +6 -3
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.d.ts +15 -0
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +40 -0
- package/dist/esm/tools/annotation/RegionSegmentTool.d.ts +21 -0
- package/dist/esm/tools/annotation/RegionSegmentTool.js +103 -0
- package/dist/esm/tools/annotation/WholeBodySegmentTool.d.ts +28 -0
- package/dist/esm/tools/annotation/WholeBodySegmentTool.js +188 -0
- package/dist/esm/tools/base/ContourSegmentationBaseTool.js +5 -46
- package/dist/esm/tools/base/GrowCutBaseTool.d.ts +49 -0
- package/dist/esm/tools/base/GrowCutBaseTool.js +106 -0
- package/dist/esm/tools/index.d.ts +4 -1
- package/dist/esm/tools/index.js +4 -1
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +2 -2
- package/dist/esm/utilities/getSphereBoundsInfo.d.ts +5 -2
- package/dist/esm/utilities/getSphereBoundsInfo.js +36 -13
- package/dist/esm/utilities/segmentation/createLabelmapVolumeForViewport.js +1 -1
- package/dist/esm/utilities/segmentation/getSVGStyleForSegment.d.ts +17 -0
- package/dist/esm/utilities/segmentation/getSVGStyleForSegment.js +68 -0
- package/dist/esm/utilities/segmentation/growCut/growCutShader.d.ts +2 -0
- package/dist/esm/utilities/segmentation/growCut/growCutShader.js +106 -0
- package/dist/esm/utilities/segmentation/growCut/index.d.ts +7 -0
- package/dist/esm/utilities/segmentation/growCut/index.js +4 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCut.d.ts +16 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCut.js +269 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.d.ts +17 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.js +111 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForSphere.d.ts +9 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForSphere.js +164 -0
- package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.d.ts +13 -0
- package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.js +176 -0
- package/dist/esm/utilities/segmentation/index.d.ts +2 -1
- package/dist/esm/utilities/segmentation/index.js +2 -1
- package/package.json +3 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import { utilities as csUtils, cache, volumeLoader } from '@cornerstonejs/core';
|
|
3
|
+
import { run } from './runGrowCut';
|
|
4
|
+
const { transformWorldToIndex, transformIndexToWorld } = csUtils;
|
|
5
|
+
const POSITIVE_SEED_VALUE = 254;
|
|
6
|
+
const NEGATIVE_SEED_VALUE = 255;
|
|
7
|
+
const POSITIVE_SEED_VARIANCE = 0.1;
|
|
8
|
+
const NEGATIVE_SEED_VARIANCE = 0.8;
|
|
9
|
+
const SUBVOLUME_PADDING_PERCENTAGE = 0.2;
|
|
10
|
+
const SUBVOLUME_MIN_PADDING = 5;
|
|
11
|
+
function _createSubVolume(referencedVolume, positiveRegionData, options) {
|
|
12
|
+
const { dimensions } = referencedVolume;
|
|
13
|
+
const positiveRegionSize = vec3.sub(vec3.create(), positiveRegionData.boundingBox.bottomRight, positiveRegionData.boundingBox.topLeft);
|
|
14
|
+
let subVolumePaddingPercentage = options?.subVolumePaddingPercentage ?? SUBVOLUME_PADDING_PERCENTAGE;
|
|
15
|
+
let subVolumeMinPadding = options?.subVolumeMinPadding ?? SUBVOLUME_MIN_PADDING;
|
|
16
|
+
if (typeof subVolumePaddingPercentage === 'number') {
|
|
17
|
+
subVolumePaddingPercentage = [
|
|
18
|
+
subVolumePaddingPercentage,
|
|
19
|
+
subVolumePaddingPercentage,
|
|
20
|
+
subVolumePaddingPercentage,
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
if (typeof subVolumeMinPadding === 'number') {
|
|
24
|
+
subVolumeMinPadding = [
|
|
25
|
+
subVolumeMinPadding,
|
|
26
|
+
subVolumeMinPadding,
|
|
27
|
+
subVolumeMinPadding,
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
const padding = vec3.mul(vec3.create(), positiveRegionSize, subVolumePaddingPercentage);
|
|
31
|
+
vec3.round(padding, padding);
|
|
32
|
+
vec3.max(padding, padding, subVolumeMinPadding);
|
|
33
|
+
const subVolumeSize = vec3.scaleAndAdd(vec3.create(), positiveRegionSize, padding, 2);
|
|
34
|
+
const ijkTopLeft = vec3.sub(vec3.create(), positiveRegionData.boundingBox.topLeft, padding);
|
|
35
|
+
const ijkBottomRight = vec3.add(vec3.create(), ijkTopLeft, subVolumeSize);
|
|
36
|
+
vec3.max(ijkTopLeft, ijkTopLeft, [0, 0, 0]);
|
|
37
|
+
vec3.min(ijkTopLeft, ijkTopLeft, dimensions);
|
|
38
|
+
vec3.max(ijkBottomRight, ijkBottomRight, [0, 0, 0]);
|
|
39
|
+
vec3.min(ijkBottomRight, ijkBottomRight, dimensions);
|
|
40
|
+
const subVolumeBoundsIJK = {
|
|
41
|
+
minX: ijkTopLeft[0],
|
|
42
|
+
maxX: ijkBottomRight[0],
|
|
43
|
+
minY: ijkTopLeft[1],
|
|
44
|
+
maxY: ijkBottomRight[1],
|
|
45
|
+
minZ: ijkTopLeft[2],
|
|
46
|
+
maxZ: ijkBottomRight[2],
|
|
47
|
+
};
|
|
48
|
+
return csUtils.createSubVolume(referencedVolume.volumeId, subVolumeBoundsIJK, {
|
|
49
|
+
targetBuffer: {
|
|
50
|
+
type: 'Float32Array',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function _getPositiveRegionData(referencedVolume, worldPosition, options) {
|
|
55
|
+
const [width, height, numSlices] = referencedVolume.dimensions;
|
|
56
|
+
const subVolPixelData = referencedVolume.voxelManager.getCompleteScalarDataArray();
|
|
57
|
+
const numPixelsPerSlice = width * height;
|
|
58
|
+
const ijkStartPosition = transformWorldToIndex(referencedVolume.imageData, worldPosition);
|
|
59
|
+
const referencePixelValue = subVolPixelData[ijkStartPosition[2] * numPixelsPerSlice +
|
|
60
|
+
ijkStartPosition[1] * width +
|
|
61
|
+
ijkStartPosition[0]];
|
|
62
|
+
const positiveSeedVariance = options.positiveSeedVariance ?? POSITIVE_SEED_VARIANCE;
|
|
63
|
+
const positiveSeedVarianceValue = Math.abs(referencePixelValue * positiveSeedVariance);
|
|
64
|
+
const minPositivePixelValue = referencePixelValue - positiveSeedVarianceValue;
|
|
65
|
+
const maxPositivePixelValue = referencePixelValue + positiveSeedVarianceValue;
|
|
66
|
+
const neighborsCoordDelta = [
|
|
67
|
+
[-1, 0, 0],
|
|
68
|
+
[1, 0, 0],
|
|
69
|
+
[0, -1, 0],
|
|
70
|
+
[0, 1, 0],
|
|
71
|
+
[0, 0, -1],
|
|
72
|
+
[0, 0, 1],
|
|
73
|
+
];
|
|
74
|
+
let minX = Infinity;
|
|
75
|
+
let minY = Infinity;
|
|
76
|
+
let minZ = Infinity;
|
|
77
|
+
let maxX = -Infinity;
|
|
78
|
+
let maxY = -Infinity;
|
|
79
|
+
let maxZ = -Infinity;
|
|
80
|
+
const startVoxelIndex = ijkStartPosition[2] * numPixelsPerSlice +
|
|
81
|
+
ijkStartPosition[1] * width +
|
|
82
|
+
ijkStartPosition[0];
|
|
83
|
+
const voxelIndexesSet = new Set([startVoxelIndex]);
|
|
84
|
+
const worldVoxelSet = new Set([worldPosition]);
|
|
85
|
+
const queue = [ijkStartPosition];
|
|
86
|
+
while (queue.length) {
|
|
87
|
+
const ijkVoxel = queue.shift();
|
|
88
|
+
const [x, y, z] = ijkVoxel;
|
|
89
|
+
minX = ijkVoxel[0] < minX ? ijkVoxel[0] : minX;
|
|
90
|
+
minY = ijkVoxel[1] < minY ? ijkVoxel[1] : minY;
|
|
91
|
+
minZ = ijkVoxel[2] < minZ ? ijkVoxel[2] : minZ;
|
|
92
|
+
maxX = ijkVoxel[0] > maxX ? ijkVoxel[0] : maxX;
|
|
93
|
+
maxY = ijkVoxel[1] > maxY ? ijkVoxel[1] : maxY;
|
|
94
|
+
maxZ = ijkVoxel[2] > maxZ ? ijkVoxel[2] : maxZ;
|
|
95
|
+
for (let i = 0, len = neighborsCoordDelta.length; i < len; i++) {
|
|
96
|
+
const neighborCoordDelta = neighborsCoordDelta[i];
|
|
97
|
+
const nx = x + neighborCoordDelta[0];
|
|
98
|
+
const ny = y + neighborCoordDelta[1];
|
|
99
|
+
const nz = z + neighborCoordDelta[2];
|
|
100
|
+
if (nx < 0 ||
|
|
101
|
+
nx >= width ||
|
|
102
|
+
ny < 0 ||
|
|
103
|
+
ny >= height ||
|
|
104
|
+
nz < 0 ||
|
|
105
|
+
nz >= numSlices) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const neighborVoxelIndex = nz * numPixelsPerSlice + ny * width + nx;
|
|
109
|
+
const neighborPixelValue = subVolPixelData[neighborVoxelIndex];
|
|
110
|
+
if (voxelIndexesSet.has(neighborVoxelIndex) ||
|
|
111
|
+
neighborPixelValue < minPositivePixelValue ||
|
|
112
|
+
neighborPixelValue > maxPositivePixelValue) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const ijkVoxel = [nx, ny, nz];
|
|
116
|
+
const worldVoxel = transformIndexToWorld(referencedVolume.imageData, ijkVoxel);
|
|
117
|
+
voxelIndexesSet.add(neighborVoxelIndex);
|
|
118
|
+
worldVoxelSet.add(worldVoxel);
|
|
119
|
+
queue.push(ijkVoxel);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
worldVoxels: Array.from(worldVoxelSet),
|
|
124
|
+
boundingBox: {
|
|
125
|
+
topLeft: [minX, minY, minZ],
|
|
126
|
+
bottomRight: [maxX, maxY, maxZ],
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function _setPositiveSeedValues(labelmap, positiveRegionData, options) {
|
|
131
|
+
const { dimensions } = labelmap;
|
|
132
|
+
const [width, height] = dimensions;
|
|
133
|
+
const numPixelsPerSlice = width * height;
|
|
134
|
+
const positiveSeedValue = options.positiveSeedValue ?? POSITIVE_SEED_VALUE;
|
|
135
|
+
const { worldVoxels } = positiveRegionData;
|
|
136
|
+
for (let i = 0, len = worldVoxels.length; i < len; i++) {
|
|
137
|
+
const worldVoxel = worldVoxels[i];
|
|
138
|
+
const ijkVoxel = transformWorldToIndex(labelmap.imageData, worldVoxel);
|
|
139
|
+
const voxelIndex = ijkVoxel[2] * numPixelsPerSlice + ijkVoxel[1] * width + ijkVoxel[0];
|
|
140
|
+
labelmap.voxelManager.setAtIndex(voxelIndex, positiveSeedValue);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function _setNegativeSeedValues(subVolume, labelmap, worldPosition, options) {
|
|
144
|
+
const [width, height] = subVolume.dimensions;
|
|
145
|
+
const subVolPixelData = subVolume.voxelManager.getCompleteScalarDataArray();
|
|
146
|
+
const labelmapData = labelmap.voxelManager.getCompleteScalarDataArray();
|
|
147
|
+
const ijkPosition = transformWorldToIndex(subVolume.imageData, worldPosition);
|
|
148
|
+
const referencePixelValue = subVolPixelData[ijkPosition[2] * width * height + ijkPosition[1] * width + ijkPosition[0]];
|
|
149
|
+
const negativeSeedVariance = options.negativeSeedVariance ?? NEGATIVE_SEED_VARIANCE;
|
|
150
|
+
const negativeSeedValue = options.negativeSeedValue ?? NEGATIVE_SEED_VALUE;
|
|
151
|
+
const negativeSeedVarianceValue = Math.abs(referencePixelValue * negativeSeedVariance);
|
|
152
|
+
const minNegativePixelValue = referencePixelValue - negativeSeedVarianceValue;
|
|
153
|
+
const maxNegativePixelValue = referencePixelValue + negativeSeedVarianceValue;
|
|
154
|
+
for (let i = 0, len = subVolPixelData.length; i < len; i++) {
|
|
155
|
+
const pixelValue = subVolPixelData[i];
|
|
156
|
+
if (!labelmapData[i] &&
|
|
157
|
+
(pixelValue < minNegativePixelValue || pixelValue > maxNegativePixelValue)) {
|
|
158
|
+
labelmap.voxelManager.setAtIndex(i, negativeSeedValue);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function _createAndCacheSegmentation(subVolume, positiveRegionData, worldPosition, options) {
|
|
163
|
+
const labelmap = volumeLoader.createAndCacheDerivedLabelmapVolume(subVolume.volumeId);
|
|
164
|
+
_setPositiveSeedValues(labelmap, positiveRegionData, options);
|
|
165
|
+
_setNegativeSeedValues(subVolume, labelmap, worldPosition, options);
|
|
166
|
+
return labelmap;
|
|
167
|
+
}
|
|
168
|
+
async function runOneClickGrowCut(referencedVolumeId, worldPosition, viewport, options) {
|
|
169
|
+
const referencedVolume = cache.getVolume(referencedVolumeId);
|
|
170
|
+
const positiveRegionData = _getPositiveRegionData(referencedVolume, worldPosition, options);
|
|
171
|
+
const subVolume = _createSubVolume(referencedVolume, positiveRegionData, options);
|
|
172
|
+
const labelmap = await _createAndCacheSegmentation(subVolume, positiveRegionData, worldPosition, options);
|
|
173
|
+
await run(subVolume.volumeId, labelmap.volumeId);
|
|
174
|
+
return labelmap;
|
|
175
|
+
}
|
|
176
|
+
export { runOneClickGrowCut as default, runOneClickGrowCut };
|
|
@@ -17,4 +17,5 @@ import { getSegmentIndexAtWorldPoint } from './getSegmentIndexAtWorldPoint';
|
|
|
17
17
|
import { getSegmentIndexAtLabelmapBorder } from './getSegmentIndexAtLabelmapBorder';
|
|
18
18
|
import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegmentationAnnotation';
|
|
19
19
|
import { getBrushToolInstances } from './getBrushToolInstances';
|
|
20
|
-
|
|
20
|
+
import * as growCut from './growCut';
|
|
21
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, };
|
|
@@ -17,4 +17,5 @@ import { getSegmentIndexAtWorldPoint } from './getSegmentIndexAtWorldPoint';
|
|
|
17
17
|
import { getSegmentIndexAtLabelmapBorder } from './getSegmentIndexAtLabelmapBorder';
|
|
18
18
|
import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegmentationAnnotation';
|
|
19
19
|
import { getBrushToolInstances } from './getBrushToolInstances';
|
|
20
|
-
|
|
20
|
+
import * as growCut from './growCut';
|
|
21
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"canvas": "^2.11.2"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@cornerstonejs/core": "^2.
|
|
107
|
+
"@cornerstonejs/core": "^2.7.1",
|
|
108
108
|
"@kitware/vtk.js": "32.1.1",
|
|
109
109
|
"@types/d3-array": "^3.0.4",
|
|
110
110
|
"@types/d3-interpolate": "^3.0.1",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"type": "individual",
|
|
124
124
|
"url": "https://ohif.org/donate"
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "bad9df9ca06d63d00d0ea9463364c355c1a57850"
|
|
127
127
|
}
|