@cornerstonejs/tools 2.8.5 → 2.9.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.
Files changed (49) hide show
  1. package/dist/esm/eventDispatchers/shared/getToolsWithActionsForKeyboardEvents.js +1 -1
  2. package/dist/esm/eventDispatchers/shared/getToolsWithActionsForMouseEvent.js +1 -1
  3. package/dist/esm/index.d.ts +2 -2
  4. package/dist/esm/index.js +2 -2
  5. package/dist/esm/stateManagement/segmentation/activeSegmentation.d.ts +1 -1
  6. package/dist/esm/stateManagement/segmentation/activeSegmentation.js +1 -1
  7. package/dist/esm/stateManagement/segmentation/index.d.ts +3 -1
  8. package/dist/esm/stateManagement/segmentation/index.js +3 -1
  9. package/dist/esm/stateManagement/segmentation/polySeg/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.js +1 -1
  10. package/dist/esm/tools/annotation/RegionSegmentPlusTool.d.ts +3 -2
  11. package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +27 -12
  12. package/dist/esm/tools/annotation/RegionSegmentTool.d.ts +1 -1
  13. package/dist/esm/tools/annotation/RegionSegmentTool.js +7 -8
  14. package/dist/esm/tools/annotation/SplineROITool.js +7 -1
  15. package/dist/esm/tools/annotation/WholeBodySegmentTool.d.ts +3 -2
  16. package/dist/esm/tools/annotation/WholeBodySegmentTool.js +40 -5
  17. package/dist/esm/tools/base/AnnotationTool.d.ts +2 -1
  18. package/dist/esm/tools/base/AnnotationTool.js +6 -3
  19. package/dist/esm/tools/base/ContourSegmentationBaseTool.d.ts +1 -0
  20. package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -0
  21. package/dist/esm/tools/base/GrowCutBaseTool.d.ts +25 -10
  22. package/dist/esm/tools/base/GrowCutBaseTool.js +124 -21
  23. package/dist/esm/tools/index.d.ts +3 -1
  24. package/dist/esm/tools/index.js +3 -1
  25. package/dist/esm/tools/segmentation/BrushTool.d.ts +0 -1
  26. package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +5 -1
  27. package/dist/esm/tools/segmentation/LabelmapBaseTool.js +107 -12
  28. package/dist/esm/tools/segmentation/strategies/compositions/index.js +1 -1
  29. package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.d.ts +5 -0
  30. package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.js +27 -0
  31. package/dist/esm/tools/segmentation/strategies/compositions/labelmapStatistics.js +69 -2
  32. package/dist/esm/types/CalculatorTypes.d.ts +4 -0
  33. package/dist/esm/utilities/contourSegmentation/addContourSegmentationAnnotation.js +5 -2
  34. package/dist/esm/utilities/index.d.ts +3 -1
  35. package/dist/esm/utilities/index.js +3 -1
  36. package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +2 -2
  37. package/dist/esm/utilities/segmentation/VolumetricCalculator.d.ts +7 -0
  38. package/dist/esm/utilities/segmentation/VolumetricCalculator.js +28 -0
  39. package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.d.ts +0 -2
  40. package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.d.ts +0 -4
  41. package/dist/esm/utilities/segmentation/index.d.ts +2 -1
  42. package/dist/esm/utilities/segmentation/index.js +2 -1
  43. package/dist/esm/utilities/segmentation/islandRemoval.d.ts +28 -0
  44. package/dist/esm/utilities/segmentation/islandRemoval.js +181 -0
  45. package/package.json +3 -3
  46. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.d.ts +0 -16
  47. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +0 -159
  48. /package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.d.ts +0 -0
  49. /package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.js +0 -0
@@ -1,13 +1,25 @@
1
- import { getEnabledElement, utilities as csUtils, cache, } from '@cornerstonejs/core';
1
+ import { getEnabledElement, utilities as csUtils, cache, getRenderingEngine, StackViewport, } from '@cornerstonejs/core';
2
2
  import { BaseTool } from '../base';
3
3
  import { SegmentationRepresentations } from '../../enums';
4
4
  import { segmentIndex as segmentIndexController, state as segmentationState, activeSegmentation, } from '../../stateManagement/segmentation';
5
5
  import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
6
6
  import { getSVGStyleForSegment } from '../../utilities/segmentation/getSVGStyleForSegment';
7
+ import IslandRemoval from '../../utilities/segmentation/islandRemoval';
7
8
  const { transformWorldToIndex, transformIndexToWorld } = csUtils;
8
9
  class GrowCutBaseTool extends BaseTool {
10
+ static { this.lastGrowCutCommand = null; }
9
11
  constructor(toolProps, defaultToolProps) {
10
- super(toolProps, defaultToolProps);
12
+ const baseToolProps = csUtils.deepMerge({
13
+ configuration: {
14
+ positiveSeedVariance: 0.1,
15
+ negativeSeedVariance: 0.9,
16
+ shrinkExpandIncrement: 0.05,
17
+ islandRemoval: {
18
+ enabled: false,
19
+ },
20
+ },
21
+ }, defaultToolProps);
22
+ super(toolProps, baseToolProps);
11
23
  }
12
24
  async preMouseDownCallback(evt) {
13
25
  const eventData = evt.detail;
@@ -16,7 +28,7 @@ class GrowCutBaseTool extends BaseTool {
16
28
  const enabledElement = getEnabledElement(element);
17
29
  const { viewport, renderingEngine } = enabledElement;
18
30
  const { viewUp } = viewport.getCamera();
19
- const { segmentationId, segmentIndex, labelmapVolumeId, referencedVolumeId, } = this.getLabelmapSegmentationData(viewport);
31
+ const { segmentationId, segmentIndex, labelmapVolumeId, referencedVolumeId, } = await this.getLabelmapSegmentationData(viewport);
20
32
  if (!this._isOrthogonalView(viewport, referencedVolumeId)) {
21
33
  throw new Error('Oblique view is not supported yet');
22
34
  }
@@ -37,18 +49,59 @@ class GrowCutBaseTool extends BaseTool {
37
49
  evt.preventDefault();
38
50
  return true;
39
51
  }
40
- async getGrowCutLabelmap() {
52
+ shrink() {
53
+ this._runLastCommand({
54
+ shrinkExpandAmount: -this.configuration.shrinkExpandIncrement,
55
+ });
56
+ }
57
+ expand() {
58
+ this._runLastCommand({
59
+ shrinkExpandAmount: this.configuration.shrinkExpandIncrement,
60
+ });
61
+ }
62
+ refresh() {
63
+ this._runLastCommand();
64
+ }
65
+ async getGrowCutLabelmap(_growCutData) {
41
66
  throw new Error('Not implemented');
42
67
  }
43
68
  async runGrowCut() {
44
- const { segmentation: { segmentationId, segmentIndex, labelmapVolumeId }, } = this.growCutData;
69
+ const { growCutData, configuration: config } = this;
70
+ const { segmentation: { segmentationId, segmentIndex, labelmapVolumeId }, } = growCutData;
71
+ const hasSeedVarianceData = config.positiveSeedVariance !== undefined &&
72
+ config.negativeSeedVariance !== undefined;
45
73
  const labelmap = cache.getVolume(labelmapVolumeId);
46
- const growcutLabelmap = await this.getGrowCutLabelmap();
47
- this.applyGrowCutLabelmap(segmentationId, segmentIndex, labelmap, growcutLabelmap);
74
+ let shrinkExpandValue = 0;
75
+ const growCutCommand = async ({ shrinkExpandAmount = 0 } = {}) => {
76
+ const { positiveSeedVariance, negativeSeedVariance } = config;
77
+ let newPositiveSeedVariance = undefined;
78
+ let newNegativeSeedVariance = undefined;
79
+ shrinkExpandValue += shrinkExpandAmount;
80
+ if (hasSeedVarianceData) {
81
+ newPositiveSeedVariance = positiveSeedVariance + shrinkExpandValue;
82
+ newNegativeSeedVariance = negativeSeedVariance + shrinkExpandValue;
83
+ }
84
+ const updatedGrowCutData = Object.assign({}, growCutData, {
85
+ options: {
86
+ positiveSeedValue: segmentIndex,
87
+ negativeSeedValue: 255,
88
+ positiveSeedVariance: newPositiveSeedVariance,
89
+ negativeSeedVariance: newNegativeSeedVariance,
90
+ },
91
+ });
92
+ const growcutLabelmap = await this.getGrowCutLabelmap(updatedGrowCutData);
93
+ this.applyGrowCutLabelmap(segmentationId, segmentIndex, labelmap, growcutLabelmap);
94
+ this._removeIslands(growCutData);
95
+ };
96
+ await growCutCommand();
97
+ if (hasSeedVarianceData) {
98
+ GrowCutBaseTool.lastGrowCutCommand = growCutCommand;
99
+ }
100
+ this.growCutData = null;
48
101
  }
49
102
  applyGrowCutLabelmap(segmentationId, segmentIndex, targetLabelmap, sourceLabelmap) {
50
103
  const srcLabelmapData = sourceLabelmap.voxelManager.getCompleteScalarDataArray();
51
- const targetLabelmapData = targetLabelmap.voxelManager.getCompleteScalarDataArray();
104
+ const tgtVoxelManager = targetLabelmap.voxelManager;
52
105
  const [srcColumns, srcRows, srcNumSlices] = sourceLabelmap.dimensions;
53
106
  const [tgtColumns, tgtRows] = targetLabelmap.dimensions;
54
107
  const srcPixelsPerSlice = srcColumns * srcRows;
@@ -62,29 +115,34 @@ class GrowCutBaseTool extends BaseTool {
62
115
  const srcOffset = srcRow * srcColumns + srcSlice * srcPixelsPerSlice;
63
116
  const tgtOffset = tgtColumn + tgtRow * tgtColumns + tgtSlice * tgtPixelsPerSlice;
64
117
  for (let column = 0; column < srcColumns; column++) {
65
- targetLabelmapData[tgtOffset + column] =
66
- srcLabelmapData[srcOffset + column] === segmentIndex
67
- ? segmentIndex
68
- : 0;
118
+ const labelmapValue = srcLabelmapData[srcOffset + column] === segmentIndex
119
+ ? segmentIndex
120
+ : 0;
121
+ tgtVoxelManager.setAtIndex(tgtOffset + column, labelmapValue);
69
122
  }
70
123
  }
71
124
  }
72
- targetLabelmap.voxelManager.setCompleteScalarDataArray(targetLabelmapData);
73
125
  triggerSegmentationDataModified(segmentationId);
74
126
  }
75
- getSegmentStyle({ segmentationId, viewportId, segmentIndex }) {
76
- return getSVGStyleForSegment({
77
- segmentationId,
78
- segmentIndex,
79
- viewportId,
80
- });
127
+ _runLastCommand({ shrinkExpandAmount = 0 } = {}) {
128
+ const cmd = GrowCutBaseTool.lastGrowCutCommand;
129
+ if (cmd) {
130
+ cmd({ shrinkExpandAmount });
131
+ }
81
132
  }
82
- getLabelmapSegmentationData(viewport) {
83
- const { segmentationId } = activeSegmentation.getActiveSegmentation(viewport.id);
133
+ async getLabelmapSegmentationData(viewport) {
134
+ const activeSeg = activeSegmentation.getActiveSegmentation(viewport.id);
135
+ if (!activeSeg) {
136
+ throw new Error('No active segmentation found');
137
+ }
138
+ const { segmentationId } = activeSeg;
84
139
  const segmentIndex = segmentIndexController.getActiveSegmentIndex(segmentationId);
85
140
  const { representationData } = segmentationState.getSegmentation(segmentationId);
86
141
  const labelmapData = representationData[SegmentationRepresentations.Labelmap];
87
142
  const { volumeId: labelmapVolumeId, referencedVolumeId } = labelmapData;
143
+ if (!labelmapVolumeId) {
144
+ throw new Error('Labelmap volume id not found - not implemented');
145
+ }
88
146
  return {
89
147
  segmentationId,
90
148
  segmentIndex,
@@ -101,6 +159,51 @@ class GrowCutBaseTool extends BaseTool {
101
159
  csUtils.isEqual(Math.abs(vec[1]), 1) ||
102
160
  csUtils.isEqual(Math.abs(vec[2]), 1));
103
161
  }
162
+ getRemoveIslandData(_growCutData) {
163
+ return;
164
+ }
165
+ _removeIslands(growCutData) {
166
+ const { islandRemoval: config } = this.configuration;
167
+ if (!config.enabled) {
168
+ return;
169
+ }
170
+ const { segmentation: { segmentIndex, labelmapVolumeId }, renderingEngineId, viewportId, } = growCutData;
171
+ const labelmap = cache.getVolume(labelmapVolumeId);
172
+ const removeIslandData = this.getRemoveIslandData(growCutData);
173
+ if (!removeIslandData) {
174
+ return;
175
+ }
176
+ const [width, height] = labelmap.dimensions;
177
+ const numPixelsPerSlice = width * height;
178
+ const { worldIslandPoints = [], islandPointIndexes = [] } = removeIslandData;
179
+ let ijkIslandPoints = [...(removeIslandData?.ijkIslandPoints ?? [])];
180
+ const renderingEngine = getRenderingEngine(renderingEngineId);
181
+ const viewport = renderingEngine.getViewport(viewportId);
182
+ const { voxelManager } = labelmap;
183
+ const islandRemoval = new IslandRemoval();
184
+ ijkIslandPoints = ijkIslandPoints.concat(worldIslandPoints.map((worldPoint) => transformWorldToIndex(labelmap.imageData, worldPoint)));
185
+ ijkIslandPoints = ijkIslandPoints.concat(islandPointIndexes.map((pointIndex) => {
186
+ const x = pointIndex % width;
187
+ const y = Math.floor(pointIndex / width) % height;
188
+ const z = Math.floor(pointIndex / numPixelsPerSlice);
189
+ return [x, y, z];
190
+ }));
191
+ islandRemoval.initialize(viewport, voxelManager, {
192
+ points: ijkIslandPoints,
193
+ previewSegmentIndex: segmentIndex,
194
+ segmentIndex,
195
+ });
196
+ islandRemoval.floodFillSegmentIsland();
197
+ islandRemoval.removeExternalIslands();
198
+ islandRemoval.removeInternalIslands();
199
+ }
200
+ getSegmentStyle({ segmentationId, viewportId, segmentIndex }) {
201
+ return getSVGStyleForSegment({
202
+ segmentationId,
203
+ segmentIndex,
204
+ viewportId,
205
+ });
206
+ }
104
207
  }
105
208
  GrowCutBaseTool.toolName = 'GrowCutBaseTool';
106
209
  export default GrowCutBaseTool;
@@ -41,6 +41,7 @@ import AnnotationEraserTool from './AnnotationEraserTool';
41
41
  import RegionSegmentTool from './annotation/RegionSegmentTool';
42
42
  import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool';
43
43
  import WholeBodySegmentTool from './annotation/WholeBodySegmentTool';
44
+ import LabelmapBaseTool from './segmentation/LabelmapBaseTool';
44
45
  import RectangleScissorsTool from './segmentation/RectangleScissorsTool';
45
46
  import CircleScissorsTool from './segmentation/CircleScissorsTool';
46
47
  import SphereScissorsTool from './segmentation/SphereScissorsTool';
@@ -51,4 +52,5 @@ import BrushTool from './segmentation/BrushTool';
51
52
  import PaintFillTool from './segmentation/PaintFillTool';
52
53
  import OrientationMarkerTool from './OrientationMarkerTool';
53
54
  import SegmentSelectTool from './segmentation/SegmentSelectTool';
54
- export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, };
55
+ import * as strategies from './segmentation/strategies';
56
+ export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, strategies, };
@@ -41,6 +41,7 @@ import AnnotationEraserTool from './AnnotationEraserTool';
41
41
  import RegionSegmentTool from './annotation/RegionSegmentTool';
42
42
  import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool';
43
43
  import WholeBodySegmentTool from './annotation/WholeBodySegmentTool';
44
+ import LabelmapBaseTool from './segmentation/LabelmapBaseTool';
44
45
  import RectangleScissorsTool from './segmentation/RectangleScissorsTool';
45
46
  import CircleScissorsTool from './segmentation/CircleScissorsTool';
46
47
  import SphereScissorsTool from './segmentation/SphereScissorsTool';
@@ -51,4 +52,5 @@ import BrushTool from './segmentation/BrushTool';
51
52
  import PaintFillTool from './segmentation/PaintFillTool';
52
53
  import OrientationMarkerTool from './OrientationMarkerTool';
53
54
  import SegmentSelectTool from './segmentation/SegmentSelectTool';
54
- export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, };
55
+ import * as strategies from './segmentation/strategies';
56
+ export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, strategies, };
@@ -3,7 +3,6 @@ import type { PublicToolProps, ToolProps, EventTypes, SVGDrawingHelper } from '.
3
3
  import LabelmapBaseTool from './LabelmapBaseTool';
4
4
  declare class BrushTool extends LabelmapBaseTool {
5
5
  static toolName: any;
6
- prg: any;
7
6
  constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
8
7
  onSetToolPassive: (evt: any) => void;
9
8
  onSetToolEnabled: () => void;
@@ -31,8 +31,9 @@ export default class LabelmapBaseTool extends BaseTool {
31
31
  centerCanvas?: Array<number>;
32
32
  viewport: Types.IViewport;
33
33
  };
34
- protected _previewData?: PreviewData;
34
+ static previewData?: PreviewData;
35
35
  constructor(toolProps: any, defaultToolProps: any);
36
+ protected get _previewData(): PreviewData;
36
37
  createMemo(segmentId: string, segmentationVoxelManager: any, preview: any): LabelmapMemo.LabelmapMemo;
37
38
  createEditData(element: any): {
38
39
  volumeId: string;
@@ -157,4 +158,7 @@ export default class LabelmapBaseTool extends BaseTool {
157
158
  }): unknown;
158
159
  rejectPreview(element?: HTMLDivElement): void;
159
160
  acceptPreview(element?: HTMLDivElement): void;
161
+ static viewportContoursToLabelmap(viewport: Types.IViewport, options?: {
162
+ removeContours: boolean;
163
+ }): void;
160
164
  }
@@ -11,17 +11,25 @@ import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/
11
11
  import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
12
12
  import { StrategyCallbacks } from '../../enums';
13
13
  import * as LabelmapMemo from '../../utilities/segmentation/createLabelmapMemo';
14
+ import { getAllAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
15
+ import { filterAnnotationsForDisplay } from '../../utilities/planar';
16
+ import { isPointInsidePolyline3D } from '../../utilities/math/polyline';
17
+ import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
18
+ import { fillInsideCircle } from './strategies';
14
19
  export default class LabelmapBaseTool extends BaseTool {
20
+ static { this.previewData = {
21
+ preview: null,
22
+ element: null,
23
+ timerStart: 0,
24
+ timer: null,
25
+ startPoint: [NaN, NaN],
26
+ isDrag: false,
27
+ }; }
15
28
  constructor(toolProps, defaultToolProps) {
16
29
  super(toolProps, defaultToolProps);
17
- this._previewData = {
18
- preview: null,
19
- element: null,
20
- timerStart: 0,
21
- timer: null,
22
- startPoint: [NaN, NaN],
23
- isDrag: false,
24
- };
30
+ }
31
+ get _previewData() {
32
+ return LabelmapBaseTool.previewData;
25
33
  }
26
34
  createMemo(segmentId, segmentationVoxelManager, preview) {
27
35
  this.memo ||= LabelmapMemo.createLabelmapMemo(segmentId, segmentationVoxelManager, preview);
@@ -175,7 +183,7 @@ export default class LabelmapBaseTool extends BaseTool {
175
183
  points: data?.handles?.points,
176
184
  segmentIndex,
177
185
  previewColors: this.configuration.preview?.enabled || this._previewData.preview
178
- ? this.configuration.preview.previewColors
186
+ ? this.configuration.preview?.previewColors
179
187
  : null,
180
188
  viewPlaneNormal,
181
189
  toolGroupId: this.toolGroupId,
@@ -188,6 +196,7 @@ export default class LabelmapBaseTool extends BaseTool {
188
196
  return operationData;
189
197
  }
190
198
  addPreview(element = this._previewData.element, options) {
199
+ const { _previewData } = this;
191
200
  const acceptReject = options?.acceptReject;
192
201
  if (acceptReject === true) {
193
202
  this.acceptPreview(element);
@@ -196,9 +205,9 @@ export default class LabelmapBaseTool extends BaseTool {
196
205
  this.rejectPreview(element);
197
206
  }
198
207
  const enabledElement = getEnabledElement(element);
199
- this._previewData.preview = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
200
- this._previewData.isDrag = true;
201
- return this._previewData.preview;
208
+ _previewData.preview = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
209
+ _previewData.isDrag = true;
210
+ return _previewData.preview;
202
211
  }
203
212
  rejectPreview(element = this._previewData.element) {
204
213
  if (!element || !this._previewData.preview) {
@@ -220,4 +229,90 @@ export default class LabelmapBaseTool extends BaseTool {
220
229
  this._previewData.preview = null;
221
230
  this.doneEditMemo();
222
231
  }
232
+ static viewportContoursToLabelmap(viewport, options) {
233
+ const removeContours = options?.removeContours ?? true;
234
+ const annotations = getAllAnnotations();
235
+ const viewAnnotations = filterAnnotationsForDisplay(viewport, annotations);
236
+ if (!viewAnnotations?.length) {
237
+ return;
238
+ }
239
+ const contourAnnotations = viewAnnotations.filter((annotation) => annotation.data.contour?.polyline?.length);
240
+ if (!contourAnnotations.length) {
241
+ return;
242
+ }
243
+ const brushInstance = new LabelmapBaseTool({}, {
244
+ configuration: {
245
+ strategies: {
246
+ FILL_INSIDE_CIRCLE: fillInsideCircle,
247
+ },
248
+ activeStrategy: 'FILL_INSIDE_CIRCLE',
249
+ },
250
+ });
251
+ const preview = brushInstance.addPreview(viewport.element);
252
+ const { memo, segmentationId } = preview;
253
+ const previewVoxels = memo?.voxelManager || preview.previewVoxelManager;
254
+ const segmentationVoxels = previewVoxels.sourceVoxelManager || previewVoxels;
255
+ const { dimensions } = previewVoxels;
256
+ const imageData = viewport
257
+ .getDefaultActor()
258
+ .actor.getMapper()
259
+ .getInputData();
260
+ for (const annotation of contourAnnotations) {
261
+ const boundsIJK = [
262
+ [Infinity, -Infinity],
263
+ [Infinity, -Infinity],
264
+ [Infinity, -Infinity],
265
+ ];
266
+ const { polyline } = annotation.data.contour;
267
+ for (const point of polyline) {
268
+ const indexPoint = imageData.worldToIndex(point);
269
+ indexPoint.forEach((v, idx) => {
270
+ boundsIJK[idx][0] = Math.min(boundsIJK[idx][0], v);
271
+ boundsIJK[idx][1] = Math.max(boundsIJK[idx][1], v);
272
+ });
273
+ }
274
+ boundsIJK.forEach((bound, idx) => {
275
+ bound[0] = Math.round(Math.max(0, bound[0]));
276
+ bound[1] = Math.round(Math.min(dimensions[idx] - 1, bound[1]));
277
+ });
278
+ const activeIndex = getActiveSegmentIndex(segmentationId);
279
+ const startPoint = annotation.data.handles?.[0] || polyline[0];
280
+ const startIndex = imageData.worldToIndex(startPoint).map(Math.round);
281
+ const startValue = segmentationVoxels.getAtIJKPoint(startIndex) || 0;
282
+ let hasZeroIndex = false;
283
+ let hasPositiveIndex = false;
284
+ for (const polyPoint of polyline) {
285
+ const polyIndex = imageData.worldToIndex(polyPoint).map(Math.round);
286
+ const polyValue = segmentationVoxels.getAtIJKPoint(polyIndex);
287
+ if (polyValue === startValue) {
288
+ hasZeroIndex = true;
289
+ }
290
+ else if (polyValue >= 0) {
291
+ hasPositiveIndex = true;
292
+ }
293
+ }
294
+ const hasBoth = hasZeroIndex && hasPositiveIndex;
295
+ const segmentIndex = hasBoth
296
+ ? startValue
297
+ : startValue === 0
298
+ ? activeIndex
299
+ : 0;
300
+ for (let i = boundsIJK[0][0]; i <= boundsIJK[0][1]; i++) {
301
+ for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
302
+ for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
303
+ const worldPoint = imageData.indexToWorld([i, j, k]);
304
+ const isContained = isPointInsidePolyline3D(worldPoint, polyline);
305
+ if (isContained) {
306
+ previewVoxels.setAtIJK(i, j, k, segmentIndex);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ if (removeContours) {
312
+ removeAnnotation(annotation.annotationUID);
313
+ }
314
+ }
315
+ const slices = previewVoxels.getArrayOfModifiedSlices();
316
+ triggerSegmentationDataModified(segmentationId, slices);
317
+ }
223
318
  }
@@ -1,7 +1,7 @@
1
1
  import determineSegmentIndex from './determineSegmentIndex';
2
2
  import dynamicThreshold from './dynamicThreshold';
3
3
  import erase from './erase';
4
- import islandRemoval from './islandRemoval';
4
+ import islandRemoval from './islandRemovalComposition';
5
5
  import preview from './preview';
6
6
  import regionFill from './regionFill';
7
7
  import setValue from './setValue';
@@ -0,0 +1,5 @@
1
+ import type { InitializedOperationData } from '../BrushStrategy';
2
+ declare const _default: {
3
+ onInteractionEnd: (operationData: InitializedOperationData) => void;
4
+ };
5
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { triggerSegmentationDataModified } from '../../../../stateManagement/segmentation/triggerSegmentationEvents';
2
+ import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
3
+ import IslandRemoval from '../../../../utilities/segmentation/islandRemoval';
4
+ export default {
5
+ [StrategyCallbacks.OnInteractionEnd]: (operationData) => {
6
+ const { strategySpecificConfiguration, previewSegmentIndex, segmentIndex, viewport, previewVoxelManager, segmentationVoxelManager, } = operationData;
7
+ if (!strategySpecificConfiguration.THRESHOLD || segmentIndex === null) {
8
+ return;
9
+ }
10
+ const islandRemoval = new IslandRemoval();
11
+ const voxelManager = previewVoxelManager ?? segmentationVoxelManager;
12
+ if (!islandRemoval.initialize(viewport, voxelManager, {
13
+ previewSegmentIndex,
14
+ segmentIndex,
15
+ })) {
16
+ return;
17
+ }
18
+ islandRemoval.floodFillSegmentIsland();
19
+ islandRemoval.removeExternalIslands();
20
+ islandRemoval.removeInternalIslands();
21
+ const arrayOfSlices = voxelManager.getArrayOfModifiedSlices();
22
+ if (!arrayOfSlices) {
23
+ return;
24
+ }
25
+ triggerSegmentationDataModified(operationData.segmentationId, arrayOfSlices, previewSegmentIndex);
26
+ },
27
+ };
@@ -2,6 +2,11 @@ import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
2
2
  import VolumetricCalculator from '../../../../utilities/segmentation/VolumetricCalculator';
3
3
  import { getActiveSegmentIndex } from '../../../../stateManagement/segmentation/getActiveSegmentIndex';
4
4
  import { getStrategyData } from '../utils/getStrategyData';
5
+ import { utilities } from '@cornerstonejs/core';
6
+ import { getPixelValueUnits } from '../../../../utilities/getPixelValueUnits';
7
+ import { AnnotationTool } from '../../../base';
8
+ import { isViewportPreScaled } from '../../../../utilities/viewport/isViewportPreScaled';
9
+ const radiusForVol1 = Math.pow((3 * 1000) / (4 * Math.PI), 1 / 3);
5
10
  export default {
6
11
  [StrategyCallbacks.GetStatistics]: function (enabledElement, operationData, options) {
7
12
  const { viewport } = enabledElement;
@@ -29,8 +34,70 @@ export default {
29
34
  return;
30
35
  }
31
36
  const imageValue = imageVoxelManager.getAtIJKPoint(pointIJK);
32
- VolumetricCalculator.statsCallback({ value: imageValue });
37
+ VolumetricCalculator.statsCallback({ value: imageValue, pointIJK });
33
38
  });
34
- return VolumetricCalculator.getStatistics({ spacing });
39
+ const targetId = viewport.getReferenceId();
40
+ const modalityUnitOptions = {
41
+ isPreScaled: isViewportPreScaled(viewport, targetId),
42
+ isSuvScaled: AnnotationTool.isSuvScaled(viewport, targetId, viewport.getCurrentImageId()),
43
+ };
44
+ const imageData = viewport.getImageData();
45
+ const unit = getPixelValueUnits(imageData.metadata.Modality, viewport.getCurrentImageId(), modalityUnitOptions);
46
+ const stats = VolumetricCalculator.getStatistics({ spacing, unit });
47
+ const { maxIJKs } = stats;
48
+ if (!maxIJKs?.length) {
49
+ return stats;
50
+ }
51
+ stats.mean.unit = unit;
52
+ stats.max.unit = unit;
53
+ stats.min.unit = unit;
54
+ if (unit !== 'SUV') {
55
+ return;
56
+ }
57
+ const radiusIJK = spacing.map((s) => Math.max(1, Math.round((1.1 * radiusForVol1) / s)));
58
+ for (const testMax of maxIJKs) {
59
+ const testStats = getSphereStats(testMax, radiusIJK, segmentationImageData, imageVoxelManager, spacing);
60
+ if (!testStats) {
61
+ continue;
62
+ }
63
+ const { mean } = testStats;
64
+ if (!stats.peakValue || stats.peakValue.value <= mean.value) {
65
+ stats.peakValue = {
66
+ name: 'peakValue',
67
+ label: 'Peak Value',
68
+ value: mean.value,
69
+ unit,
70
+ };
71
+ }
72
+ }
73
+ return stats;
35
74
  },
36
75
  };
76
+ function getSphereStats(testMax, radiusIJK, segData, imageVoxels, spacing) {
77
+ const { pointIJK: centerIJK } = testMax;
78
+ const boundsIJK = centerIJK.map((ijk, idx) => [
79
+ ijk - radiusIJK[idx],
80
+ ijk + radiusIJK[idx],
81
+ ]);
82
+ const testFunction = (_pointLPS, pointIJK) => {
83
+ const i = (pointIJK[0] - centerIJK[0]) / radiusIJK[0];
84
+ const j = (pointIJK[1] - centerIJK[1]) / radiusIJK[1];
85
+ const k = (pointIJK[2] - centerIJK[2]) / radiusIJK[2];
86
+ const radius = i * i + j * j + k * k;
87
+ return radius <= 1;
88
+ };
89
+ const statsFunction = ({ pointIJK, pointLPS }) => {
90
+ const value = imageVoxels.getAtIJKPoint(pointIJK);
91
+ if (value === undefined) {
92
+ return;
93
+ }
94
+ VolumetricCalculator.statsCallback({ value, pointLPS, pointIJK });
95
+ };
96
+ VolumetricCalculator.statsInit({ storePointData: false });
97
+ utilities.pointInShapeCallback(segData, {
98
+ pointInShapeFn: testFunction,
99
+ callback: statsFunction,
100
+ boundsIJK,
101
+ });
102
+ return VolumetricCalculator.getStatistics({ spacing });
103
+ }
@@ -31,6 +31,10 @@ type NamedStatistics = {
31
31
  name: 'circumference';
32
32
  };
33
33
  pointsInShape?: Types.IPointsManager<Types.Point3>;
34
+ maxIJKs?: Array<{
35
+ value: number;
36
+ pointIJK: Types.Point3;
37
+ }>;
34
38
  array: Statistics[];
35
39
  };
36
40
  export type { Statistics, NamedStatistics };
@@ -11,8 +11,11 @@ export function addContourSegmentationAnnotation(annotation) {
11
11
  if (!segmentation.representationData.Contour) {
12
12
  segmentation.representationData.Contour = { annotationUIDsMap: new Map() };
13
13
  }
14
- const { annotationUIDsMap } = segmentation.representationData.Contour;
15
- let annotationsUIDsSet = annotationUIDsMap.get(segmentIndex);
14
+ let { annotationUIDsMap } = segmentation.representationData.Contour;
15
+ if (!annotationUIDsMap) {
16
+ annotationUIDsMap = new Map();
17
+ }
18
+ let annotationsUIDsSet = annotationUIDsMap?.get(segmentIndex);
16
19
  if (!annotationsUIDsSet) {
17
20
  annotationsUIDsSet = new Set();
18
21
  annotationUIDsMap.set(segmentIndex, annotationsUIDsSet);
@@ -33,4 +33,6 @@ import * as voi from './voi';
33
33
  import * as contourSegmentation from './contourSegmentation';
34
34
  import { pointInSurroundingSphereCallback } from './pointInSurroundingSphereCallback';
35
35
  declare const roundNumber: typeof utilities.roundNumber;
36
- export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, };
36
+ import normalizeViewportPlane from './normalizeViewportPlane';
37
+ import IslandRemoval from './segmentation/islandRemoval';
38
+ export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, normalizeViewportPlane, IslandRemoval, };
@@ -33,4 +33,6 @@ import * as voi from './voi';
33
33
  import * as contourSegmentation from './contourSegmentation';
34
34
  import { pointInSurroundingSphereCallback } from './pointInSurroundingSphereCallback';
35
35
  const roundNumber = utilities.roundNumber;
36
- export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, };
36
+ import normalizeViewportPlane from './normalizeViewportPlane';
37
+ import IslandRemoval from './segmentation/islandRemoval';
38
+ export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, normalizeViewportPlane, IslandRemoval, };
@@ -27,14 +27,14 @@ export default function filterAnnotationsWithinSlice(annotations, camera, spacin
27
27
  const annotationsWithinSlice = [];
28
28
  for (const annotation of annotationsWithParallelNormals) {
29
29
  const data = annotation.data;
30
- const point = data.handles.points[0];
30
+ const point = data.handles.points[0] || data.contour?.polyline[0];
31
31
  if (!annotation.isVisible) {
32
32
  continue;
33
33
  }
34
34
  const dir = vec3.create();
35
35
  if (!point) {
36
36
  annotationsWithinSlice.push(annotation);
37
- return;
37
+ continue;
38
38
  }
39
39
  vec3.sub(dir, focalPoint, point);
40
40
  const dot = vec3.dot(dir, viewPlaneNormal);
@@ -1,8 +1,15 @@
1
+ import type { Types } from '@cornerstonejs/core';
1
2
  import type { NamedStatistics } from '../../types';
2
3
  import { BasicStatsCalculator } from '../math/basic';
3
4
  export default class VolumetricCalculator extends BasicStatsCalculator {
5
+ private static maxIJKs;
4
6
  static getStatistics(options: {
5
7
  spacing?: number;
6
8
  unit?: string;
7
9
  }): NamedStatistics;
10
+ static statsCallback(data: {
11
+ value: number | Types.RGB;
12
+ pointLPS?: Types.Point3;
13
+ pointIJK?: Types.Point3;
14
+ }): void;
8
15
  }