@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.
- package/dist/esm/eventDispatchers/shared/getToolsWithActionsForKeyboardEvents.js +1 -1
- package/dist/esm/eventDispatchers/shared/getToolsWithActionsForMouseEvent.js +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/activeSegmentation.d.ts +1 -1
- package/dist/esm/stateManagement/segmentation/activeSegmentation.js +1 -1
- package/dist/esm/stateManagement/segmentation/index.d.ts +3 -1
- package/dist/esm/stateManagement/segmentation/index.js +3 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.js +1 -1
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.d.ts +3 -2
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +27 -12
- package/dist/esm/tools/annotation/RegionSegmentTool.d.ts +1 -1
- package/dist/esm/tools/annotation/RegionSegmentTool.js +7 -8
- package/dist/esm/tools/annotation/SplineROITool.js +7 -1
- package/dist/esm/tools/annotation/WholeBodySegmentTool.d.ts +3 -2
- package/dist/esm/tools/annotation/WholeBodySegmentTool.js +40 -5
- package/dist/esm/tools/base/AnnotationTool.d.ts +2 -1
- package/dist/esm/tools/base/AnnotationTool.js +6 -3
- package/dist/esm/tools/base/ContourSegmentationBaseTool.d.ts +1 -0
- package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -0
- package/dist/esm/tools/base/GrowCutBaseTool.d.ts +25 -10
- package/dist/esm/tools/base/GrowCutBaseTool.js +124 -21
- package/dist/esm/tools/index.d.ts +3 -1
- package/dist/esm/tools/index.js +3 -1
- package/dist/esm/tools/segmentation/BrushTool.d.ts +0 -1
- package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +5 -1
- package/dist/esm/tools/segmentation/LabelmapBaseTool.js +107 -12
- package/dist/esm/tools/segmentation/strategies/compositions/index.js +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.d.ts +5 -0
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.js +27 -0
- package/dist/esm/tools/segmentation/strategies/compositions/labelmapStatistics.js +69 -2
- package/dist/esm/types/CalculatorTypes.d.ts +4 -0
- package/dist/esm/utilities/contourSegmentation/addContourSegmentationAnnotation.js +5 -2
- package/dist/esm/utilities/index.d.ts +3 -1
- package/dist/esm/utilities/index.js +3 -1
- package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +2 -2
- package/dist/esm/utilities/segmentation/VolumetricCalculator.d.ts +7 -0
- package/dist/esm/utilities/segmentation/VolumetricCalculator.js +28 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.d.ts +0 -2
- package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.d.ts +0 -4
- package/dist/esm/utilities/segmentation/index.d.ts +2 -1
- package/dist/esm/utilities/segmentation/index.js +2 -1
- package/dist/esm/utilities/segmentation/islandRemoval.d.ts +28 -0
- package/dist/esm/utilities/segmentation/islandRemoval.js +181 -0
- package/package.json +3 -3
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.d.ts +0 -16
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +0 -159
- /package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.d.ts +0 -0
- /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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
127
|
+
_runLastCommand({ shrinkExpandAmount = 0 } = {}) {
|
|
128
|
+
const cmd = GrowCutBaseTool.lastGrowCutCommand;
|
|
129
|
+
if (cmd) {
|
|
130
|
+
cmd({ shrinkExpandAmount });
|
|
131
|
+
}
|
|
81
132
|
}
|
|
82
|
-
getLabelmapSegmentationData(viewport) {
|
|
83
|
-
const
|
|
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
|
-
|
|
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, };
|
package/dist/esm/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
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 './
|
|
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,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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|