@cornerstonejs/tools 3.5.3 → 3.6.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/config.d.ts +7 -0
- package/dist/esm/enums/WorkerTypes.d.ts +2 -1
- package/dist/esm/enums/WorkerTypes.js +1 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/tools/annotation/BidirectionalTool.d.ts +3 -0
- package/dist/esm/tools/annotation/BidirectionalTool.js +39 -1
- package/dist/esm/tools/index.d.ts +2 -1
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/segmentation/SegmentBidirectionalTool.d.ts +27 -0
- package/dist/esm/tools/segmentation/SegmentBidirectionalTool.js +253 -0
- package/dist/esm/tools/segmentation/strategies/compositions/ensureImageVolume.js +5 -8
- package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +5 -10
- package/dist/esm/types/CalculatorTypes.d.ts +21 -3
- package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +34 -0
- package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +1 -0
- package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +88 -13
- package/dist/esm/utilities/registerComputeWorker.js +4 -1
- package/dist/esm/utilities/segmentation/VolumetricCalculator.js +12 -2
- package/dist/esm/utilities/segmentation/computeMetabolicStats.d.ts +8 -0
- package/dist/esm/utilities/segmentation/computeMetabolicStats.js +58 -0
- package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js +7 -5
- package/dist/esm/utilities/segmentation/createMergedLabelmapForIndex.js +10 -2
- package/dist/esm/utilities/segmentation/findLargestBidirectional.d.ts +5 -0
- package/dist/esm/utilities/segmentation/findLargestBidirectional.js +1 -1
- package/dist/esm/utilities/segmentation/getOrCreateImageVolume.d.ts +3 -0
- package/dist/esm/utilities/segmentation/getOrCreateImageVolume.js +18 -0
- package/dist/esm/utilities/segmentation/getOrCreateSegmentationVolume.d.ts +2 -1
- package/dist/esm/utilities/segmentation/getOrCreateSegmentationVolume.js +5 -1
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentation.d.ts +1 -0
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentation.js +34 -0
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.d.ts +1 -0
- package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.js +20 -0
- package/dist/esm/utilities/segmentation/getSegmentLargestBidirectional.d.ts +5 -0
- package/dist/esm/utilities/segmentation/getSegmentLargestBidirectional.js +54 -0
- package/dist/esm/utilities/segmentation/getStatistics.js +50 -108
- package/dist/esm/utilities/segmentation/index.d.ts +5 -1
- package/dist/esm/utilities/segmentation/index.js +5 -1
- package/dist/esm/utilities/segmentation/isLineInSegment.d.ts +13 -1
- package/dist/esm/utilities/segmentation/isLineInSegment.js +20 -12
- package/dist/esm/utilities/segmentation/segmentContourAction.js +1 -0
- package/dist/esm/utilities/segmentation/utilsForWorker.d.ts +38 -0
- package/dist/esm/utilities/segmentation/utilsForWorker.js +125 -0
- package/dist/esm/workers/computeWorker.js +336 -38
- package/package.json +4 -4
|
@@ -13,6 +13,9 @@ function createBasicStatsState(storePointData) {
|
|
|
13
13
|
minLPS: null,
|
|
14
14
|
runMean: [0],
|
|
15
15
|
m2: [0],
|
|
16
|
+
m3: [0],
|
|
17
|
+
m4: [0],
|
|
18
|
+
allValues: [[]],
|
|
16
19
|
pointsInShape: storePointData ? PointsManager.create3(1024) : null,
|
|
17
20
|
};
|
|
18
21
|
}
|
|
@@ -25,6 +28,9 @@ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
|
|
|
25
28
|
state.sum.push(state.sum[0], state.sum[0]);
|
|
26
29
|
state.runMean.push(0, 0);
|
|
27
30
|
state.m2.push(state.m2[0], state.m2[0]);
|
|
31
|
+
state.m3.push(state.m3[0], state.m3[0]);
|
|
32
|
+
state.m4.push(state.m4[0], state.m4[0]);
|
|
33
|
+
state.allValues.push([], []);
|
|
28
34
|
}
|
|
29
35
|
if (state?.pointsInShape && pointLPS) {
|
|
30
36
|
state.pointsInShape.push(pointLPS);
|
|
@@ -33,46 +39,82 @@ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
|
|
|
33
39
|
state.count += 1;
|
|
34
40
|
state.max.forEach((it, idx) => {
|
|
35
41
|
const value = newArray[idx];
|
|
42
|
+
state.allValues[idx].push(value);
|
|
43
|
+
const n = state.count;
|
|
36
44
|
const delta = value - state.runMean[idx];
|
|
45
|
+
const delta_n = delta / n;
|
|
46
|
+
const term1 = delta * delta_n * (n - 1);
|
|
37
47
|
state.sum[idx] += value;
|
|
38
|
-
state.runMean[idx] +=
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
state.runMean[idx] += delta_n;
|
|
49
|
+
state.m4[idx] +=
|
|
50
|
+
term1 * delta_n * delta_n * (n * n - 3 * n + 3) +
|
|
51
|
+
6 * delta_n * delta_n * state.m2[idx] -
|
|
52
|
+
4 * delta_n * state.m3[idx];
|
|
53
|
+
state.m3[idx] += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2[idx];
|
|
54
|
+
state.m2[idx] += term1;
|
|
41
55
|
if (value < state.min[idx]) {
|
|
42
56
|
state.min[idx] = value;
|
|
43
57
|
if (idx === 0) {
|
|
44
|
-
state.minIJK = pointIJK;
|
|
45
|
-
state.minLPS = pointLPS;
|
|
58
|
+
state.minIJK = pointIJK ? [...pointIJK] : null;
|
|
59
|
+
state.minLPS = pointLPS ? [...pointLPS] : null;
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
if (value > state.max[idx]) {
|
|
49
63
|
state.max[idx] = value;
|
|
50
64
|
if (idx === 0) {
|
|
51
|
-
state.maxIJK = pointIJK;
|
|
52
|
-
state.maxLPS = pointLPS;
|
|
65
|
+
state.maxIJK = pointIJK ? [...pointIJK] : null;
|
|
66
|
+
state.maxLPS = pointLPS ? [...pointLPS] : null;
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
});
|
|
56
70
|
}
|
|
71
|
+
function calculateMedian(values) {
|
|
72
|
+
if (values.length === 0) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
76
|
+
const mid = Math.floor(sorted.length / 2);
|
|
77
|
+
if (sorted.length % 2 === 0) {
|
|
78
|
+
return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
return sorted[mid];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
57
84
|
function basicGetStatistics(state, unit) {
|
|
58
85
|
const mean = state.sum.map((sum) => sum / state.count);
|
|
59
86
|
const stdDev = state.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / state.count));
|
|
87
|
+
const skewness = state.m3.map((m3, idx) => {
|
|
88
|
+
const variance = state.m2[idx] / state.count;
|
|
89
|
+
if (variance === 0) {
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
return m3 / (state.count * Math.pow(variance, 1.5));
|
|
93
|
+
});
|
|
94
|
+
const kurtosis = state.m4.map((m4, idx) => {
|
|
95
|
+
const variance = state.m2[idx] / state.count;
|
|
96
|
+
if (variance === 0) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
return m4 / (state.count * variance * variance) - 3;
|
|
100
|
+
});
|
|
101
|
+
const median = state.allValues.map((values) => calculateMedian(values));
|
|
60
102
|
const named = {
|
|
61
103
|
max: {
|
|
62
104
|
name: 'max',
|
|
63
105
|
label: 'Max Pixel',
|
|
64
106
|
value: state.max.length === 1 ? state.max[0] : state.max,
|
|
65
107
|
unit,
|
|
66
|
-
pointIJK: state.maxIJK,
|
|
67
|
-
pointLPS: state.maxLPS,
|
|
108
|
+
pointIJK: state.maxIJK ? [...state.maxIJK] : null,
|
|
109
|
+
pointLPS: state.maxLPS ? [...state.maxLPS] : null,
|
|
68
110
|
},
|
|
69
111
|
min: {
|
|
70
112
|
name: 'min',
|
|
71
113
|
label: 'Min Pixel',
|
|
72
114
|
value: state.min.length === 1 ? state.min[0] : state.min,
|
|
73
115
|
unit,
|
|
74
|
-
pointIJK: state.minIJK,
|
|
75
|
-
pointLPS: state.minLPS,
|
|
116
|
+
pointIJK: state.minIJK ? [...state.minIJK] : null,
|
|
117
|
+
pointLPS: state.minLPS ? [...state.minLPS] : null,
|
|
76
118
|
},
|
|
77
119
|
mean: {
|
|
78
120
|
name: 'mean',
|
|
@@ -88,14 +130,44 @@ function basicGetStatistics(state, unit) {
|
|
|
88
130
|
},
|
|
89
131
|
count: {
|
|
90
132
|
name: 'count',
|
|
91
|
-
label: '
|
|
133
|
+
label: 'Voxel Count',
|
|
92
134
|
value: state.count,
|
|
93
135
|
unit: null,
|
|
94
136
|
},
|
|
137
|
+
median: {
|
|
138
|
+
name: 'median',
|
|
139
|
+
label: 'Median',
|
|
140
|
+
value: median.length === 1 ? median[0] : median,
|
|
141
|
+
unit,
|
|
142
|
+
},
|
|
143
|
+
skewness: {
|
|
144
|
+
name: 'skewness',
|
|
145
|
+
label: 'Skewness',
|
|
146
|
+
value: skewness.length === 1 ? skewness[0] : skewness,
|
|
147
|
+
unit: null,
|
|
148
|
+
},
|
|
149
|
+
kurtosis: {
|
|
150
|
+
name: 'kurtosis',
|
|
151
|
+
label: 'Kurtosis',
|
|
152
|
+
value: kurtosis.length === 1 ? kurtosis[0] : kurtosis,
|
|
153
|
+
unit: null,
|
|
154
|
+
},
|
|
155
|
+
maxLPS: {
|
|
156
|
+
name: 'maxLPS',
|
|
157
|
+
label: 'Max LPS',
|
|
158
|
+
value: state.maxLPS ? Array.from(state.maxLPS) : null,
|
|
159
|
+
unit: null,
|
|
160
|
+
},
|
|
161
|
+
minLPS: {
|
|
162
|
+
name: 'minLPS',
|
|
163
|
+
label: 'Min LPS',
|
|
164
|
+
value: state.minLPS ? Array.from(state.minLPS) : null,
|
|
165
|
+
unit: null,
|
|
166
|
+
},
|
|
95
167
|
pointsInShape: state.pointsInShape,
|
|
96
168
|
array: [],
|
|
97
169
|
};
|
|
98
|
-
named.array.push(named.max, named.mean, named.stdDev, named.
|
|
170
|
+
named.array.push(named.min, named.max, named.mean, named.stdDev, named.median, named.skewness, named.kurtosis, named.count, named.maxLPS, named.minLPS);
|
|
99
171
|
const store = state.pointsInShape !== null;
|
|
100
172
|
const freshState = createBasicStatsState(store);
|
|
101
173
|
state.max = freshState.max;
|
|
@@ -108,6 +180,9 @@ function basicGetStatistics(state, unit) {
|
|
|
108
180
|
state.minLPS = freshState.minLPS;
|
|
109
181
|
state.runMean = freshState.runMean;
|
|
110
182
|
state.m2 = freshState.m2;
|
|
183
|
+
state.m3 = freshState.m3;
|
|
184
|
+
state.m4 = freshState.m4;
|
|
185
|
+
state.allValues = freshState.allValues;
|
|
111
186
|
state.pointsInShape = freshState.pointsInShape;
|
|
112
187
|
return named;
|
|
113
188
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getWebWorkerManager } from '@cornerstonejs/core';
|
|
2
|
+
import { getConfig } from '../config';
|
|
2
3
|
let registered = false;
|
|
3
4
|
export function registerComputeWorker() {
|
|
4
5
|
if (registered) {
|
|
@@ -12,9 +13,11 @@ export function registerComputeWorker() {
|
|
|
12
13
|
});
|
|
13
14
|
};
|
|
14
15
|
const workerManager = getWebWorkerManager();
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
const computeWorkerConfig = config.computeWorker;
|
|
15
18
|
const options = {
|
|
16
19
|
maxWorkerInstances: 1,
|
|
17
|
-
autoTerminateOnIdle: {
|
|
20
|
+
autoTerminateOnIdle: computeWorkerConfig?.autoTerminateOnIdle ?? {
|
|
18
21
|
enabled: true,
|
|
19
22
|
idleTimeThreshold: 2000,
|
|
20
23
|
},
|
|
@@ -14,13 +14,22 @@ function volumetricStatsCallback(state, data) {
|
|
|
14
14
|
(length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
+
const dataCopy = {
|
|
18
|
+
value: data.value,
|
|
19
|
+
pointLPS: data.pointLPS
|
|
20
|
+
? [data.pointLPS[0], data.pointLPS[1], data.pointLPS[2]]
|
|
21
|
+
: undefined,
|
|
22
|
+
pointIJK: data.pointIJK
|
|
23
|
+
? [data.pointIJK[0], data.pointIJK[1], data.pointIJK[2]]
|
|
24
|
+
: undefined,
|
|
25
|
+
};
|
|
17
26
|
if (!length || value >= maxIJKs[length - 1].value) {
|
|
18
|
-
maxIJKs.push(
|
|
27
|
+
maxIJKs.push(dataCopy);
|
|
19
28
|
}
|
|
20
29
|
else {
|
|
21
30
|
for (let i = 0; i < length; i++) {
|
|
22
31
|
if (value <= maxIJKs[i].value) {
|
|
23
|
-
maxIJKs.splice(i, 0,
|
|
32
|
+
maxIJKs.splice(i, 0, dataCopy);
|
|
24
33
|
break;
|
|
25
34
|
}
|
|
26
35
|
}
|
|
@@ -42,6 +51,7 @@ function volumetricGetStatistics(state, stats, options) {
|
|
|
42
51
|
: stats.count.value * volumeScale,
|
|
43
52
|
unit: volumeUnit,
|
|
44
53
|
name: 'volume',
|
|
54
|
+
label: 'Volume',
|
|
45
55
|
};
|
|
46
56
|
stats.maxIJKs = state.maxIJKs.filter((entry) => entry.pointIJK !== undefined);
|
|
47
57
|
stats.array.push(stats.volume);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NamedStatistics } from '../../types';
|
|
2
|
+
declare function computeMetabolicStats({ segmentationIds, segmentIndex, }: {
|
|
3
|
+
segmentationIds: string[];
|
|
4
|
+
segmentIndex: number;
|
|
5
|
+
}): Promise<NamedStatistics | {
|
|
6
|
+
[segmentIndex: number]: NamedStatistics;
|
|
7
|
+
}>;
|
|
8
|
+
export { computeMetabolicStats };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { utilities, getWebWorkerManager } from '@cornerstonejs/core';
|
|
2
|
+
import { triggerWorkerProgress } from './utilsForWorker';
|
|
3
|
+
import { WorkerTypes } from '../../enums';
|
|
4
|
+
import { registerComputeWorker } from '../registerComputeWorker';
|
|
5
|
+
import createMergedLabelmapForIndex from './createMergedLabelmapForIndex';
|
|
6
|
+
import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
|
|
7
|
+
import getOrCreateSegmentationVolume from './getOrCreateSegmentationVolume';
|
|
8
|
+
import { getReferenceVolumeForSegmentation } from './getReferenceVolumeForSegmentation';
|
|
9
|
+
async function computeMetabolicStats({ segmentationIds, segmentIndex, }) {
|
|
10
|
+
registerComputeWorker();
|
|
11
|
+
triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 0);
|
|
12
|
+
const segmentation = getSegmentation(segmentationIds[0]);
|
|
13
|
+
const { imageIds: segImageIds } = segmentation.representationData
|
|
14
|
+
.Labelmap;
|
|
15
|
+
const isValidVolume = utilities.isValidVolume(segImageIds);
|
|
16
|
+
if (!isValidVolume) {
|
|
17
|
+
throw new Error('Invalid volume - TMTV cannot be calculated');
|
|
18
|
+
}
|
|
19
|
+
const stats = await calculateForVolume({
|
|
20
|
+
segmentationIds,
|
|
21
|
+
segmentIndex,
|
|
22
|
+
});
|
|
23
|
+
return stats;
|
|
24
|
+
}
|
|
25
|
+
async function calculateForVolume({ segmentationIds, segmentIndex }) {
|
|
26
|
+
const labelmapVolumes = segmentationIds.map((id) => {
|
|
27
|
+
return getOrCreateSegmentationVolume(id);
|
|
28
|
+
});
|
|
29
|
+
const mergedLabelmap = createMergedLabelmapForIndex(labelmapVolumes, segmentIndex);
|
|
30
|
+
if (!mergedLabelmap) {
|
|
31
|
+
throw new Error('Invalid volume - TMTV cannot be calculated');
|
|
32
|
+
}
|
|
33
|
+
const { imageData, dimensions, direction, origin, voxelManager } = mergedLabelmap;
|
|
34
|
+
const spacing = imageData.getSpacing();
|
|
35
|
+
const segmentationScalarData = voxelManager.getCompleteScalarDataArray();
|
|
36
|
+
const segmentationInfo = {
|
|
37
|
+
scalarData: segmentationScalarData,
|
|
38
|
+
dimensions,
|
|
39
|
+
spacing,
|
|
40
|
+
origin,
|
|
41
|
+
direction,
|
|
42
|
+
};
|
|
43
|
+
const referenceVolume = getReferenceVolumeForSegmentation(segmentationIds[0]);
|
|
44
|
+
const imageInfo = {
|
|
45
|
+
dimensions: referenceVolume.dimensions,
|
|
46
|
+
spacing: referenceVolume.spacing,
|
|
47
|
+
origin: referenceVolume.origin,
|
|
48
|
+
direction: referenceVolume.direction,
|
|
49
|
+
scalarData: referenceVolume.voxelManager.getCompleteScalarDataArray(),
|
|
50
|
+
};
|
|
51
|
+
const stats = await getWebWorkerManager().executeTask('compute', 'computeMetabolicStats', {
|
|
52
|
+
segmentationInfo,
|
|
53
|
+
imageInfo,
|
|
54
|
+
});
|
|
55
|
+
triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 100);
|
|
56
|
+
return stats;
|
|
57
|
+
}
|
|
58
|
+
export { computeMetabolicStats };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { generateContourSetsFromLabelmap } from '../contours';
|
|
2
|
-
import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
|
|
3
2
|
import findLargestBidirectional from './findLargestBidirectional';
|
|
4
|
-
|
|
3
|
+
import getOrCreateSegmentationVolume from './getOrCreateSegmentationVolume';
|
|
5
4
|
export default function contourAndFindLargestBidirectional(segmentation) {
|
|
6
5
|
const contours = generateContourSetsFromLabelmap({
|
|
7
6
|
segmentations: segmentation,
|
|
@@ -9,15 +8,18 @@ export default function contourAndFindLargestBidirectional(segmentation) {
|
|
|
9
8
|
if (!contours?.length || !contours[0].sliceContours.length) {
|
|
10
9
|
return;
|
|
11
10
|
}
|
|
12
|
-
const {
|
|
11
|
+
const { segments = [
|
|
13
12
|
null,
|
|
14
13
|
{ label: 'Unspecified', color: null, containedSegmentIndices: null },
|
|
15
14
|
], } = segmentation;
|
|
16
|
-
const
|
|
15
|
+
const vol = getOrCreateSegmentationVolume(segmentation.segmentationId);
|
|
16
|
+
if (!vol) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
17
19
|
const segmentIndex = segments.findIndex((it) => !!it);
|
|
18
20
|
if (segmentIndex === -1) {
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
23
|
segments[segmentIndex].segmentIndex = segmentIndex;
|
|
22
|
-
return findLargestBidirectional(contours[0],
|
|
24
|
+
return findLargestBidirectional(contours[0], vol.volumeId, segments[segmentIndex]);
|
|
23
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { volumeLoader, utilities as csUtils } from '@cornerstonejs/core';
|
|
1
|
+
import { volumeLoader, utilities as csUtils, cache } from '@cornerstonejs/core';
|
|
2
2
|
function createMergedLabelmapForIndex(labelmaps, segmentIndex = 1, volumeId = 'mergedLabelmap') {
|
|
3
3
|
labelmaps.forEach(({ direction, dimensions, origin, spacing }) => {
|
|
4
4
|
if (!csUtils.isEqual(dimensions, labelmaps[0].dimensions) ||
|
|
@@ -28,7 +28,15 @@ function createMergedLabelmapForIndex(labelmaps, segmentIndex = 1, volumeId = 'm
|
|
|
28
28
|
direction: labelmap.direction,
|
|
29
29
|
dimensions: labelmap.dimensions,
|
|
30
30
|
};
|
|
31
|
-
const
|
|
31
|
+
const cachedVolume = cache.getVolume(volumeId);
|
|
32
|
+
let mergedVolume;
|
|
33
|
+
if (cachedVolume) {
|
|
34
|
+
mergedVolume = cachedVolume;
|
|
35
|
+
mergedVolume.voxelManager.setCompleteScalarDataArray(outputData);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
mergedVolume = volumeLoader.createLocalVolume(volumeId, options);
|
|
39
|
+
}
|
|
32
40
|
return mergedVolume;
|
|
33
41
|
}
|
|
34
42
|
export default createMergedLabelmapForIndex;
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
import type { BidirectionalData } from './createBidirectionalToolData';
|
|
1
2
|
export default function findLargestBidirectional(contours: any, segVolumeId: string, segment: any): any;
|
|
3
|
+
export declare function createBidirectionalForSlice(sliceContour: any, isInSegment: any, currentMax?: {
|
|
4
|
+
maxMajor: number;
|
|
5
|
+
maxMinor: number;
|
|
6
|
+
}): BidirectionalData;
|
|
@@ -18,7 +18,7 @@ export default function findLargestBidirectional(contours, segVolumeId, segment)
|
|
|
18
18
|
}
|
|
19
19
|
return maxBidirectional;
|
|
20
20
|
}
|
|
21
|
-
function createBidirectionalForSlice(sliceContour, isInSegment, currentMax = { maxMajor: 0, maxMinor: 0 }) {
|
|
21
|
+
export function createBidirectionalForSlice(sliceContour, isInSegment, currentMax = { maxMajor: 0, maxMinor: 0 }) {
|
|
22
22
|
const { points } = sliceContour.polyData;
|
|
23
23
|
const { maxMinor: currentMaxMinor, maxMajor: currentMaxMajor } = currentMax;
|
|
24
24
|
let maxMajor = currentMaxMajor * currentMaxMajor;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cache, volumeLoader, utilities as csUtils, } from '@cornerstonejs/core';
|
|
2
|
+
function getOrCreateImageVolume(referencedImageIds) {
|
|
3
|
+
if (!referencedImageIds || referencedImageIds.length <= 1) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const isValidVolume = csUtils.isValidVolume(referencedImageIds);
|
|
7
|
+
if (!isValidVolume) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const volumeId = cache.generateVolumeId(referencedImageIds);
|
|
11
|
+
let imageVolume = cache.getVolume(volumeId);
|
|
12
|
+
if (imageVolume) {
|
|
13
|
+
return imageVolume;
|
|
14
|
+
}
|
|
15
|
+
imageVolume = volumeLoader.createAndCacheVolumeFromImagesSync(volumeId, referencedImageIds);
|
|
16
|
+
return imageVolume;
|
|
17
|
+
}
|
|
18
|
+
export default getOrCreateImageVolume;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import { type Types } from '@cornerstonejs/core';
|
|
2
|
+
declare function getOrCreateSegmentationVolume(segmentationId: any): Types.IImageVolume | undefined;
|
|
2
3
|
export default getOrCreateSegmentationVolume;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cache, volumeLoader } from '@cornerstonejs/core';
|
|
1
|
+
import { cache, volumeLoader, utilities, } from '@cornerstonejs/core';
|
|
2
2
|
import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
|
|
3
3
|
function getOrCreateSegmentationVolume(segmentationId) {
|
|
4
4
|
const { representationData } = getSegmentation(segmentationId);
|
|
@@ -15,6 +15,10 @@ function getOrCreateSegmentationVolume(segmentationId) {
|
|
|
15
15
|
if (!labelmapImageIds || labelmapImageIds.length === 1) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
+
const isValidVolume = utilities.isValidVolume(labelmapImageIds);
|
|
19
|
+
if (!isValidVolume) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
segVolume = volumeLoader.createAndCacheVolumeFromImagesSync(volumeId, labelmapImageIds);
|
|
19
23
|
return segVolume;
|
|
20
24
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getReferenceVolumeForSegmentation(segmentationId: string): import("@cornerstonejs/core").ImageVolume;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cache } from '@cornerstonejs/core';
|
|
2
|
+
import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
|
|
3
|
+
import getOrCreateImageVolume from './getOrCreateImageVolume';
|
|
4
|
+
export function getReferenceVolumeForSegmentation(segmentationId) {
|
|
5
|
+
const segmentation = getSegmentation(segmentationId);
|
|
6
|
+
if (!segmentation) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
let referenceImageIds;
|
|
10
|
+
const labelmap = segmentation.representationData.Labelmap;
|
|
11
|
+
if ('imageIds' in labelmap) {
|
|
12
|
+
const { imageIds } = labelmap;
|
|
13
|
+
const firstImage = cache.getImage(imageIds[0]);
|
|
14
|
+
const volumeInfo = cache.getVolumeContainingImageId(firstImage.referencedImageId);
|
|
15
|
+
if (volumeInfo?.volume) {
|
|
16
|
+
return volumeInfo.volume;
|
|
17
|
+
}
|
|
18
|
+
referenceImageIds = imageIds.map((imageId) => cache.getImage(imageId).referencedImageId);
|
|
19
|
+
}
|
|
20
|
+
else if ('volumeId' in labelmap) {
|
|
21
|
+
const { volumeId, referencedVolumeId } = labelmap;
|
|
22
|
+
if (referencedVolumeId) {
|
|
23
|
+
const refVolume = cache.getVolume(referencedVolumeId);
|
|
24
|
+
if (refVolume) {
|
|
25
|
+
return refVolume;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const segVolume = cache.getVolume(volumeId);
|
|
29
|
+
if (segVolume) {
|
|
30
|
+
referenceImageIds = segVolume.imageIds.map((imageId) => cache.getImage(imageId).referencedImageId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return getOrCreateImageVolume(referenceImageIds);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getReferenceVolumeForSegmentationVolume(segmentationVolumeId: string): any;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cache } from '@cornerstonejs/core';
|
|
2
|
+
export function getReferenceVolumeForSegmentationVolume(segmentationVolumeId) {
|
|
3
|
+
const segmentationVolume = cache.getVolume(segmentationVolumeId);
|
|
4
|
+
if (!segmentationVolume) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const referencedVolumeId = segmentationVolume.referencedVolumeId;
|
|
8
|
+
let imageVolume;
|
|
9
|
+
if (referencedVolumeId) {
|
|
10
|
+
imageVolume = cache.getVolume(referencedVolumeId);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const imageIds = segmentationVolume.imageIds;
|
|
14
|
+
const image = cache.getImage(imageIds[0]);
|
|
15
|
+
const referencedImageId = image.referencedImageId;
|
|
16
|
+
const volumeInfo = cache.getVolumeContainingImageId(referencedImageId);
|
|
17
|
+
imageVolume = volumeInfo?.volume;
|
|
18
|
+
}
|
|
19
|
+
return imageVolume;
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getWebWorkerManager } from '@cornerstonejs/core';
|
|
2
|
+
import { WorkerTypes } from '../../enums';
|
|
3
|
+
import { registerComputeWorker } from '../registerComputeWorker';
|
|
4
|
+
import { triggerWorkerProgress, getSegmentationDataForWorker, prepareVolumeStrategyDataForWorker, prepareStackDataForWorker, } from './utilsForWorker';
|
|
5
|
+
export async function getSegmentLargestBidirectional({ segmentationId, segmentIndices, mode = 'individual', }) {
|
|
6
|
+
registerComputeWorker();
|
|
7
|
+
triggerWorkerProgress(WorkerTypes.COMPUTE_LARGEST_BIDIRECTIONAL, 0);
|
|
8
|
+
const segData = getSegmentationDataForWorker(segmentationId, segmentIndices);
|
|
9
|
+
if (!segData) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const { operationData, segImageIds, reconstructableVolume, indices } = segData;
|
|
13
|
+
const bidirectionalData = reconstructableVolume
|
|
14
|
+
? await calculateVolumeBidirectional({
|
|
15
|
+
operationData,
|
|
16
|
+
indices,
|
|
17
|
+
mode,
|
|
18
|
+
})
|
|
19
|
+
: await calculateStackBidirectional({
|
|
20
|
+
segImageIds,
|
|
21
|
+
indices,
|
|
22
|
+
mode,
|
|
23
|
+
});
|
|
24
|
+
triggerWorkerProgress(WorkerTypes.COMPUTE_LARGEST_BIDIRECTIONAL, 100);
|
|
25
|
+
return bidirectionalData;
|
|
26
|
+
}
|
|
27
|
+
async function calculateVolumeBidirectional({ operationData, indices, mode }) {
|
|
28
|
+
const strategyData = prepareVolumeStrategyDataForWorker(operationData);
|
|
29
|
+
const { segmentationVoxelManager, segmentationImageData } = strategyData;
|
|
30
|
+
const segmentationScalarData = segmentationVoxelManager.getCompleteScalarDataArray();
|
|
31
|
+
const segmentationInfo = {
|
|
32
|
+
scalarData: segmentationScalarData,
|
|
33
|
+
dimensions: segmentationImageData.getDimensions(),
|
|
34
|
+
spacing: segmentationImageData.getSpacing(),
|
|
35
|
+
origin: segmentationImageData.getOrigin(),
|
|
36
|
+
direction: segmentationImageData.getDirection(),
|
|
37
|
+
};
|
|
38
|
+
const bidirectionalData = await getWebWorkerManager().executeTask('compute', 'getSegmentLargestBidirectionalInternal', {
|
|
39
|
+
segmentationInfo,
|
|
40
|
+
indices,
|
|
41
|
+
mode,
|
|
42
|
+
});
|
|
43
|
+
return bidirectionalData;
|
|
44
|
+
}
|
|
45
|
+
async function calculateStackBidirectional({ segImageIds, indices, mode }) {
|
|
46
|
+
const { segmentationInfo } = prepareStackDataForWorker(segImageIds);
|
|
47
|
+
const bidirectionalData = await getWebWorkerManager().executeTask('compute', 'getSegmentLargestBidirectionalInternal', {
|
|
48
|
+
segmentationInfo,
|
|
49
|
+
indices,
|
|
50
|
+
mode,
|
|
51
|
+
isStack: true,
|
|
52
|
+
});
|
|
53
|
+
return bidirectionalData;
|
|
54
|
+
}
|