@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.
Files changed (47) hide show
  1. package/dist/cjs/drawingSvg/drawCircle.js +5 -1
  2. package/dist/cjs/drawingSvg/drawCircle.js.map +1 -1
  3. package/dist/cjs/store/ToolGroupManager/ToolGroup.js +1 -1
  4. package/dist/cjs/store/ToolGroupManager/ToolGroup.js.map +1 -1
  5. package/dist/cjs/tools/base/AnnotationTool.js +16 -14
  6. package/dist/cjs/tools/base/AnnotationTool.js.map +1 -1
  7. package/dist/cjs/tools/segmentation/BrushTool.js +24 -17
  8. package/dist/cjs/tools/segmentation/BrushTool.js.map +1 -1
  9. package/dist/cjs/tools/segmentation/strategies/eraseSphere.d.ts +2 -0
  10. package/dist/cjs/tools/segmentation/strategies/eraseSphere.js.map +1 -1
  11. package/dist/cjs/tools/segmentation/strategies/fillCircle.js +5 -7
  12. package/dist/cjs/tools/segmentation/strategies/fillCircle.js.map +1 -1
  13. package/dist/cjs/tools/segmentation/strategies/fillSphere.d.ts +3 -0
  14. package/dist/cjs/tools/segmentation/strategies/fillSphere.js +38 -10
  15. package/dist/cjs/tools/segmentation/strategies/fillSphere.js.map +1 -1
  16. package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.d.ts +3 -0
  17. package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.js +10 -0
  18. package/dist/cjs/tools/segmentation/strategies/utils/isWithinThreshold.js.map +1 -0
  19. package/dist/esm/drawingSvg/drawCircle.js +5 -1
  20. package/dist/esm/drawingSvg/drawCircle.js.map +1 -1
  21. package/dist/esm/store/ToolGroupManager/ToolGroup.js +2 -2
  22. package/dist/esm/store/ToolGroupManager/ToolGroup.js.map +1 -1
  23. package/dist/esm/tools/base/AnnotationTool.js +16 -14
  24. package/dist/esm/tools/base/AnnotationTool.js.map +1 -1
  25. package/dist/esm/tools/segmentation/BrushTool.js +25 -18
  26. package/dist/esm/tools/segmentation/BrushTool.js.map +1 -1
  27. package/dist/esm/tools/segmentation/strategies/eraseSphere.d.ts +2 -0
  28. package/dist/esm/tools/segmentation/strategies/eraseSphere.js.map +1 -1
  29. package/dist/esm/tools/segmentation/strategies/fillCircle.js +1 -6
  30. package/dist/esm/tools/segmentation/strategies/fillCircle.js.map +1 -1
  31. package/dist/esm/tools/segmentation/strategies/fillSphere.d.ts +3 -0
  32. package/dist/esm/tools/segmentation/strategies/fillSphere.js +33 -9
  33. package/dist/esm/tools/segmentation/strategies/fillSphere.js.map +1 -1
  34. package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.d.ts +3 -0
  35. package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.js +8 -0
  36. package/dist/esm/tools/segmentation/strategies/utils/isWithinThreshold.js.map +1 -0
  37. package/dist/umd/index.js +1 -1
  38. package/dist/umd/index.js.map +1 -1
  39. package/package.json +3 -3
  40. package/src/drawingSvg/drawCircle.ts +13 -1
  41. package/src/store/ToolGroupManager/ToolGroup.ts +4 -1
  42. package/src/tools/base/AnnotationTool.ts +19 -17
  43. package/src/tools/segmentation/BrushTool.ts +42 -19
  44. package/src/tools/segmentation/strategies/eraseSphere.ts +2 -0
  45. package/src/tools/segmentation/strategies/fillCircle.ts +1 -13
  46. package/src/tools/segmentation/strategies/fillSphere.ts +56 -8
  47. 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.2",
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.2",
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": "b41b433d7c4062667a7b51ca8ad37c4ff939ba09"
55
+ "gitHead": "c6b8f5db43f3ae2e397240bf7d0da0be7a4fd434"
56
56
  }
@@ -15,13 +15,23 @@ function drawCircle(
15
15
  options = {},
16
16
  dataId = ''
17
17
  ): void {
18
- const { color, fill, width, lineWidth, lineDash } = Object.assign(
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
- _configuration = csUtils.deepMerge(
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 (worldBoundingBox) {
206
- const canvasBoundingBox = {
207
- topLeft: viewport.worldToCanvas(worldBoundingBox.topLeft),
208
- topRight: viewport.worldToCanvas(worldBoundingBox.topRight),
209
- bottomLeft: viewport.worldToCanvas(worldBoundingBox.bottomLeft),
210
- bottomRight: viewport.worldToCanvas(worldBoundingBox.bottomRight),
211
- };
212
-
213
- if (
214
- canvasCoords[0] >= canvasBoundingBox.topLeft[0] &&
215
- canvasCoords[0] <= canvasBoundingBox.bottomRight[0] &&
216
- canvasCoords[1] >= canvasBoundingBox.topLeft[1] &&
217
- canvasCoords[1] <= canvasBoundingBox.bottomRight[1]
218
- ) {
219
- data.handles.activeHandleIndex = null;
220
- return textBox;
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 { fillInsideSphere } from './strategies/fillSphere';
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 radius = brushSize;
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
- const bottomCanvas: Types.Point2 = [
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
- canvasToWorld(bottomCanvas),
308
- canvasToWorld(topCanvas),
309
- canvasToWorld(leftCanvas),
310
- canvasToWorld(rightCanvas),
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
- const callback = ({ index, value }) => {
36
- if (segmentsLocked.includes(value)) {
37
- return;
38
- }
39
- scalarData[index] = segmentIndex;
40
- scalarIndex.push(index);
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;