@cornerstonejs/tools 1.13.2 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/drawingSvg/drawCircle.js +5 -1
- package/dist/cjs/drawingSvg/drawCircle.js.map +1 -1
- package/dist/cjs/store/ToolGroupManager/ToolGroup.js +1 -1
- package/dist/cjs/store/ToolGroupManager/ToolGroup.js.map +1 -1
- package/dist/cjs/tools/base/AnnotationTool.js +16 -14
- package/dist/cjs/tools/base/AnnotationTool.js.map +1 -1
- package/dist/cjs/tools/segmentation/BrushTool.js +24 -17
- package/dist/cjs/tools/segmentation/BrushTool.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/eraseSphere.d.ts +2 -0
- package/dist/cjs/tools/segmentation/strategies/eraseSphere.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/fillCircle.js +5 -7
- package/dist/cjs/tools/segmentation/strategies/fillCircle.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/fillSphere.d.ts +3 -0
- package/dist/cjs/tools/segmentation/strategies/fillSphere.js +38 -10
- package/dist/cjs/tools/segmentation/strategies/fillSphere.js.map +1 -1
- package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.d.ts +3 -0
- package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.js +10 -0
- package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.js.map +1 -0
- package/dist/esm/drawingSvg/drawCircle.js +5 -1
- package/dist/esm/drawingSvg/drawCircle.js.map +1 -1
- package/dist/esm/store/ToolGroupManager/ToolGroup.js +2 -2
- package/dist/esm/store/ToolGroupManager/ToolGroup.js.map +1 -1
- package/dist/esm/tools/base/AnnotationTool.js +16 -14
- package/dist/esm/tools/base/AnnotationTool.js.map +1 -1
- package/dist/esm/tools/segmentation/BrushTool.js +25 -18
- package/dist/esm/tools/segmentation/BrushTool.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/eraseSphere.d.ts +2 -0
- package/dist/esm/tools/segmentation/strategies/eraseSphere.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/fillCircle.js +1 -6
- package/dist/esm/tools/segmentation/strategies/fillCircle.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/fillSphere.d.ts +3 -0
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +33 -9
- package/dist/esm/tools/segmentation/strategies/fillSphere.js.map +1 -1
- package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.d.ts +3 -0
- package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.js +8 -0
- package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.js.map +1 -0
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/drawingSvg/drawCircle.ts +13 -1
- package/src/store/ToolGroupManager/ToolGroup.ts +4 -1
- package/src/tools/base/AnnotationTool.ts +19 -17
- package/src/tools/segmentation/BrushTool.ts +42 -19
- package/src/tools/segmentation/strategies/eraseSphere.ts +2 -0
- package/src/tools/segmentation/strategies/fillCircle.ts +1 -13
- package/src/tools/segmentation/strategies/fillSphere.ts +56 -8
- package/src/tools/segmentation/strategies/utils/isWithinThreshold.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.3",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"main": "dist/umd/index.js",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@cornerstonejs/core": "^1.13.
|
|
32
|
+
"@cornerstonejs/core": "^1.13.3",
|
|
33
33
|
"lodash.clonedeep": "4.5.0",
|
|
34
34
|
"lodash.get": "^4.4.2"
|
|
35
35
|
},
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"type": "individual",
|
|
53
53
|
"url": "https://ohif.org/donate"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "c6b8f5db43f3ae2e397240bf7d0da0be7a4fd434"
|
|
56
56
|
}
|
|
@@ -15,13 +15,23 @@ function drawCircle(
|
|
|
15
15
|
options = {},
|
|
16
16
|
dataId = ''
|
|
17
17
|
): void {
|
|
18
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
color,
|
|
20
|
+
fill,
|
|
21
|
+
width,
|
|
22
|
+
lineWidth,
|
|
23
|
+
lineDash,
|
|
24
|
+
fillOpacity,
|
|
25
|
+
strokeOpacity,
|
|
26
|
+
} = Object.assign(
|
|
19
27
|
{
|
|
20
28
|
color: 'dodgerblue',
|
|
21
29
|
fill: 'transparent',
|
|
22
30
|
width: '2',
|
|
23
31
|
lineDash: undefined,
|
|
24
32
|
lineWidth: undefined,
|
|
33
|
+
strokeOpacity: 1,
|
|
34
|
+
fillOpacity: 1,
|
|
25
35
|
},
|
|
26
36
|
options
|
|
27
37
|
);
|
|
@@ -42,6 +52,8 @@ function drawCircle(
|
|
|
42
52
|
fill,
|
|
43
53
|
'stroke-width': strokeWidth,
|
|
44
54
|
'stroke-dasharray': lineDash,
|
|
55
|
+
'fill-opacity': fillOpacity, // setting fill opacity
|
|
56
|
+
'stroke-opacity': strokeOpacity, // setting stroke opacity
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
if (existingCircleElement) {
|
|
@@ -621,7 +621,10 @@ export default class ToolGroup implements IToolGroup {
|
|
|
621
621
|
if (overwrite) {
|
|
622
622
|
_configuration = configuration;
|
|
623
623
|
} else {
|
|
624
|
-
|
|
624
|
+
// We should not deep copy here, it is the job of the application to
|
|
625
|
+
// deep copy the configuration before passing it to the toolGroup, otherwise
|
|
626
|
+
// some strange appending behaviour happens for the arrays
|
|
627
|
+
_configuration = Object.assign(
|
|
625
628
|
this._toolInstances[toolName].configuration,
|
|
626
629
|
configuration
|
|
627
630
|
);
|
|
@@ -200,24 +200,26 @@ abstract class AnnotationTool extends AnnotationDisplayTool {
|
|
|
200
200
|
|
|
201
201
|
const { data } = annotation;
|
|
202
202
|
const { points, textBox } = data.handles;
|
|
203
|
-
const { worldBoundingBox } = textBox;
|
|
204
203
|
|
|
205
|
-
if (
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
204
|
+
if (textBox) {
|
|
205
|
+
const { worldBoundingBox } = textBox;
|
|
206
|
+
if (worldBoundingBox) {
|
|
207
|
+
const canvasBoundingBox = {
|
|
208
|
+
topLeft: viewport.worldToCanvas(worldBoundingBox.topLeft),
|
|
209
|
+
topRight: viewport.worldToCanvas(worldBoundingBox.topRight),
|
|
210
|
+
bottomLeft: viewport.worldToCanvas(worldBoundingBox.bottomLeft),
|
|
211
|
+
bottomRight: viewport.worldToCanvas(worldBoundingBox.bottomRight),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
canvasCoords[0] >= canvasBoundingBox.topLeft[0] &&
|
|
216
|
+
canvasCoords[0] <= canvasBoundingBox.bottomRight[0] &&
|
|
217
|
+
canvasCoords[1] >= canvasBoundingBox.topLeft[1] &&
|
|
218
|
+
canvasCoords[1] <= canvasBoundingBox.bottomRight[1]
|
|
219
|
+
) {
|
|
220
|
+
data.handles.activeHandleIndex = null;
|
|
221
|
+
return textBox;
|
|
222
|
+
}
|
|
221
223
|
}
|
|
222
224
|
}
|
|
223
225
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cache, getEnabledElement, StackViewport } from '@cornerstonejs/core';
|
|
2
|
+
import { vec3 } from 'gl-matrix';
|
|
2
3
|
|
|
3
4
|
import type { Types } from '@cornerstonejs/core';
|
|
4
5
|
import type {
|
|
@@ -8,7 +9,10 @@ import type {
|
|
|
8
9
|
SVGDrawingHelper,
|
|
9
10
|
} from '../../types';
|
|
10
11
|
import { BaseTool } from '../base';
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
fillInsideSphere,
|
|
14
|
+
thresholdInsideSphere,
|
|
15
|
+
} from './strategies/fillSphere';
|
|
12
16
|
import { eraseInsideSphere } from './strategies/eraseSphere';
|
|
13
17
|
import {
|
|
14
18
|
thresholdInsideCircle,
|
|
@@ -59,10 +63,11 @@ class BrushTool extends BaseTool {
|
|
|
59
63
|
configuration: {
|
|
60
64
|
strategies: {
|
|
61
65
|
FILL_INSIDE_CIRCLE: fillInsideCircle,
|
|
62
|
-
THRESHOLD_INSIDE_CIRCLE: thresholdInsideCircle,
|
|
63
66
|
ERASE_INSIDE_CIRCLE: eraseInsideCircle,
|
|
64
67
|
FILL_INSIDE_SPHERE: fillInsideSphere,
|
|
65
68
|
ERASE_INSIDE_SPHERE: eraseInsideSphere,
|
|
69
|
+
THRESHOLD_INSIDE_CIRCLE: thresholdInsideCircle,
|
|
70
|
+
THRESHOLD_INSIDE_SPHERE: thresholdInsideSphere,
|
|
66
71
|
},
|
|
67
72
|
strategySpecificConfiguration: {
|
|
68
73
|
THRESHOLD_INSIDE_CIRCLE: {
|
|
@@ -277,24 +282,42 @@ class BrushTool extends BaseTool {
|
|
|
277
282
|
const enabledElement = getEnabledElement(element);
|
|
278
283
|
const { viewport } = enabledElement;
|
|
279
284
|
const { canvasToWorld } = viewport;
|
|
285
|
+
const camera = viewport.getCamera();
|
|
280
286
|
const { brushSize } = this.configuration;
|
|
281
|
-
// Center of circle in canvas Coordinates
|
|
282
287
|
|
|
283
|
-
const
|
|
288
|
+
const viewUp = vec3.fromValues(
|
|
289
|
+
camera.viewUp[0],
|
|
290
|
+
camera.viewUp[1],
|
|
291
|
+
camera.viewUp[2]
|
|
292
|
+
);
|
|
293
|
+
const viewPlaneNormal = vec3.fromValues(
|
|
294
|
+
camera.viewPlaneNormal[0],
|
|
295
|
+
camera.viewPlaneNormal[1],
|
|
296
|
+
camera.viewPlaneNormal[2]
|
|
297
|
+
);
|
|
298
|
+
const viewRight = vec3.create();
|
|
284
299
|
|
|
285
|
-
|
|
300
|
+
vec3.cross(viewRight, viewUp, viewPlaneNormal);
|
|
301
|
+
|
|
302
|
+
// in the world coordinate system, the brushSize is the radius of the circle
|
|
303
|
+
// in mm
|
|
304
|
+
const centerCursorInWorld: Types.Point3 = canvasToWorld([
|
|
286
305
|
centerCanvas[0],
|
|
287
|
-
centerCanvas[1] + radius,
|
|
288
|
-
];
|
|
289
|
-
const topCanvas: Types.Point2 = [centerCanvas[0], centerCanvas[1] - radius];
|
|
290
|
-
const leftCanvas: Types.Point2 = [
|
|
291
|
-
centerCanvas[0] - radius,
|
|
292
|
-
centerCanvas[1],
|
|
293
|
-
];
|
|
294
|
-
const rightCanvas: Types.Point2 = [
|
|
295
|
-
centerCanvas[0] + radius,
|
|
296
306
|
centerCanvas[1],
|
|
297
|
-
];
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
const bottomCursorInWorld = vec3.create();
|
|
310
|
+
const topCursorInWorld = vec3.create();
|
|
311
|
+
const leftCursorInWorld = vec3.create();
|
|
312
|
+
const rightCursorInWorld = vec3.create();
|
|
313
|
+
|
|
314
|
+
// Calculate the bottom and top points of the circle in world coordinates
|
|
315
|
+
for (let i = 0; i <= 2; i++) {
|
|
316
|
+
bottomCursorInWorld[i] = centerCursorInWorld[i] - viewUp[i] * brushSize;
|
|
317
|
+
topCursorInWorld[i] = centerCursorInWorld[i] + viewUp[i] * brushSize;
|
|
318
|
+
leftCursorInWorld[i] = centerCursorInWorld[i] - viewRight[i] * brushSize;
|
|
319
|
+
rightCursorInWorld[i] = centerCursorInWorld[i] + viewRight[i] * brushSize;
|
|
320
|
+
}
|
|
298
321
|
|
|
299
322
|
const { brushCursor } = this._hoverData;
|
|
300
323
|
const { data } = brushCursor;
|
|
@@ -304,10 +327,10 @@ class BrushTool extends BaseTool {
|
|
|
304
327
|
}
|
|
305
328
|
|
|
306
329
|
data.handles.points = [
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
330
|
+
bottomCursorInWorld,
|
|
331
|
+
topCursorInWorld,
|
|
332
|
+
leftCursorInWorld,
|
|
333
|
+
rightCursorInWorld,
|
|
311
334
|
];
|
|
312
335
|
|
|
313
336
|
data.invalidated = false;
|
|
@@ -4,6 +4,7 @@ import { fillInsideSphere } from './fillSphere';
|
|
|
4
4
|
|
|
5
5
|
type OperationData = {
|
|
6
6
|
points: [Types.Point3, Types.Point3, Types.Point3, Types.Point3];
|
|
7
|
+
imageVolume: Types.IImageVolume;
|
|
7
8
|
volume: Types.IImageVolume;
|
|
8
9
|
segmentIndex: number;
|
|
9
10
|
segmentationId: string;
|
|
@@ -11,6 +12,7 @@ type OperationData = {
|
|
|
11
12
|
viewPlaneNormal: Types.Point3;
|
|
12
13
|
viewUp: Types.Point3;
|
|
13
14
|
constraintFn: () => boolean;
|
|
15
|
+
strategySpecificConfiguration: any;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export function eraseInsideSphere(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { getBoundingBoxAroundShape } from '../../../utilities/boundingBox';
|
|
10
10
|
import { triggerSegmentationDataModified } from '../../../stateManagement/segmentation/triggerSegmentationEvents';
|
|
11
11
|
import { pointInShapeCallback } from '../../../utilities';
|
|
12
|
+
import isWithinThreshold from './utils/isWithinThreshold';
|
|
12
13
|
|
|
13
14
|
const { transformWorldToIndex } = csUtils;
|
|
14
15
|
|
|
@@ -117,19 +118,6 @@ function fillCircle(
|
|
|
117
118
|
triggerSegmentationDataModified(segmentationId, arrayOfSlices);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
function isWithinThreshold(
|
|
121
|
-
index: number,
|
|
122
|
-
imageVolume: Types.IImageVolume,
|
|
123
|
-
strategySpecificConfiguration: any
|
|
124
|
-
) {
|
|
125
|
-
const { THRESHOLD_INSIDE_CIRCLE } = strategySpecificConfiguration;
|
|
126
|
-
|
|
127
|
-
const voxelValue = imageVolume.getScalarData()[index];
|
|
128
|
-
const { threshold } = THRESHOLD_INSIDE_CIRCLE;
|
|
129
|
-
|
|
130
|
-
return threshold[0] <= voxelValue && voxelValue <= threshold[1];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
121
|
/**
|
|
134
122
|
* Fill inside the circular region segment inside the segmentation defined by the operationData.
|
|
135
123
|
* It fills the segmentation pixels inside the defined circle.
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import { utilities as csUtils } from '@cornerstonejs/core';
|
|
2
3
|
|
|
3
4
|
import { triggerSegmentationDataModified } from '../../../stateManagement/segmentation/triggerSegmentationEvents';
|
|
4
5
|
import { pointInSurroundingSphereCallback } from '../../../utilities';
|
|
6
|
+
import isWithinThreshold from './utils/isWithinThreshold';
|
|
5
7
|
|
|
6
8
|
type OperationData = {
|
|
7
9
|
points: [Types.Point3, Types.Point3, Types.Point3, Types.Point3];
|
|
8
10
|
volume: Types.IImageVolume;
|
|
11
|
+
imageVolume: Types.IImageVolume;
|
|
9
12
|
segmentIndex: number;
|
|
10
13
|
segmentationId: string;
|
|
11
14
|
segmentsLocked: number[];
|
|
12
15
|
viewPlaneNormal: Types.Point3;
|
|
13
16
|
viewUp: Types.Point3;
|
|
17
|
+
strategySpecificConfiguration: any;
|
|
14
18
|
constraintFn: () => boolean;
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
function fillSphere(
|
|
18
22
|
enabledElement: Types.IEnabledElement,
|
|
19
23
|
operationData: OperationData,
|
|
20
|
-
_inside = true
|
|
24
|
+
_inside = true,
|
|
25
|
+
threshold = false
|
|
21
26
|
): void {
|
|
22
27
|
const { viewport } = enabledElement;
|
|
23
28
|
const {
|
|
24
29
|
volume: segmentation,
|
|
25
30
|
segmentsLocked,
|
|
26
31
|
segmentIndex,
|
|
32
|
+
imageVolume,
|
|
33
|
+
strategySpecificConfiguration,
|
|
27
34
|
segmentationId,
|
|
28
35
|
points,
|
|
29
36
|
} = operationData;
|
|
@@ -32,13 +39,30 @@ function fillSphere(
|
|
|
32
39
|
const scalarData = segmentation.getScalarData();
|
|
33
40
|
const scalarIndex = [];
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
let callback;
|
|
43
|
+
|
|
44
|
+
if (threshold) {
|
|
45
|
+
callback = ({ value, index, pointIJK }) => {
|
|
46
|
+
if (segmentsLocked.includes(value)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
isWithinThreshold(index, imageVolume, strategySpecificConfiguration)
|
|
52
|
+
) {
|
|
53
|
+
scalarData[index] = segmentIndex;
|
|
54
|
+
scalarIndex.push(index);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
callback = ({ index, value }) => {
|
|
59
|
+
if (segmentsLocked.includes(value)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
scalarData[index] = segmentIndex;
|
|
63
|
+
scalarIndex.push(index);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
42
66
|
|
|
43
67
|
pointInSurroundingSphereCallback(
|
|
44
68
|
imageData,
|
|
@@ -74,6 +98,30 @@ export function fillInsideSphere(
|
|
|
74
98
|
fillSphere(enabledElement, operationData, true);
|
|
75
99
|
}
|
|
76
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Fill inside the circular region segment inside the segmentation defined by the operationData.
|
|
103
|
+
* It fills the segmentation pixels inside the defined circle.
|
|
104
|
+
* @param enabledElement - The element for which the segment is being filled.
|
|
105
|
+
* @param operationData - EraseOperationData
|
|
106
|
+
*/
|
|
107
|
+
export function thresholdInsideSphere(
|
|
108
|
+
enabledElement: Types.IEnabledElement,
|
|
109
|
+
operationData: OperationData
|
|
110
|
+
): void {
|
|
111
|
+
const { volume, imageVolume } = operationData;
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
!csUtils.isEqual(volume.dimensions, imageVolume.dimensions) ||
|
|
115
|
+
!csUtils.isEqual(volume.direction, imageVolume.direction)
|
|
116
|
+
) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'Only source data the same dimensions/size/orientation as the segmentation currently supported.'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fillSphere(enabledElement, operationData, true, true);
|
|
123
|
+
}
|
|
124
|
+
|
|
77
125
|
/**
|
|
78
126
|
* Fill outside a sphere with the given segment index in the given operation data. The
|
|
79
127
|
* operation data contains the sphere required points.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Types } from '@cornerstonejs/core';
|
|
2
|
+
|
|
3
|
+
function isWithinThreshold(
|
|
4
|
+
index: number,
|
|
5
|
+
imageVolume: Types.IImageVolume,
|
|
6
|
+
strategySpecificConfiguration: any
|
|
7
|
+
) {
|
|
8
|
+
const { THRESHOLD_INSIDE_CIRCLE } = strategySpecificConfiguration;
|
|
9
|
+
|
|
10
|
+
const voxelValue = imageVolume.getScalarData()[index];
|
|
11
|
+
const { threshold } = THRESHOLD_INSIDE_CIRCLE;
|
|
12
|
+
|
|
13
|
+
return threshold[0] <= voxelValue && voxelValue <= threshold[1];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default isWithinThreshold;
|