@cornerstonejs/tools 2.8.6 → 2.10.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/internalAddSegmentationRepresentation.js +1 -0
- package/dist/esm/stateManagement/segmentation/polySeg/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.js +1 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +4 -2
- 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/displayTools/Labelmap/addLabelmapToElement.d.ts +5 -1
- package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.js +17 -3
- package/dist/esm/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.d.ts +10 -0
- package/dist/esm/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.js +123 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.d.ts +1 -0
- package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.js +17 -10
- 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/SegmentSelectTool.js +0 -1
- 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/types/SegmentationStateTypes.d.ts +3 -1
- 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/planar/getPointInLineOfSightWithCriteria.d.ts +5 -1
- package/dist/esm/utilities/planar/getPointInLineOfSightWithCriteria.js +40 -29
- package/dist/esm/utilities/planar/index.d.ts +3 -2
- package/dist/esm/utilities/planar/index.js +3 -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
|
@@ -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
|
}
|
|
@@ -7,7 +7,6 @@ import RepresentationTypes from '../../enums/SegmentationRepresentations';
|
|
|
7
7
|
import { setActiveSegmentIndex } from '../../stateManagement/segmentation/segmentIndex';
|
|
8
8
|
import { getHoveredContourSegmentationAnnotation, getSegmentIndexAtLabelmapBorder, getSegmentIndexAtWorldPoint, } from '../../utilities/segmentation';
|
|
9
9
|
import { state } from '../../store/state';
|
|
10
|
-
import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
|
|
11
10
|
class SegmentSelectTool extends BaseTool {
|
|
12
11
|
static { this.SelectMode = {
|
|
13
12
|
Inside: 'Inside',
|
|
@@ -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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Types } from '@cornerstonejs/core';
|
|
1
|
+
import type { Enums as coreEnums, Types } from '@cornerstonejs/core';
|
|
2
2
|
import type * as Enums from '../enums';
|
|
3
3
|
import type { ContourSegmentationData } from './ContourTypes';
|
|
4
4
|
import type { LabelmapSegmentationData } from './LabelmapTypes';
|
|
@@ -35,6 +35,7 @@ export type LabelmapRenderingConfig = {
|
|
|
35
35
|
cfun: vtkColorTransferFunction;
|
|
36
36
|
ofun: vtkPiecewiseFunction;
|
|
37
37
|
colorLUTIndex: number;
|
|
38
|
+
blendMode?: coreEnums.BlendModes;
|
|
38
39
|
};
|
|
39
40
|
export type ContourRenderingConfig = {};
|
|
40
41
|
export type SurfaceRenderingConfig = {};
|
|
@@ -89,6 +90,7 @@ export type RepresentationPublicInput = {
|
|
|
89
90
|
type?: Enums.SegmentationRepresentations;
|
|
90
91
|
config?: {
|
|
91
92
|
colorLUTOrIndex?: Types.ColorLUT | number;
|
|
93
|
+
blendMode?: coreEnums.BlendModes;
|
|
92
94
|
};
|
|
93
95
|
};
|
|
94
96
|
export {};
|
|
@@ -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,2 +1,6 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
|
-
export
|
|
2
|
+
export declare function getPointInLineOfSightWithCriteria(viewport: Types.IVolumeViewport, worldPos: Types.Point3, targetVolumeId: string, criteriaFunction: (intensity: number, point: Types.Point3) => Types.Point3, stepSize?: number): Types.Point3;
|
|
3
|
+
export declare function getPointsInLineOfSight(viewport: Types.IVolumeViewport, worldPos: Types.Point3, { targetVolumeId, stepSize }: {
|
|
4
|
+
targetVolumeId: string;
|
|
5
|
+
stepSize: number;
|
|
6
|
+
}): Types.Point3[];
|
|
@@ -1,38 +1,49 @@
|
|
|
1
|
-
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
|
|
2
1
|
import { utilities as csUtils } from '@cornerstonejs/core';
|
|
3
|
-
export
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const bounds = viewport.getBounds();
|
|
9
|
-
const xMin = bounds[0];
|
|
10
|
-
const xMax = bounds[1];
|
|
11
|
-
const vector = [0, 0, 0];
|
|
12
|
-
let point = [0, 0, 0];
|
|
13
|
-
vtkMath.subtract(worldPos, cameraPosition, vector);
|
|
2
|
+
export function getPointInLineOfSightWithCriteria(viewport, worldPos, targetVolumeId, criteriaFunction, stepSize = 0.25) {
|
|
3
|
+
const points = getPointsInLineOfSight(viewport, worldPos, {
|
|
4
|
+
targetVolumeId,
|
|
5
|
+
stepSize,
|
|
6
|
+
});
|
|
14
7
|
let pickedPoint;
|
|
15
|
-
for (
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (_inBounds(point, bounds)) {
|
|
21
|
-
const intensity = viewport.getIntensityFromWorld(point);
|
|
22
|
-
const pointToPick = criteriaFunction(intensity, point);
|
|
23
|
-
if (pointToPick) {
|
|
24
|
-
pickedPoint = pointToPick;
|
|
25
|
-
}
|
|
8
|
+
for (const point of points) {
|
|
9
|
+
const intensity = viewport.getIntensityFromWorld(point);
|
|
10
|
+
const pointToPick = criteriaFunction(intensity, point);
|
|
11
|
+
if (pointToPick) {
|
|
12
|
+
pickedPoint = pointToPick;
|
|
26
13
|
}
|
|
27
14
|
}
|
|
28
15
|
return pickedPoint;
|
|
29
16
|
}
|
|
17
|
+
export function getPointsInLineOfSight(viewport, worldPos, { targetVolumeId, stepSize }) {
|
|
18
|
+
const camera = viewport.getCamera();
|
|
19
|
+
const { viewPlaneNormal: normalDirection } = camera;
|
|
20
|
+
const { spacingInNormalDirection } = csUtils.getTargetVolumeAndSpacingInNormalDir(viewport, camera, targetVolumeId);
|
|
21
|
+
const step = spacingInNormalDirection * stepSize || 1;
|
|
22
|
+
const bounds = viewport.getBounds();
|
|
23
|
+
const points = [];
|
|
24
|
+
let currentPos = [...worldPos];
|
|
25
|
+
while (_inBounds(currentPos, bounds)) {
|
|
26
|
+
points.push([...currentPos]);
|
|
27
|
+
currentPos[0] += normalDirection[0] * step;
|
|
28
|
+
currentPos[1] += normalDirection[1] * step;
|
|
29
|
+
currentPos[2] += normalDirection[2] * step;
|
|
30
|
+
}
|
|
31
|
+
currentPos = [...worldPos];
|
|
32
|
+
while (_inBounds(currentPos, bounds)) {
|
|
33
|
+
points.push([...currentPos]);
|
|
34
|
+
currentPos[0] -= normalDirection[0] * step;
|
|
35
|
+
currentPos[1] -= normalDirection[1] * step;
|
|
36
|
+
currentPos[2] -= normalDirection[2] * step;
|
|
37
|
+
}
|
|
38
|
+
return points;
|
|
39
|
+
}
|
|
30
40
|
const _inBounds = function (point, bounds) {
|
|
31
41
|
const [xMin, xMax, yMin, yMax, zMin, zMax] = bounds;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
point[
|
|
35
|
-
point[1]
|
|
36
|
-
point[
|
|
37
|
-
point[2]
|
|
42
|
+
const padding = 10;
|
|
43
|
+
return (point[0] > xMin + padding &&
|
|
44
|
+
point[0] < xMax - padding &&
|
|
45
|
+
point[1] > yMin + padding &&
|
|
46
|
+
point[1] < yMax - padding &&
|
|
47
|
+
point[2] > zMin + padding &&
|
|
48
|
+
point[2] < zMax - padding);
|
|
38
49
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import filterAnnotationsWithinSlice from './filterAnnotationsWithinSlice';
|
|
2
2
|
import getWorldWidthAndHeightFromCorners from './getWorldWidthAndHeightFromCorners';
|
|
3
3
|
import filterAnnotationsForDisplay from './filterAnnotationsForDisplay';
|
|
4
|
-
import getPointInLineOfSightWithCriteria from './getPointInLineOfSightWithCriteria';
|
|
4
|
+
import { getPointInLineOfSightWithCriteria, getPointsInLineOfSight } from './getPointInLineOfSightWithCriteria';
|
|
5
5
|
import { isPlaneIntersectingAABB } from './isPlaneIntersectingAABB';
|
|
6
6
|
import { filterAnnotationsWithinSamePlane } from './filterAnnotationsWithinPlane';
|
|
7
7
|
declare const _default: {
|
|
@@ -11,6 +11,7 @@ declare const _default: {
|
|
|
11
11
|
getPointInLineOfSightWithCriteria: typeof getPointInLineOfSightWithCriteria;
|
|
12
12
|
isPlaneIntersectingAABB: (origin: any, normal: any, minX: any, minY: any, minZ: any, maxX: any, maxY: any, maxZ: any) => boolean;
|
|
13
13
|
filterAnnotationsWithinSamePlane: typeof filterAnnotationsWithinSamePlane;
|
|
14
|
+
getPointsInLineOfSight: typeof getPointsInLineOfSight;
|
|
14
15
|
};
|
|
15
16
|
export default _default;
|
|
16
|
-
export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, };
|
|
17
|
+
export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, getPointsInLineOfSight, };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import filterAnnotationsWithinSlice from './filterAnnotationsWithinSlice';
|
|
2
2
|
import getWorldWidthAndHeightFromCorners from './getWorldWidthAndHeightFromCorners';
|
|
3
3
|
import filterAnnotationsForDisplay from './filterAnnotationsForDisplay';
|
|
4
|
-
import getPointInLineOfSightWithCriteria from './getPointInLineOfSightWithCriteria';
|
|
4
|
+
import { getPointInLineOfSightWithCriteria, getPointsInLineOfSight, } from './getPointInLineOfSightWithCriteria';
|
|
5
5
|
import { isPlaneIntersectingAABB } from './isPlaneIntersectingAABB';
|
|
6
6
|
import { filterAnnotationsWithinSamePlane } from './filterAnnotationsWithinPlane';
|
|
7
7
|
export default {
|
|
@@ -11,5 +11,6 @@ export default {
|
|
|
11
11
|
getPointInLineOfSightWithCriteria,
|
|
12
12
|
isPlaneIntersectingAABB,
|
|
13
13
|
filterAnnotationsWithinSamePlane,
|
|
14
|
+
getPointsInLineOfSight,
|
|
14
15
|
};
|
|
15
|
-
export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, };
|
|
16
|
+
export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, getPointsInLineOfSight, };
|
|
@@ -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
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { BasicStatsCalculator } from '../math/basic';
|
|
2
|
+
const TEST_MAX_LOCATIONS = 10;
|
|
2
3
|
export default class VolumetricCalculator extends BasicStatsCalculator {
|
|
4
|
+
static { this.maxIJKs = []; }
|
|
3
5
|
static getStatistics(options) {
|
|
4
6
|
const { spacing } = options;
|
|
5
7
|
const stats = BasicStatsCalculator.getStatistics();
|
|
@@ -14,7 +16,33 @@ export default class VolumetricCalculator extends BasicStatsCalculator {
|
|
|
14
16
|
unit: volumeUnit,
|
|
15
17
|
name: 'volume',
|
|
16
18
|
};
|
|
19
|
+
stats.maxIJKs = this.maxIJKs;
|
|
17
20
|
stats.array.push(stats.volume);
|
|
21
|
+
this.maxIJKs = [];
|
|
18
22
|
return stats;
|
|
19
23
|
}
|
|
24
|
+
static statsCallback(data) {
|
|
25
|
+
BasicStatsCalculator.statsCallback(data);
|
|
26
|
+
const { value } = data;
|
|
27
|
+
const { maxIJKs } = this;
|
|
28
|
+
const { length } = maxIJKs;
|
|
29
|
+
if (typeof value !== 'number' ||
|
|
30
|
+
(length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!length || value >= maxIJKs[length - 1].value) {
|
|
34
|
+
maxIJKs.push(data);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
for (let i = 0; i < length; i++) {
|
|
38
|
+
if (value <= maxIJKs[i].value) {
|
|
39
|
+
maxIJKs.splice(i, 0, data);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (length >= TEST_MAX_LOCATIONS) {
|
|
45
|
+
maxIJKs.splice(0, 1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
20
48
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { GrowCutOptions } from './runGrowCut';
|
|
3
3
|
type GrowCutOneClickOptions = GrowCutOptions & {
|
|
4
|
-
positiveSeedValue?: number;
|
|
5
|
-
negativeSeedValue?: number;
|
|
6
|
-
positiveSeedVariance?: number;
|
|
7
|
-
negativeSeedVariance?: number;
|
|
8
4
|
subVolumePaddingPercentage?: number | [number, number, number];
|
|
9
5
|
subVolumeMinPadding?: number | [number, number, number];
|
|
10
6
|
};
|
|
@@ -19,4 +19,5 @@ import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegm
|
|
|
19
19
|
import { getBrushToolInstances } from './getBrushToolInstances';
|
|
20
20
|
import * as growCut from './growCut';
|
|
21
21
|
import * as LabelmapMemo from './createLabelmapMemo';
|
|
22
|
-
|
|
22
|
+
import IslandRemoval from './islandRemoval';
|
|
23
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, };
|
|
@@ -19,4 +19,5 @@ import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegm
|
|
|
19
19
|
import { getBrushToolInstances } from './getBrushToolInstances';
|
|
20
20
|
import * as growCut from './growCut';
|
|
21
21
|
import * as LabelmapMemo from './createLabelmapMemo';
|
|
22
|
-
|
|
22
|
+
import IslandRemoval from './islandRemoval';
|
|
23
|
+
export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export declare enum SegmentationEnum {
|
|
3
|
+
SEGMENT = -1,
|
|
4
|
+
ISLAND = -2,
|
|
5
|
+
INTERIOR = -3,
|
|
6
|
+
EXTERIOR = -4,
|
|
7
|
+
INTERIOR_SMALL = -5,
|
|
8
|
+
INTERIOR_TEST = -6
|
|
9
|
+
}
|
|
10
|
+
export default class IslandRemoval {
|
|
11
|
+
segmentSet: Types.RLEVoxelMap<SegmentationEnum>;
|
|
12
|
+
segmentIndex: number;
|
|
13
|
+
fillSegments: (index: number) => boolean;
|
|
14
|
+
previewVoxelManager: Types.VoxelManager<number>;
|
|
15
|
+
previewSegmentIndex: number;
|
|
16
|
+
selectedPoints: Types.Point3[];
|
|
17
|
+
private fillInternalEdge;
|
|
18
|
+
private maxInternalRemove;
|
|
19
|
+
constructor(options?: {
|
|
20
|
+
maxInternalRemove?: number;
|
|
21
|
+
fillInternalEdge?: boolean;
|
|
22
|
+
});
|
|
23
|
+
initialize(viewport: any, segmentationVoxels: any, options: any): boolean;
|
|
24
|
+
floodFillSegmentIsland(): number;
|
|
25
|
+
removeExternalIslands(): void;
|
|
26
|
+
removeInternalIslands(): number[];
|
|
27
|
+
static covers(rle: any, row: any): boolean;
|
|
28
|
+
}
|