@cornerstonejs/tools 2.18.3 → 2.18.5
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/geometricSurfaceUtils.d.ts +8 -0
- package/dist/esm/geometricSurfaceUtils.js +103 -0
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +7 -1
- package/dist/esm/tools/segmentation/strategies/compositions/index.d.ts +7 -1
- package/dist/esm/tools/segmentation/strategies/compositions/labelmapInterpolation.d.ts +8 -2
- package/dist/esm/tools/segmentation/strategies/compositions/labelmapInterpolation.js +14 -5
- package/dist/esm/utilities/math/vec2/liangBarksyClip.d.ts +1 -1
- package/dist/esm/workers/polySegConverters.js +17 -9
- package/package.json +3 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface RotationMatrixInformation {
|
|
2
|
+
isStandard: boolean;
|
|
3
|
+
rotationMatrix: number[];
|
|
4
|
+
}
|
|
5
|
+
export declare function inverse3x3Matrix(matrix: number[]): number[];
|
|
6
|
+
export declare function checkStandardBasis(directions: number[]): RotationMatrixInformation;
|
|
7
|
+
export declare function rotatePoints(rotationMatrix: number[], origin: number[], points: number[]): number[];
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
function validate3x3Matrix(matrix) {
|
|
2
|
+
if (!Array.isArray(matrix) || matrix.length !== 9) {
|
|
3
|
+
throw new Error('Matrix must be an array of 9 numbers');
|
|
4
|
+
}
|
|
5
|
+
if (!matrix.every((n) => typeof n === 'number' && !isNaN(n))) {
|
|
6
|
+
throw new Error('Matrix must contain only valid numbers');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function inverse3x3Matrix(matrix) {
|
|
10
|
+
validate3x3Matrix(matrix);
|
|
11
|
+
const mat = [
|
|
12
|
+
[matrix[0], matrix[1], matrix[2]],
|
|
13
|
+
[matrix[3], matrix[4], matrix[5]],
|
|
14
|
+
[matrix[6], matrix[7], matrix[8]],
|
|
15
|
+
];
|
|
16
|
+
const determinant = mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) -
|
|
17
|
+
mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) +
|
|
18
|
+
mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]);
|
|
19
|
+
if (Math.abs(determinant) < 1e-10) {
|
|
20
|
+
throw new Error('Matrix is not invertible (determinant is zero)');
|
|
21
|
+
}
|
|
22
|
+
const adjugate = [
|
|
23
|
+
[
|
|
24
|
+
mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1],
|
|
25
|
+
-(mat[0][1] * mat[2][2] - mat[0][2] * mat[2][1]),
|
|
26
|
+
mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1],
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
-(mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]),
|
|
30
|
+
mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0],
|
|
31
|
+
-(mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]),
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0],
|
|
35
|
+
-(mat[0][0] * mat[2][1] - mat[0][1] * mat[2][0]),
|
|
36
|
+
mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0],
|
|
37
|
+
],
|
|
38
|
+
];
|
|
39
|
+
const inverse = [];
|
|
40
|
+
for (let i = 0; i < 3; i++) {
|
|
41
|
+
for (let j = 0; j < 3; j++) {
|
|
42
|
+
inverse.push(adjugate[i][j] / determinant);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return inverse;
|
|
46
|
+
}
|
|
47
|
+
function normalizeVector(v) {
|
|
48
|
+
const magnitude = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
49
|
+
return v.map((component) => component / magnitude);
|
|
50
|
+
}
|
|
51
|
+
export function checkStandardBasis(directions) {
|
|
52
|
+
validate3x3Matrix(directions);
|
|
53
|
+
const xVector = directions.slice(0, 3);
|
|
54
|
+
const yVector = directions.slice(3, 6);
|
|
55
|
+
const zVector = directions.slice(6, 9);
|
|
56
|
+
const normalizedX = normalizeVector(xVector);
|
|
57
|
+
const normalizedY = normalizeVector(yVector);
|
|
58
|
+
const normalizedZ = normalizeVector(zVector);
|
|
59
|
+
const standardBasis = {
|
|
60
|
+
x: [1, 0, 0],
|
|
61
|
+
y: [0, 1, 0],
|
|
62
|
+
z: [0, 0, 1],
|
|
63
|
+
};
|
|
64
|
+
const epsilon = 1e-10;
|
|
65
|
+
const isStandard = normalizedX.every((val, i) => Math.abs(val - standardBasis.x[i]) < epsilon) &&
|
|
66
|
+
normalizedY.every((val, i) => Math.abs(val - standardBasis.y[i]) < epsilon) &&
|
|
67
|
+
normalizedZ.every((val, i) => Math.abs(val - standardBasis.z[i]) < epsilon);
|
|
68
|
+
const rotationMatrix = isStandard
|
|
69
|
+
? [...standardBasis.x, ...standardBasis.y, ...standardBasis.z]
|
|
70
|
+
: inverse3x3Matrix([...normalizedX, ...normalizedY, ...normalizedZ]);
|
|
71
|
+
return {
|
|
72
|
+
isStandard,
|
|
73
|
+
rotationMatrix,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function rotatePoint(point, origin, rotationMatrix) {
|
|
77
|
+
const x = point[0] - origin[0];
|
|
78
|
+
const y = point[1] - origin[1];
|
|
79
|
+
const z = point[2] - origin[2];
|
|
80
|
+
return [
|
|
81
|
+
rotationMatrix[0] * x +
|
|
82
|
+
rotationMatrix[1] * y +
|
|
83
|
+
rotationMatrix[2] * z +
|
|
84
|
+
origin[0],
|
|
85
|
+
rotationMatrix[3] * x +
|
|
86
|
+
rotationMatrix[4] * y +
|
|
87
|
+
rotationMatrix[5] * z +
|
|
88
|
+
origin[1],
|
|
89
|
+
rotationMatrix[6] * x +
|
|
90
|
+
rotationMatrix[7] * y +
|
|
91
|
+
rotationMatrix[8] * z +
|
|
92
|
+
origin[2],
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
export function rotatePoints(rotationMatrix, origin, points) {
|
|
96
|
+
const rotatedPoints = [];
|
|
97
|
+
for (let i = 0; i < points.length; i += 3) {
|
|
98
|
+
const point = points.slice(i, i + 3);
|
|
99
|
+
const rotated = rotatePoint(point, origin, rotationMatrix);
|
|
100
|
+
rotatedPoints.push(...rotated);
|
|
101
|
+
}
|
|
102
|
+
return rotatedPoints;
|
|
103
|
+
}
|
|
@@ -67,7 +67,13 @@ export default class BrushStrategy {
|
|
|
67
67
|
}) => import("../../../types").NamedStatistics;
|
|
68
68
|
};
|
|
69
69
|
labelmapInterpolation: {
|
|
70
|
-
interpolate: (operationData: InitializedOperationData, configuration:
|
|
70
|
+
interpolate: (operationData: InitializedOperationData, configuration: {
|
|
71
|
+
label?: number;
|
|
72
|
+
axis?: number;
|
|
73
|
+
noHeuristicAlignment?: boolean;
|
|
74
|
+
noUseDistanceTransform?: boolean;
|
|
75
|
+
useCustomSlicePositions?: boolean;
|
|
76
|
+
}) => Promise<InitializedOperationData>;
|
|
71
77
|
};
|
|
72
78
|
};
|
|
73
79
|
protected static childFunctions: {
|
|
@@ -38,7 +38,13 @@ declare const _default: {
|
|
|
38
38
|
}) => import("../../../../types").NamedStatistics;
|
|
39
39
|
};
|
|
40
40
|
labelmapInterpolation: {
|
|
41
|
-
interpolate: (operationData: import("../BrushStrategy").InitializedOperationData, configuration:
|
|
41
|
+
interpolate: (operationData: import("../BrushStrategy").InitializedOperationData, configuration: {
|
|
42
|
+
label?: number;
|
|
43
|
+
axis?: number;
|
|
44
|
+
noHeuristicAlignment?: boolean;
|
|
45
|
+
noUseDistanceTransform?: boolean;
|
|
46
|
+
useCustomSlicePositions?: boolean;
|
|
47
|
+
}) => Promise<import("../BrushStrategy").InitializedOperationData>;
|
|
42
48
|
};
|
|
43
49
|
};
|
|
44
50
|
export default _default;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import type { MorphologicalContourInterpolationOptions } from '@itk-wasm/morphological-contour-interpolation';
|
|
2
1
|
import type { InitializedOperationData } from '../BrushStrategy';
|
|
2
|
+
type MorphologicalContourInterpolationOptions = {
|
|
3
|
+
label?: number;
|
|
4
|
+
axis?: number;
|
|
5
|
+
noHeuristicAlignment?: boolean;
|
|
6
|
+
noUseDistanceTransform?: boolean;
|
|
7
|
+
useCustomSlicePositions?: boolean;
|
|
8
|
+
};
|
|
3
9
|
declare const _default: {
|
|
4
|
-
interpolate: (operationData: InitializedOperationData, configuration: MorphologicalContourInterpolationOptions) => InitializedOperationData
|
|
10
|
+
interpolate: (operationData: InitializedOperationData, configuration: MorphologicalContourInterpolationOptions) => Promise<InitializedOperationData>;
|
|
5
11
|
};
|
|
6
12
|
export default _default;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { utilities } from '@cornerstonejs/core';
|
|
1
|
+
import { utilities, peerImport } from '@cornerstonejs/core';
|
|
3
2
|
import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
|
|
4
3
|
import getItkImage from '../utils/getItkImage';
|
|
5
4
|
import { triggerSegmentationDataModified } from '../../../../stateManagement/segmentation/triggerSegmentationEvents';
|
|
6
5
|
import PreviewMethods from './preview';
|
|
7
|
-
const { VoxelManager } = utilities;
|
|
8
6
|
export default {
|
|
9
|
-
[StrategyCallbacks.Interpolate]: (operationData, configuration) => {
|
|
7
|
+
[StrategyCallbacks.Interpolate]: async (operationData, configuration) => {
|
|
10
8
|
const { segmentationImageData, segmentIndex, preview, segmentationVoxelManager, previewSegmentIndex, previewVoxelManager, } = operationData;
|
|
11
9
|
if (preview) {
|
|
12
10
|
const callback = ({ index }) => {
|
|
@@ -15,7 +13,18 @@ export default {
|
|
|
15
13
|
previewVoxelManager.forEach(callback);
|
|
16
14
|
}
|
|
17
15
|
const inputImage = getItkImage(segmentationImageData, 'interpolation');
|
|
18
|
-
|
|
16
|
+
let itkModule;
|
|
17
|
+
try {
|
|
18
|
+
itkModule = await peerImport('@itk-wasm/morphological-contour-interpolation');
|
|
19
|
+
if (!itkModule) {
|
|
20
|
+
throw new Error('Module not found');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.debug("Warning: '@itk-wasm/morphological-contour-interpolation' module not found. Please install it separately.");
|
|
25
|
+
return operationData;
|
|
26
|
+
}
|
|
27
|
+
const outputPromise = itkModule.morphologicalContourInterpolation(inputImage, {
|
|
19
28
|
...configuration,
|
|
20
29
|
label: segmentIndex,
|
|
21
30
|
webWorker: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function clip(a: any, b: any, box: any, da?: any, db?: any):
|
|
1
|
+
export default function clip(a: any, b: any, box: any, da?: any, db?: any): 0 | 1;
|
|
@@ -2,7 +2,6 @@ import { expose } from 'comlink';
|
|
|
2
2
|
import { utilities } from '@cornerstonejs/core';
|
|
3
3
|
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
4
4
|
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
|
|
5
|
-
import ICRPolySeg from '@icr/polyseg-wasm';
|
|
6
5
|
import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
|
|
7
6
|
import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
|
|
8
7
|
import vtkContourLoopExtraction from '@kitware/vtk.js/Filters/General/ContourLoopExtraction';
|
|
@@ -10,11 +9,21 @@ import vtkCutter from '@kitware/vtk.js/Filters/Core/Cutter';
|
|
|
10
9
|
import { getBoundingBoxAroundShapeWorld } from '../utilities/boundingBox';
|
|
11
10
|
import { containsPoint, getAABB, projectTo2D, } from '../utilities/math/polyline';
|
|
12
11
|
import { isPlaneIntersectingAABB } from '../utilities/planar';
|
|
12
|
+
import { checkStandardBasis, rotatePoints } from '../geometricSurfaceUtils';
|
|
13
13
|
const polySegConverters = {
|
|
14
14
|
polySeg: null,
|
|
15
15
|
polySegInitializing: false,
|
|
16
16
|
polySegInitializingPromise: null,
|
|
17
17
|
async initializePolySeg(progressCallback) {
|
|
18
|
+
let ICRPolySeg;
|
|
19
|
+
try {
|
|
20
|
+
ICRPolySeg = (await import('@icr/polyseg-wasm')).default;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error(error);
|
|
24
|
+
console.debug("Warning: '@icr/polyseg-wasm' module not found. Please install it separately.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
18
27
|
if (this.polySegInitializing) {
|
|
19
28
|
await this.polySegInitializingPromise;
|
|
20
29
|
return;
|
|
@@ -47,14 +56,16 @@ const polySegConverters = {
|
|
|
47
56
|
const [progressCallback] = callbacks;
|
|
48
57
|
await this.initializePolySeg(progressCallback);
|
|
49
58
|
const results = this.polySeg.instance.convertLabelmapToSurface(args.scalarData, args.dimensions, args.spacing, args.direction, args.origin, [args.segmentIndex]);
|
|
59
|
+
const rotationInfo = checkStandardBasis(args.direction);
|
|
60
|
+
if (!rotationInfo.isStandard) {
|
|
61
|
+
const rotatedPoints = rotatePoints(rotationInfo.rotationMatrix, args.origin, results.points);
|
|
62
|
+
results.points = [...rotatedPoints];
|
|
63
|
+
}
|
|
50
64
|
return results;
|
|
51
65
|
},
|
|
52
66
|
async convertContourToVolumeLabelmap(args, ...callbacks) {
|
|
53
67
|
const [progressCallback] = callbacks;
|
|
54
|
-
|
|
55
|
-
await polySeg.initialize({
|
|
56
|
-
updateProgress: progressCallback,
|
|
57
|
-
});
|
|
68
|
+
await this.initializePolySeg(progressCallback);
|
|
58
69
|
const { segmentIndices, scalarData, annotationUIDsInSegmentMap, dimensions, origin, direction, spacing, } = args;
|
|
59
70
|
const segmentationVoxelManager = utilities.VoxelManager.createScalarVolumeVoxelManager({
|
|
60
71
|
dimensions,
|
|
@@ -124,10 +135,7 @@ const polySegConverters = {
|
|
|
124
135
|
},
|
|
125
136
|
async convertContourToStackLabelmap(args, ...callbacks) {
|
|
126
137
|
const [progressCallback] = callbacks;
|
|
127
|
-
|
|
128
|
-
await polySeg.initialize({
|
|
129
|
-
updateProgress: progressCallback,
|
|
130
|
-
});
|
|
138
|
+
await this.initializePolySeg(progressCallback);
|
|
131
139
|
const { segmentationsInfo, annotationUIDsInSegmentMap, segmentIndices } = args;
|
|
132
140
|
const segmentationVoxelManagers = new Map();
|
|
133
141
|
segmentationsInfo.forEach((segmentationInfo, referencedImageId) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.5",
|
|
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.18.
|
|
107
|
+
"@cornerstonejs/core": "^2.18.5",
|
|
108
108
|
"@kitware/vtk.js": "32.9.0",
|
|
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": "be8bb617be050ca5aa5a9d2d61013a9c674efa5d"
|
|
127
127
|
}
|