@cornerstonejs/tools 3.1.1 → 3.2.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/esm/enums/Events.d.ts +1 -0
- package/dist/esm/enums/Events.js +1 -0
- package/dist/esm/tools/CrosshairsTool.d.ts +1 -0
- package/dist/esm/tools/CrosshairsTool.js +18 -3
- package/dist/esm/utilities/getCalibratedUnits.d.ts +1 -0
- package/dist/esm/utilities/getCalibratedUnits.js +13 -3
- package/dist/esm/utilities/math/basic/BasicStatsCalculator.d.ts +25 -16
- package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +132 -107
- package/dist/esm/utilities/math/basic/Calculator.d.ts +8 -5
- package/dist/esm/utilities/math/basic/Calculator.js +9 -2
- package/dist/esm/utilities/math/basic/index.d.ts +3 -3
- package/dist/esm/utilities/math/basic/index.js +3 -3
- package/dist/esm/utilities/segmentation/SegmentStatsCalculator.d.ts +26 -0
- package/dist/esm/utilities/segmentation/SegmentStatsCalculator.js +44 -0
- package/dist/esm/utilities/segmentation/VolumetricCalculator.d.ts +31 -5
- package/dist/esm/utilities/segmentation/VolumetricCalculator.js +90 -42
- package/dist/esm/utilities/segmentation/getStatistics.d.ts +6 -2
- package/dist/esm/utilities/segmentation/getStatistics.js +67 -10
- package/dist/esm/utilities/segmentation/index.d.ts +2 -1
- package/dist/esm/utilities/segmentation/index.js +2 -1
- package/dist/esm/workers/computeWorker.js +14 -7
- package/package.json +3 -3
|
@@ -3,6 +3,7 @@ declare enum Events {
|
|
|
3
3
|
TOOLGROUP_VIEWPORT_ADDED = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_ADDED",
|
|
4
4
|
TOOLGROUP_VIEWPORT_REMOVED = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_REMOVED",
|
|
5
5
|
TOOL_MODE_CHANGED = "CORNERSTONE_TOOLS_TOOL_MODE_CHANGED",
|
|
6
|
+
CROSSHAIR_TOOL_CENTER_CHANGED = "CORNERSTONE_TOOLS_CROSSHAIR_TOOL_CENTER_CHANGED",
|
|
6
7
|
ANNOTATION_ADDED = "CORNERSTONE_TOOLS_ANNOTATION_ADDED",
|
|
7
8
|
ANNOTATION_COMPLETED = "CORNERSTONE_TOOLS_ANNOTATION_COMPLETED",
|
|
8
9
|
ANNOTATION_MODIFIED = "CORNERSTONE_TOOLS_ANNOTATION_MODIFIED",
|
package/dist/esm/enums/Events.js
CHANGED
|
@@ -4,6 +4,7 @@ var Events;
|
|
|
4
4
|
Events["TOOLGROUP_VIEWPORT_ADDED"] = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_ADDED";
|
|
5
5
|
Events["TOOLGROUP_VIEWPORT_REMOVED"] = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_REMOVED";
|
|
6
6
|
Events["TOOL_MODE_CHANGED"] = "CORNERSTONE_TOOLS_TOOL_MODE_CHANGED";
|
|
7
|
+
Events["CROSSHAIR_TOOL_CENTER_CHANGED"] = "CORNERSTONE_TOOLS_CROSSHAIR_TOOL_CENTER_CHANGED";
|
|
7
8
|
Events["ANNOTATION_ADDED"] = "CORNERSTONE_TOOLS_ANNOTATION_ADDED";
|
|
8
9
|
Events["ANNOTATION_COMPLETED"] = "CORNERSTONE_TOOLS_ANNOTATION_COMPLETED";
|
|
9
10
|
Events["ANNOTATION_MODIFIED"] = "CORNERSTONE_TOOLS_ANNOTATION_MODIFIED";
|
|
@@ -33,6 +33,7 @@ declare class CrosshairsTool extends AnnotationTool {
|
|
|
33
33
|
resetCrosshairs: () => void;
|
|
34
34
|
computeToolCenter: () => void;
|
|
35
35
|
_computeToolCenter: (viewportsInfo: any) => void;
|
|
36
|
+
setToolCenter(toolCenter: Types.Point3, suppressEvents?: boolean): void;
|
|
36
37
|
addNewAnnotation: (evt: EventTypes.InteractionEventType) => CrosshairsAnnotation;
|
|
37
38
|
cancel: () => void;
|
|
38
39
|
getHandleNearImagePoint(element: HTMLDivElement, annotation: Annotation, canvasCoords: Types.Point2, proximity: number): ToolHandle | undefined;
|
|
@@ -2,7 +2,7 @@ import { vec2, vec3 } from 'gl-matrix';
|
|
|
2
2
|
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
|
|
3
3
|
import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
|
|
4
4
|
import { AnnotationTool } from './base';
|
|
5
|
-
import { getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, } from '@cornerstonejs/core';
|
|
5
|
+
import { getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, triggerEvent, eventTarget, } from '@cornerstonejs/core';
|
|
6
6
|
import { getToolGroup, getToolGroupForViewport, } from '../store/ToolGroupManager';
|
|
7
7
|
import { addAnnotation, getAnnotations, removeAnnotation, } from '../stateManagement/annotation/annotationState';
|
|
8
8
|
import { drawCircle as drawCircleSvg, drawHandles as drawHandlesSvg, drawLine as drawLineSvg, } from '../drawingSvg';
|
|
@@ -162,8 +162,8 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
162
162
|
const firstPlane = csUtils.planar.planeEquation(normal1, point1);
|
|
163
163
|
const secondPlane = csUtils.planar.planeEquation(normal2, point2);
|
|
164
164
|
const thirdPlane = csUtils.planar.planeEquation(normal3, point3);
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const toolCenter = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane);
|
|
166
|
+
this.setToolCenter(toolCenter);
|
|
167
167
|
};
|
|
168
168
|
this.addNewAnnotation = (evt) => {
|
|
169
169
|
const eventDetail = evt.detail;
|
|
@@ -258,6 +258,10 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
258
258
|
this.toolCenter[0] += deltaCameraPosition[0];
|
|
259
259
|
this.toolCenter[1] += deltaCameraPosition[1];
|
|
260
260
|
this.toolCenter[2] += deltaCameraPosition[2];
|
|
261
|
+
triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
|
|
262
|
+
toolGroupId: this.toolGroupId,
|
|
263
|
+
toolCenter: this.toolCenter,
|
|
264
|
+
});
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
267
|
if (this.configuration.autoPan?.enabled) {
|
|
@@ -1171,6 +1175,17 @@ class CrosshairsTool extends AnnotationTool {
|
|
|
1171
1175
|
}
|
|
1172
1176
|
});
|
|
1173
1177
|
}
|
|
1178
|
+
setToolCenter(toolCenter, suppressEvents = false) {
|
|
1179
|
+
this.toolCenter = toolCenter;
|
|
1180
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
1181
|
+
triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
|
|
1182
|
+
if (!suppressEvents) {
|
|
1183
|
+
triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
|
|
1184
|
+
toolGroupId: this.toolGroupId,
|
|
1185
|
+
toolCenter: this.toolCenter,
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1174
1189
|
getHandleNearImagePoint(element, annotation, canvasCoords, proximity) {
|
|
1175
1190
|
const enabledElement = getEnabledElement(element);
|
|
1176
1191
|
const { viewport } = enabledElement;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Enums, utilities } from '@cornerstonejs/core';
|
|
2
2
|
const { CalibrationTypes } = Enums;
|
|
3
3
|
const PIXEL_UNITS = 'px';
|
|
4
|
+
const VOXEL_UNITS = 'voxels';
|
|
4
5
|
const SUPPORTED_REGION_DATA_TYPES = [
|
|
5
6
|
1,
|
|
6
7
|
2,
|
|
@@ -33,15 +34,21 @@ const SQUARE = '\xb2';
|
|
|
33
34
|
const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
34
35
|
const { calibration, hasPixelSpacing } = image;
|
|
35
36
|
let unit = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
|
|
37
|
+
const volumeUnit = hasPixelSpacing ? 'mm\xb3' : VOXEL_UNITS;
|
|
36
38
|
let areaUnit = unit + SQUARE;
|
|
37
39
|
let scale = 1;
|
|
38
40
|
let calibrationType = '';
|
|
39
41
|
if (!calibration ||
|
|
40
42
|
(!calibration.type && !calibration.sequenceOfUltrasoundRegions)) {
|
|
41
|
-
return { unit, areaUnit, scale };
|
|
43
|
+
return { unit, areaUnit, scale, volumeUnit };
|
|
42
44
|
}
|
|
43
45
|
if (calibration.type === CalibrationTypes.UNCALIBRATED) {
|
|
44
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
unit: PIXEL_UNITS,
|
|
48
|
+
areaUnit: PIXEL_UNITS + SQUARE,
|
|
49
|
+
scale,
|
|
50
|
+
volumeUnit: VOXEL_UNITS,
|
|
51
|
+
};
|
|
45
52
|
}
|
|
46
53
|
if (calibration.sequenceOfUltrasoundRegions) {
|
|
47
54
|
let imageIndex1, imageIndex2;
|
|
@@ -62,7 +69,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
62
69
|
imageIndex2[1] >= region.regionLocationMinY0 &&
|
|
63
70
|
imageIndex2[1] <= region.regionLocationMaxY1);
|
|
64
71
|
if (!regions?.length) {
|
|
65
|
-
return { unit, areaUnit, scale };
|
|
72
|
+
return { unit, areaUnit, scale, volumeUnit };
|
|
66
73
|
}
|
|
67
74
|
regions = regions.filter((region) => SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
|
|
68
75
|
SUPPORTED_LENGTH_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`));
|
|
@@ -71,6 +78,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
71
78
|
unit: PIXEL_UNITS,
|
|
72
79
|
areaUnit: PIXEL_UNITS + SQUARE,
|
|
73
80
|
scale,
|
|
81
|
+
volumeUnit: VOXEL_UNITS,
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
const region = regions[0];
|
|
@@ -88,6 +96,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
88
96
|
unit: PIXEL_UNITS,
|
|
89
97
|
areaUnit: PIXEL_UNITS + SQUARE,
|
|
90
98
|
scale,
|
|
99
|
+
volumeUnit: VOXEL_UNITS,
|
|
91
100
|
};
|
|
92
101
|
}
|
|
93
102
|
}
|
|
@@ -107,6 +116,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
|
|
|
107
116
|
unit: unit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
108
117
|
areaUnit: areaUnit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
109
118
|
scale,
|
|
119
|
+
volumeUnit: volumeUnit + (calibrationType ? ` ${calibrationType}` : ''),
|
|
110
120
|
};
|
|
111
121
|
};
|
|
112
122
|
const getCalibratedProbeUnitsAndValue = (image, handles) => {
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import type { NamedStatistics } from '../../../types';
|
|
2
|
-
import Calculator from './Calculator';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
private static
|
|
6
|
-
private static sum;
|
|
7
|
-
private static count;
|
|
8
|
-
private static maxIJK;
|
|
9
|
-
private static maxLPS;
|
|
10
|
-
private static minIJK;
|
|
11
|
-
private static minLPS;
|
|
12
|
-
private static runMean;
|
|
13
|
-
private static m2;
|
|
14
|
-
private static pointsInShape;
|
|
2
|
+
import { Calculator, InstanceCalculator } from './Calculator';
|
|
3
|
+
import type { Types } from '@cornerstonejs/core';
|
|
4
|
+
export declare class BasicStatsCalculator extends Calculator {
|
|
5
|
+
private static state;
|
|
15
6
|
static statsInit(options: {
|
|
16
7
|
storePointData: boolean;
|
|
17
8
|
}): void;
|
|
18
9
|
static statsCallback: ({ value: newValue, pointLPS, pointIJK, }: {
|
|
19
|
-
value:
|
|
20
|
-
pointLPS?:
|
|
21
|
-
pointIJK?:
|
|
10
|
+
value: number | Types.RGB;
|
|
11
|
+
pointLPS?: Types.Point3 | null;
|
|
12
|
+
pointIJK?: Types.Point3 | null;
|
|
22
13
|
}) => void;
|
|
23
14
|
static getStatistics: (options?: {
|
|
24
15
|
unit: string;
|
|
25
16
|
}) => NamedStatistics;
|
|
26
17
|
}
|
|
18
|
+
export declare class InstanceBasicStatsCalculator extends InstanceCalculator {
|
|
19
|
+
private state;
|
|
20
|
+
constructor(options: {
|
|
21
|
+
storePointData: boolean;
|
|
22
|
+
});
|
|
23
|
+
statsInit(options: {
|
|
24
|
+
storePointData: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
statsCallback(data: {
|
|
27
|
+
value: number | Types.RGB;
|
|
28
|
+
pointLPS?: Types.Point3 | null;
|
|
29
|
+
pointIJK?: Types.Point3 | null;
|
|
30
|
+
}): void;
|
|
31
|
+
getStatistics(options?: {
|
|
32
|
+
unit: string;
|
|
33
|
+
spacing?: number[] | number;
|
|
34
|
+
}): NamedStatistics;
|
|
35
|
+
}
|
|
@@ -1,119 +1,144 @@
|
|
|
1
1
|
import { utilities } from '@cornerstonejs/core';
|
|
2
|
-
import Calculator from './Calculator';
|
|
2
|
+
import { Calculator, InstanceCalculator } from './Calculator';
|
|
3
3
|
const { PointsManager } = utilities;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
function createBasicStatsState(storePointData) {
|
|
5
|
+
return {
|
|
6
|
+
max: [-Infinity],
|
|
7
|
+
min: [Infinity],
|
|
8
|
+
sum: [0],
|
|
9
|
+
count: 0,
|
|
10
|
+
maxIJK: null,
|
|
11
|
+
maxLPS: null,
|
|
12
|
+
minIJK: null,
|
|
13
|
+
minLPS: null,
|
|
14
|
+
runMean: [0],
|
|
15
|
+
m2: [0],
|
|
16
|
+
pointsInShape: storePointData ? PointsManager.create3(1024) : null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
|
|
20
|
+
if (Array.isArray(newValue) &&
|
|
21
|
+
newValue.length > 1 &&
|
|
22
|
+
state.max.length === 1) {
|
|
23
|
+
state.max.push(state.max[0], state.max[0]);
|
|
24
|
+
state.min.push(state.min[0], state.min[0]);
|
|
25
|
+
state.sum.push(state.sum[0], state.sum[0]);
|
|
26
|
+
state.runMean.push(0, 0);
|
|
27
|
+
state.m2.push(state.m2[0], state.m2[0]);
|
|
28
|
+
}
|
|
29
|
+
if (state?.pointsInShape && pointLPS) {
|
|
30
|
+
state.pointsInShape.push(pointLPS);
|
|
31
|
+
}
|
|
32
|
+
const newArray = Array.isArray(newValue) ? newValue : [newValue];
|
|
33
|
+
state.count += 1;
|
|
34
|
+
state.max.forEach((it, idx) => {
|
|
35
|
+
const value = newArray[idx];
|
|
36
|
+
const delta = value - state.runMean[idx];
|
|
37
|
+
state.sum[idx] += value;
|
|
38
|
+
state.runMean[idx] += delta / state.count;
|
|
39
|
+
const delta2 = value - state.runMean[idx];
|
|
40
|
+
state.m2[idx] += delta * delta2;
|
|
41
|
+
state.min[idx] = Math.min(state.min[idx], value);
|
|
42
|
+
if (value < state.min[idx]) {
|
|
43
|
+
state.min[idx] = value;
|
|
44
|
+
if (idx === 0) {
|
|
45
|
+
state.minIJK = pointIJK;
|
|
46
|
+
state.minLPS = pointLPS;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (value > state.max[idx]) {
|
|
50
|
+
state.max[idx] = value;
|
|
51
|
+
if (idx === 0) {
|
|
52
|
+
state.maxIJK = pointIJK;
|
|
53
|
+
state.maxLPS = pointLPS;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function basicGetStatistics(state, unit) {
|
|
59
|
+
const mean = state.sum.map((sum) => sum / state.count);
|
|
60
|
+
const stdDev = state.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / state.count));
|
|
61
|
+
const named = {
|
|
62
|
+
max: {
|
|
63
|
+
name: 'max',
|
|
64
|
+
label: 'Max Pixel',
|
|
65
|
+
value: state.max.length === 1 ? state.max[0] : state.max,
|
|
66
|
+
unit,
|
|
67
|
+
pointIJK: state.maxIJK,
|
|
68
|
+
pointLPS: state.maxLPS,
|
|
69
|
+
},
|
|
70
|
+
min: {
|
|
71
|
+
name: 'min',
|
|
72
|
+
label: 'Min Pixel',
|
|
73
|
+
value: state.min.length === 1 ? state.min[0] : state.min,
|
|
74
|
+
unit,
|
|
75
|
+
pointIJK: state.minIJK,
|
|
76
|
+
pointLPS: state.minLPS,
|
|
77
|
+
},
|
|
78
|
+
mean: {
|
|
79
|
+
name: 'mean',
|
|
80
|
+
label: 'Mean Pixel',
|
|
81
|
+
value: mean.length === 1 ? mean[0] : mean,
|
|
82
|
+
unit,
|
|
83
|
+
},
|
|
84
|
+
stdDev: {
|
|
85
|
+
name: 'stdDev',
|
|
86
|
+
label: 'Standard Deviation',
|
|
87
|
+
value: stdDev.length === 1 ? stdDev[0] : stdDev,
|
|
88
|
+
unit,
|
|
89
|
+
},
|
|
90
|
+
count: {
|
|
91
|
+
name: 'count',
|
|
92
|
+
label: 'Pixel Count',
|
|
93
|
+
value: state.count,
|
|
94
|
+
unit: null,
|
|
95
|
+
},
|
|
96
|
+
pointsInShape: state.pointsInShape,
|
|
97
|
+
array: [],
|
|
98
|
+
};
|
|
99
|
+
named.array.push(named.max, named.mean, named.stdDev, named.stdDev, named.count);
|
|
100
|
+
const store = state.pointsInShape !== null;
|
|
101
|
+
const freshState = createBasicStatsState(store);
|
|
102
|
+
state.max = freshState.max;
|
|
103
|
+
state.min = freshState.min;
|
|
104
|
+
state.sum = freshState.sum;
|
|
105
|
+
state.count = freshState.count;
|
|
106
|
+
state.maxIJK = freshState.maxIJK;
|
|
107
|
+
state.maxLPS = freshState.maxLPS;
|
|
108
|
+
state.minIJK = freshState.minIJK;
|
|
109
|
+
state.minLPS = freshState.minLPS;
|
|
110
|
+
state.runMean = freshState.runMean;
|
|
111
|
+
state.m2 = freshState.m2;
|
|
112
|
+
state.pointsInShape = freshState.pointsInShape;
|
|
113
|
+
return named;
|
|
114
|
+
}
|
|
115
|
+
export class BasicStatsCalculator extends Calculator {
|
|
116
|
+
static { this.state = createBasicStatsState(true); }
|
|
16
117
|
static statsInit(options) {
|
|
17
118
|
if (!options.storePointData) {
|
|
18
|
-
|
|
119
|
+
this.state.pointsInShape = null;
|
|
19
120
|
}
|
|
121
|
+
this.state = createBasicStatsState(options.storePointData);
|
|
20
122
|
}
|
|
21
123
|
static { this.statsCallback = ({ value: newValue, pointLPS = null, pointIJK = null, }) => {
|
|
22
|
-
|
|
23
|
-
newValue.length > 1 &&
|
|
24
|
-
this.max.length === 1) {
|
|
25
|
-
this.max.push(this.max[0], this.max[0]);
|
|
26
|
-
this.min.push(this.min[0], this.min[0]);
|
|
27
|
-
this.sum.push(this.sum[0], this.sum[0]);
|
|
28
|
-
this.runMean.push(0, 0);
|
|
29
|
-
this.m2.push(this.m2[0], this.m2[0]);
|
|
30
|
-
}
|
|
31
|
-
if (this.pointsInShape && pointLPS) {
|
|
32
|
-
this.pointsInShape?.push(pointLPS);
|
|
33
|
-
}
|
|
34
|
-
const newArray = Array.isArray(newValue) ? newValue : [newValue];
|
|
35
|
-
this.count += 1;
|
|
36
|
-
this.max.map((it, idx) => {
|
|
37
|
-
const value = newArray[idx];
|
|
38
|
-
const delta = value - this.runMean[idx];
|
|
39
|
-
this.sum[idx] += value;
|
|
40
|
-
this.runMean[idx] += delta / this.count;
|
|
41
|
-
const delta2 = value - this.runMean[idx];
|
|
42
|
-
this.m2[idx] += delta * delta2;
|
|
43
|
-
this.min[idx] = Math.min(this.min[idx], value);
|
|
44
|
-
if (value < this.min[idx]) {
|
|
45
|
-
this.min[idx] = value;
|
|
46
|
-
if (idx === 0) {
|
|
47
|
-
this.minIJK = pointIJK;
|
|
48
|
-
this.minLPS = pointLPS;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (value > this.max[idx]) {
|
|
52
|
-
this.max[idx] = value;
|
|
53
|
-
if (idx === 0) {
|
|
54
|
-
this.maxIJK = pointIJK;
|
|
55
|
-
this.maxLPS = pointLPS;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
124
|
+
basicStatsCallback(this.state, newValue, pointLPS, pointIJK);
|
|
59
125
|
}; }
|
|
60
126
|
static { this.getStatistics = (options) => {
|
|
61
|
-
|
|
62
|
-
const stdDev = this.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / this.count));
|
|
63
|
-
const unit = options?.unit || null;
|
|
64
|
-
const named = {
|
|
65
|
-
max: {
|
|
66
|
-
name: 'max',
|
|
67
|
-
label: 'Max Pixel',
|
|
68
|
-
value: singleArrayAsNumber(this.max),
|
|
69
|
-
unit,
|
|
70
|
-
pointIJK: this.maxIJK,
|
|
71
|
-
pointLPS: this.maxLPS,
|
|
72
|
-
},
|
|
73
|
-
min: {
|
|
74
|
-
name: 'min',
|
|
75
|
-
label: 'Min Pixel',
|
|
76
|
-
value: singleArrayAsNumber(this.min),
|
|
77
|
-
unit,
|
|
78
|
-
pointIJK: this.minIJK,
|
|
79
|
-
pointLPS: this.minLPS,
|
|
80
|
-
},
|
|
81
|
-
mean: {
|
|
82
|
-
name: 'mean',
|
|
83
|
-
label: 'Mean Pixel',
|
|
84
|
-
value: singleArrayAsNumber(mean),
|
|
85
|
-
unit,
|
|
86
|
-
},
|
|
87
|
-
stdDev: {
|
|
88
|
-
name: 'stdDev',
|
|
89
|
-
label: 'Standard Deviation',
|
|
90
|
-
value: singleArrayAsNumber(stdDev),
|
|
91
|
-
unit,
|
|
92
|
-
},
|
|
93
|
-
count: {
|
|
94
|
-
name: 'count',
|
|
95
|
-
label: 'Pixel Count',
|
|
96
|
-
value: this.count,
|
|
97
|
-
unit: null,
|
|
98
|
-
},
|
|
99
|
-
pointsInShape: this.pointsInShape,
|
|
100
|
-
array: [],
|
|
101
|
-
};
|
|
102
|
-
named.array.push(named.max, named.mean, named.stdDev, named.stdDev, named.count);
|
|
103
|
-
this.max = [-Infinity];
|
|
104
|
-
this.min = [Infinity];
|
|
105
|
-
this.sum = [0];
|
|
106
|
-
this.m2 = [0];
|
|
107
|
-
this.runMean = [0];
|
|
108
|
-
this.count = 0;
|
|
109
|
-
this.maxIJK = null;
|
|
110
|
-
this.maxLPS = null;
|
|
111
|
-
this.minIJK = null;
|
|
112
|
-
this.minLPS = null;
|
|
113
|
-
this.pointsInShape = PointsManager.create3(1024);
|
|
114
|
-
return named;
|
|
127
|
+
return basicGetStatistics(this.state, options?.unit);
|
|
115
128
|
}; }
|
|
116
129
|
}
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
export class InstanceBasicStatsCalculator extends InstanceCalculator {
|
|
131
|
+
constructor(options) {
|
|
132
|
+
super(options);
|
|
133
|
+
this.state = createBasicStatsState(options.storePointData);
|
|
134
|
+
}
|
|
135
|
+
statsInit(options) {
|
|
136
|
+
this.state = createBasicStatsState(options.storePointData);
|
|
137
|
+
}
|
|
138
|
+
statsCallback(data) {
|
|
139
|
+
basicStatsCallback(this.state, data.value, data.pointLPS, data.pointIJK);
|
|
140
|
+
}
|
|
141
|
+
getStatistics(options) {
|
|
142
|
+
return basicGetStatistics(this.state, options?.unit);
|
|
143
|
+
}
|
|
119
144
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { NamedStatistics } from '../../../types';
|
|
2
|
-
declare abstract class Calculator {
|
|
3
|
-
static run: ({ value }: {
|
|
4
|
-
value: any;
|
|
5
|
-
}) => void;
|
|
2
|
+
export declare abstract class Calculator {
|
|
6
3
|
static getStatistics: () => NamedStatistics;
|
|
7
4
|
}
|
|
8
|
-
export
|
|
5
|
+
export declare class InstanceCalculator {
|
|
6
|
+
private storePointData;
|
|
7
|
+
constructor(options: {
|
|
8
|
+
storePointData: boolean;
|
|
9
|
+
});
|
|
10
|
+
getStatistics(): void;
|
|
11
|
+
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
class Calculator {
|
|
1
|
+
export class Calculator {
|
|
2
|
+
}
|
|
3
|
+
export class InstanceCalculator {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.storePointData = options.storePointData;
|
|
6
|
+
}
|
|
7
|
+
getStatistics() {
|
|
8
|
+
console.debug('InstanceCalculator getStatistics called');
|
|
9
|
+
}
|
|
2
10
|
}
|
|
3
|
-
export default Calculator;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import BasicStatsCalculator from './BasicStatsCalculator';
|
|
2
|
-
import Calculator from './Calculator';
|
|
3
|
-
export { BasicStatsCalculator, Calculator };
|
|
1
|
+
import { BasicStatsCalculator, InstanceBasicStatsCalculator } from './BasicStatsCalculator';
|
|
2
|
+
import { Calculator, InstanceCalculator } from './Calculator';
|
|
3
|
+
export { BasicStatsCalculator, InstanceBasicStatsCalculator, Calculator, InstanceCalculator, };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import BasicStatsCalculator from './BasicStatsCalculator';
|
|
2
|
-
import Calculator from './Calculator';
|
|
3
|
-
export { BasicStatsCalculator, Calculator };
|
|
1
|
+
import { BasicStatsCalculator, InstanceBasicStatsCalculator, } from './BasicStatsCalculator';
|
|
2
|
+
import { Calculator, InstanceCalculator } from './Calculator';
|
|
3
|
+
export { BasicStatsCalculator, InstanceBasicStatsCalculator, Calculator, InstanceCalculator, };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { NamedStatistics } from '../../types';
|
|
3
|
+
export default class SegmentStatsCalculator {
|
|
4
|
+
private static calculators;
|
|
5
|
+
private static indices;
|
|
6
|
+
private static mode;
|
|
7
|
+
static statsInit(options: {
|
|
8
|
+
storePointData: boolean;
|
|
9
|
+
indices: number[];
|
|
10
|
+
mode: 'collective' | 'individual';
|
|
11
|
+
}): void;
|
|
12
|
+
static statsCallback(data: {
|
|
13
|
+
value: number | Types.RGB;
|
|
14
|
+
pointLPS?: Types.Point3;
|
|
15
|
+
pointIJK?: Types.Point3;
|
|
16
|
+
segmentIndex?: number;
|
|
17
|
+
}): void;
|
|
18
|
+
static getStatistics(options?: {
|
|
19
|
+
spacing?: number[] | number;
|
|
20
|
+
unit?: string;
|
|
21
|
+
calibration?: unknown;
|
|
22
|
+
hasPixelSpacing?: boolean;
|
|
23
|
+
}): NamedStatistics | {
|
|
24
|
+
[segmentIndex: number]: NamedStatistics;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { InstanceVolumetricCalculator } from './VolumetricCalculator';
|
|
2
|
+
export default class SegmentStatsCalculator {
|
|
3
|
+
static { this.calculators = new Map(); }
|
|
4
|
+
static { this.indices = []; }
|
|
5
|
+
static { this.mode = 'collective'; }
|
|
6
|
+
static statsInit(options) {
|
|
7
|
+
const { storePointData, indices, mode } = options;
|
|
8
|
+
this.mode = mode;
|
|
9
|
+
this.indices = indices;
|
|
10
|
+
this.calculators.clear();
|
|
11
|
+
if (this.mode === 'individual') {
|
|
12
|
+
indices.forEach((index) => {
|
|
13
|
+
this.calculators.set(index, new InstanceVolumetricCalculator({ storePointData }));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this.calculators.set(indices, new InstanceVolumetricCalculator({ storePointData }));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
static statsCallback(data) {
|
|
21
|
+
const { segmentIndex, ...statsData } = data;
|
|
22
|
+
if (!segmentIndex) {
|
|
23
|
+
throw new Error('Segment index is required for stats calculation');
|
|
24
|
+
}
|
|
25
|
+
const calculator = this.mode === 'individual'
|
|
26
|
+
? this.calculators.get(segmentIndex)
|
|
27
|
+
: this.calculators.get(this.indices);
|
|
28
|
+
if (!calculator) {
|
|
29
|
+
throw new Error(`No calculator found for segment ${segmentIndex}`);
|
|
30
|
+
}
|
|
31
|
+
calculator.statsCallback(statsData);
|
|
32
|
+
}
|
|
33
|
+
static getStatistics(options) {
|
|
34
|
+
if (this.mode === 'individual') {
|
|
35
|
+
const result = {};
|
|
36
|
+
this.calculators.forEach((calculator, segmentIndex) => {
|
|
37
|
+
result[segmentIndex] = calculator.getStatistics(options);
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
const calculator = this.calculators.get(this.indices);
|
|
42
|
+
return calculator.getStatistics(options);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { NamedStatistics } from '../../types';
|
|
3
|
-
import { BasicStatsCalculator } from '../math/basic';
|
|
4
|
-
export
|
|
5
|
-
private static
|
|
3
|
+
import { BasicStatsCalculator, InstanceBasicStatsCalculator } from '../math/basic/BasicStatsCalculator';
|
|
4
|
+
export declare class VolumetricCalculator extends BasicStatsCalculator {
|
|
5
|
+
private static volumetricState;
|
|
6
|
+
static statsInit(options: {
|
|
7
|
+
storePointData: boolean;
|
|
8
|
+
}): void;
|
|
9
|
+
static statsCallback(data: {
|
|
10
|
+
value: number | Types.RGB;
|
|
11
|
+
pointLPS?: Types.Point3;
|
|
12
|
+
pointIJK?: Types.Point3;
|
|
13
|
+
}): void;
|
|
6
14
|
static getStatistics(options: {
|
|
7
|
-
spacing?: number;
|
|
15
|
+
spacing?: number[] | number;
|
|
8
16
|
unit?: string;
|
|
17
|
+
calibration?: unknown;
|
|
18
|
+
hasPixelSpacing?: boolean;
|
|
9
19
|
}): NamedStatistics;
|
|
10
|
-
|
|
20
|
+
}
|
|
21
|
+
export declare class InstanceVolumetricCalculator extends InstanceBasicStatsCalculator {
|
|
22
|
+
private volumetricState;
|
|
23
|
+
constructor(options: {
|
|
24
|
+
storePointData: boolean;
|
|
25
|
+
});
|
|
26
|
+
statsInit(options: {
|
|
27
|
+
storePointData: boolean;
|
|
28
|
+
}): void;
|
|
29
|
+
statsCallback(data: {
|
|
11
30
|
value: number | Types.RGB;
|
|
12
31
|
pointLPS?: Types.Point3;
|
|
13
32
|
pointIJK?: Types.Point3;
|
|
14
33
|
}): void;
|
|
34
|
+
getStatistics(options?: {
|
|
35
|
+
spacing?: number[] | number;
|
|
36
|
+
unit?: string;
|
|
37
|
+
calibration?: unknown;
|
|
38
|
+
hasPixelSpacing?: boolean;
|
|
39
|
+
}): NamedStatistics;
|
|
15
40
|
}
|
|
41
|
+
export default VolumetricCalculator;
|
|
@@ -1,48 +1,96 @@
|
|
|
1
|
-
import { BasicStatsCalculator } from '../math/basic';
|
|
1
|
+
import { BasicStatsCalculator, InstanceBasicStatsCalculator, } from '../math/basic/BasicStatsCalculator';
|
|
2
|
+
import { getCalibratedLengthUnitsAndScale } from '../getCalibratedUnits';
|
|
2
3
|
const TEST_MAX_LOCATIONS = 10;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
: stats.count.value * volumeScale,
|
|
16
|
-
unit: volumeUnit,
|
|
17
|
-
name: 'volume',
|
|
18
|
-
};
|
|
19
|
-
stats.maxIJKs = this.maxIJKs;
|
|
20
|
-
stats.array.push(stats.volume);
|
|
21
|
-
this.maxIJKs = [];
|
|
22
|
-
return stats;
|
|
4
|
+
function createVolumetricState() {
|
|
5
|
+
return {
|
|
6
|
+
maxIJKs: [],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function volumetricStatsCallback(state, data) {
|
|
10
|
+
const { value } = data;
|
|
11
|
+
const { maxIJKs } = state;
|
|
12
|
+
const length = maxIJKs.length;
|
|
13
|
+
if (typeof value !== 'number' ||
|
|
14
|
+
(length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
|
|
15
|
+
return;
|
|
23
16
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
if (!length || value >= maxIJKs[length - 1].value) {
|
|
34
|
-
maxIJKs.push(data);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
for (let i = 0; i < length; i++) {
|
|
38
|
-
if (value <= maxIJKs[i].value) {
|
|
39
|
-
maxIJKs.splice(i, 0, data);
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
17
|
+
if (!length || value >= maxIJKs[length - 1].value) {
|
|
18
|
+
maxIJKs.push(data);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
for (let i = 0; i < length; i++) {
|
|
22
|
+
if (value <= maxIJKs[i].value) {
|
|
23
|
+
maxIJKs.splice(i, 0, data);
|
|
24
|
+
break;
|
|
42
25
|
}
|
|
43
26
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
}
|
|
28
|
+
if (length >= TEST_MAX_LOCATIONS) {
|
|
29
|
+
maxIJKs.splice(0, 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function volumetricGetStatistics(state, stats, options) {
|
|
33
|
+
const { spacing, calibration } = options;
|
|
34
|
+
const { volumeUnit } = getCalibratedLengthUnitsAndScale({
|
|
35
|
+
calibration,
|
|
36
|
+
hasPixelSpacing: true,
|
|
37
|
+
}, []);
|
|
38
|
+
const volumeScale = spacing ? spacing[0] * spacing[1] * spacing[2] * 1000 : 1;
|
|
39
|
+
stats.volume = {
|
|
40
|
+
value: Array.isArray(stats.count.value)
|
|
41
|
+
? stats.count.value.map((v) => v * volumeScale)
|
|
42
|
+
: stats.count.value * volumeScale,
|
|
43
|
+
unit: volumeUnit,
|
|
44
|
+
name: 'volume',
|
|
45
|
+
};
|
|
46
|
+
stats.maxIJKs = state.maxIJKs.filter((entry) => entry.pointIJK !== undefined);
|
|
47
|
+
stats.array.push(stats.volume);
|
|
48
|
+
state.maxIJKs = [];
|
|
49
|
+
return stats;
|
|
50
|
+
}
|
|
51
|
+
export class VolumetricCalculator extends BasicStatsCalculator {
|
|
52
|
+
static { this.volumetricState = createVolumetricState(); }
|
|
53
|
+
static statsInit(options) {
|
|
54
|
+
super.statsInit(options);
|
|
55
|
+
this.volumetricState = createVolumetricState();
|
|
56
|
+
}
|
|
57
|
+
static statsCallback(data) {
|
|
58
|
+
super.statsCallback(data);
|
|
59
|
+
volumetricStatsCallback(this.volumetricState, data);
|
|
60
|
+
}
|
|
61
|
+
static getStatistics(options) {
|
|
62
|
+
const optionsWithUnit = {
|
|
63
|
+
...options,
|
|
64
|
+
unit: options?.unit || 'none',
|
|
65
|
+
calibration: options?.calibration,
|
|
66
|
+
hasPixelSpacing: options?.hasPixelSpacing,
|
|
67
|
+
};
|
|
68
|
+
const stats = super.getStatistics(optionsWithUnit);
|
|
69
|
+
return volumetricGetStatistics(this.volumetricState, stats, optionsWithUnit);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class InstanceVolumetricCalculator extends InstanceBasicStatsCalculator {
|
|
73
|
+
constructor(options) {
|
|
74
|
+
super(options);
|
|
75
|
+
this.volumetricState = createVolumetricState();
|
|
76
|
+
}
|
|
77
|
+
statsInit(options) {
|
|
78
|
+
super.statsInit(options);
|
|
79
|
+
this.volumetricState = createVolumetricState();
|
|
80
|
+
}
|
|
81
|
+
statsCallback(data) {
|
|
82
|
+
super.statsCallback(data);
|
|
83
|
+
volumetricStatsCallback(this.volumetricState, data);
|
|
84
|
+
}
|
|
85
|
+
getStatistics(options) {
|
|
86
|
+
const optionsWithUnit = {
|
|
87
|
+
...options,
|
|
88
|
+
unit: options?.unit || 'none',
|
|
89
|
+
calibration: options?.calibration,
|
|
90
|
+
hasPixelSpacing: options?.hasPixelSpacing,
|
|
91
|
+
};
|
|
92
|
+
const stats = super.getStatistics(optionsWithUnit);
|
|
93
|
+
return volumetricGetStatistics(this.volumetricState, stats, optionsWithUnit);
|
|
47
94
|
}
|
|
48
95
|
}
|
|
96
|
+
export default VolumetricCalculator;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { NamedStatistics } from '../../types';
|
|
2
|
+
declare function getStatistics({ segmentationId, segmentIndices, mode, }: {
|
|
2
3
|
segmentationId: string;
|
|
3
4
|
segmentIndices: number[] | number;
|
|
4
|
-
|
|
5
|
+
mode?: 'collective' | 'individual';
|
|
6
|
+
}): Promise<NamedStatistics | {
|
|
7
|
+
[segmentIndex: number]: NamedStatistics;
|
|
8
|
+
}>;
|
|
5
9
|
export default getStatistics;
|
|
@@ -16,7 +16,7 @@ const triggerWorkerProgress = (eventTarget, progress) => {
|
|
|
16
16
|
type: WorkerTypes.COMPUTE_STATISTICS,
|
|
17
17
|
});
|
|
18
18
|
};
|
|
19
|
-
async function getStatistics({ segmentationId, segmentIndices, }) {
|
|
19
|
+
async function getStatistics({ segmentationId, segmentIndices, mode = 'collective', }) {
|
|
20
20
|
registerComputeWorker();
|
|
21
21
|
triggerWorkerProgress(eventTarget, 0);
|
|
22
22
|
const segmentation = getSegmentation(segmentationId);
|
|
@@ -51,11 +51,21 @@ async function getStatistics({ segmentationId, segmentIndices, }) {
|
|
|
51
51
|
const { refImageId, modalityUnitOptions } = getImageReferenceInfo(segVolumeId, segImageIds);
|
|
52
52
|
const unit = getPixelValueUnitsImageId(refImageId, modalityUnitOptions);
|
|
53
53
|
const stats = reconstructableVolume
|
|
54
|
-
? await calculateVolumeStatistics(
|
|
55
|
-
|
|
54
|
+
? await calculateVolumeStatistics({
|
|
55
|
+
operationData,
|
|
56
|
+
indices,
|
|
57
|
+
unit,
|
|
58
|
+
mode,
|
|
59
|
+
})
|
|
60
|
+
: await calculateStackStatistics({
|
|
61
|
+
segImageIds,
|
|
62
|
+
indices,
|
|
63
|
+
unit,
|
|
64
|
+
mode,
|
|
65
|
+
});
|
|
56
66
|
return stats;
|
|
57
67
|
}
|
|
58
|
-
async function calculateVolumeStatistics(operationData, indices, unit) {
|
|
68
|
+
async function calculateVolumeStatistics({ operationData, indices, unit, mode, }) {
|
|
59
69
|
const strategyData = getStrategyData({
|
|
60
70
|
operationData,
|
|
61
71
|
strategy: {
|
|
@@ -87,8 +97,33 @@ async function calculateVolumeStatistics(operationData, indices, unit) {
|
|
|
87
97
|
segmentationInfo,
|
|
88
98
|
imageInfo,
|
|
89
99
|
indices,
|
|
100
|
+
mode,
|
|
90
101
|
});
|
|
91
102
|
triggerWorkerProgress(eventTarget, 100);
|
|
103
|
+
if (mode === 'collective') {
|
|
104
|
+
return processSegmentationStatistics({
|
|
105
|
+
stats,
|
|
106
|
+
unit,
|
|
107
|
+
spacing,
|
|
108
|
+
segmentationImageData,
|
|
109
|
+
imageVoxelManager,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const finalStats = {};
|
|
114
|
+
Object.entries(stats).forEach(([segmentIndex, stat]) => {
|
|
115
|
+
finalStats[segmentIndex] = processSegmentationStatistics({
|
|
116
|
+
stats: stat,
|
|
117
|
+
unit,
|
|
118
|
+
spacing,
|
|
119
|
+
segmentationImageData,
|
|
120
|
+
imageVoxelManager,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
return finalStats;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const processSegmentationStatistics = ({ stats, unit, spacing, segmentationImageData, imageVoxelManager, }) => {
|
|
92
127
|
stats.mean.unit = unit;
|
|
93
128
|
stats.max.unit = unit;
|
|
94
129
|
stats.min.unit = unit;
|
|
@@ -112,8 +147,8 @@ async function calculateVolumeStatistics(operationData, indices, unit) {
|
|
|
112
147
|
}
|
|
113
148
|
}
|
|
114
149
|
return stats;
|
|
115
|
-
}
|
|
116
|
-
async function calculateStackStatistics(segImageIds, indices, unit) {
|
|
150
|
+
};
|
|
151
|
+
async function calculateStackStatistics({ segImageIds, indices, unit, mode }) {
|
|
117
152
|
triggerWorkerProgress(eventTarget, 0);
|
|
118
153
|
const segmentationInfo = [];
|
|
119
154
|
const imageInfo = [];
|
|
@@ -142,12 +177,34 @@ async function calculateStackStatistics(segImageIds, indices, unit) {
|
|
|
142
177
|
segmentationInfo,
|
|
143
178
|
imageInfo,
|
|
144
179
|
indices,
|
|
180
|
+
mode,
|
|
145
181
|
});
|
|
146
182
|
triggerWorkerProgress(eventTarget, 100);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
183
|
+
const spacing = segmentationInfo[0].spacing;
|
|
184
|
+
const segmentationImageData = segmentationInfo[0];
|
|
185
|
+
const imageVoxelManager = imageInfo[0].voxelManager;
|
|
186
|
+
if (mode === 'collective') {
|
|
187
|
+
return processSegmentationStatistics({
|
|
188
|
+
stats,
|
|
189
|
+
unit,
|
|
190
|
+
spacing,
|
|
191
|
+
segmentationImageData,
|
|
192
|
+
imageVoxelManager,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const finalStats = {};
|
|
197
|
+
Object.entries(stats).forEach(([segmentIndex, stat]) => {
|
|
198
|
+
finalStats[segmentIndex] = processSegmentationStatistics({
|
|
199
|
+
stats: stat,
|
|
200
|
+
unit,
|
|
201
|
+
spacing,
|
|
202
|
+
segmentationImageData,
|
|
203
|
+
imageVoxelManager,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
return finalStats;
|
|
207
|
+
}
|
|
151
208
|
}
|
|
152
209
|
function getSphereStats(testMax, radiusIJK, segData, imageVoxels, spacing) {
|
|
153
210
|
const { pointIJK: centerIJK } = testMax;
|
|
@@ -7,6 +7,7 @@ import floodFill from './floodFill';
|
|
|
7
7
|
import { getBrushSizeForToolGroup, setBrushSizeForToolGroup } from './brushSizeForToolGroup';
|
|
8
8
|
import { getBrushThresholdForToolGroup, setBrushThresholdForToolGroup } from './brushThresholdForToolGroup';
|
|
9
9
|
import VolumetricCalculator from './VolumetricCalculator';
|
|
10
|
+
import SegmentStatsCalculator from './SegmentStatsCalculator';
|
|
10
11
|
import thresholdSegmentationByRange from './thresholdSegmentationByRange';
|
|
11
12
|
import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
|
|
12
13
|
import createBidirectionalToolData from './createBidirectionalToolData';
|
|
@@ -25,4 +26,4 @@ import getStatistics from './getStatistics';
|
|
|
25
26
|
import * as validateLabelmap from './validateLabelmap';
|
|
26
27
|
import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
|
|
27
28
|
import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
|
|
28
|
-
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
|
|
29
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
|
|
@@ -7,6 +7,7 @@ import floodFill from './floodFill';
|
|
|
7
7
|
import { getBrushSizeForToolGroup, setBrushSizeForToolGroup, } from './brushSizeForToolGroup';
|
|
8
8
|
import { getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, } from './brushThresholdForToolGroup';
|
|
9
9
|
import VolumetricCalculator from './VolumetricCalculator';
|
|
10
|
+
import SegmentStatsCalculator from './SegmentStatsCalculator';
|
|
10
11
|
import thresholdSegmentationByRange from './thresholdSegmentationByRange';
|
|
11
12
|
import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
|
|
12
13
|
import createBidirectionalToolData from './createBidirectionalToolData';
|
|
@@ -25,4 +26,4 @@ import getStatistics from './getStatistics';
|
|
|
25
26
|
import * as validateLabelmap from './validateLabelmap';
|
|
26
27
|
import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
|
|
27
28
|
import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
|
|
28
|
-
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
|
|
29
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { expose } from 'comlink';
|
|
2
2
|
import { utilities } from '@cornerstonejs/core';
|
|
3
|
-
import
|
|
3
|
+
import SegmentStatsCalculator from '../utilities/segmentation/SegmentStatsCalculator';
|
|
4
4
|
const { VoxelManager } = utilities;
|
|
5
5
|
const computeWorker = {
|
|
6
6
|
calculateSegmentsStatisticsVolume: (args) => {
|
|
7
|
-
const { segmentationInfo, imageInfo, indices } = args;
|
|
7
|
+
const { segmentationInfo, imageInfo, indices, mode } = args;
|
|
8
8
|
const { scalarData: segmentationScalarData, dimensions: segmentationDimensions, spacing: segmentationSpacing, } = segmentationInfo;
|
|
9
9
|
const { scalarData: imageScalarData, dimensions: imageDimensions } = imageInfo;
|
|
10
10
|
if (segmentationDimensions[0] !== imageDimensions[0] ||
|
|
@@ -20,26 +20,30 @@ const computeWorker = {
|
|
|
20
20
|
dimensions: imageDimensions,
|
|
21
21
|
scalarData: imageScalarData,
|
|
22
22
|
});
|
|
23
|
+
SegmentStatsCalculator.statsInit({ storePointData: false, indices, mode });
|
|
23
24
|
segVoxelManager.forEach(({ value, pointIJK, index }) => {
|
|
24
25
|
if (indices.indexOf(value) === -1) {
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
28
|
const imageValue = imageVoxelManager.getAtIndex(index);
|
|
28
|
-
|
|
29
|
+
SegmentStatsCalculator.statsCallback({
|
|
30
|
+
segmentIndex: value,
|
|
29
31
|
value: imageValue,
|
|
30
32
|
pointIJK,
|
|
31
33
|
});
|
|
32
34
|
}, {
|
|
33
35
|
boundsIJK: imageVoxelManager.getDefaultBounds(),
|
|
34
36
|
});
|
|
35
|
-
const stats =
|
|
37
|
+
const stats = SegmentStatsCalculator.getStatistics({
|
|
36
38
|
spacing: segmentationSpacing,
|
|
37
39
|
unit: 'mm',
|
|
40
|
+
mode,
|
|
38
41
|
});
|
|
39
42
|
return stats;
|
|
40
43
|
},
|
|
41
44
|
calculateSegmentsStatisticsStack: (args) => {
|
|
42
|
-
const { segmentationInfo, imageInfo, indices } = args;
|
|
45
|
+
const { segmentationInfo, imageInfo, indices, mode } = args;
|
|
46
|
+
SegmentStatsCalculator.statsInit({ storePointData: true, indices, mode });
|
|
43
47
|
for (let i = 0; i < segmentationInfo.length; i++) {
|
|
44
48
|
const segInfo = segmentationInfo[i];
|
|
45
49
|
const imgInfo = imageInfo[i];
|
|
@@ -61,16 +65,19 @@ const computeWorker = {
|
|
|
61
65
|
return;
|
|
62
66
|
}
|
|
63
67
|
const imageValue = imageVoxelManager.getAtIndex(index);
|
|
64
|
-
|
|
68
|
+
SegmentStatsCalculator.statsCallback({
|
|
69
|
+
segmentIndex: value,
|
|
65
70
|
value: imageValue,
|
|
71
|
+
pointIJK,
|
|
66
72
|
});
|
|
67
73
|
}, {
|
|
68
74
|
boundsIJK: imageVoxelManager.getDefaultBounds(),
|
|
69
75
|
});
|
|
70
76
|
}
|
|
71
77
|
const spacing = segmentationInfo[0].spacing;
|
|
72
|
-
const stats =
|
|
78
|
+
const stats = SegmentStatsCalculator.getStatistics({
|
|
73
79
|
spacing,
|
|
80
|
+
mode,
|
|
74
81
|
});
|
|
75
82
|
return stats;
|
|
76
83
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"canvas": "^2.11.2"
|
|
104
104
|
},
|
|
105
105
|
"peerDependencies": {
|
|
106
|
-
"@cornerstonejs/core": "^3.
|
|
106
|
+
"@cornerstonejs/core": "^3.2.0",
|
|
107
107
|
"@kitware/vtk.js": "32.9.0",
|
|
108
108
|
"@types/d3-array": "^3.0.4",
|
|
109
109
|
"@types/d3-interpolate": "^3.0.1",
|
|
@@ -122,5 +122,5 @@
|
|
|
122
122
|
"type": "individual",
|
|
123
123
|
"url": "https://ohif.org/donate"
|
|
124
124
|
},
|
|
125
|
-
"gitHead": "
|
|
125
|
+
"gitHead": "ee69f78b449fce9b37702ad5e158d6764d00aff9"
|
|
126
126
|
}
|