@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,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
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { utilities } from '@cornerstonejs/core';
|
|
2
|
+
import normalizeViewportPlane from '../normalizeViewportPlane';
|
|
3
|
+
const { RLEVoxelMap, VoxelManager } = utilities;
|
|
4
|
+
const MAX_IMAGE_SIZE = 65535;
|
|
5
|
+
export var SegmentationEnum;
|
|
6
|
+
(function (SegmentationEnum) {
|
|
7
|
+
SegmentationEnum[SegmentationEnum["SEGMENT"] = -1] = "SEGMENT";
|
|
8
|
+
SegmentationEnum[SegmentationEnum["ISLAND"] = -2] = "ISLAND";
|
|
9
|
+
SegmentationEnum[SegmentationEnum["INTERIOR"] = -3] = "INTERIOR";
|
|
10
|
+
SegmentationEnum[SegmentationEnum["EXTERIOR"] = -4] = "EXTERIOR";
|
|
11
|
+
SegmentationEnum[SegmentationEnum["INTERIOR_SMALL"] = -5] = "INTERIOR_SMALL";
|
|
12
|
+
SegmentationEnum[SegmentationEnum["INTERIOR_TEST"] = -6] = "INTERIOR_TEST";
|
|
13
|
+
})(SegmentationEnum || (SegmentationEnum = {}));
|
|
14
|
+
export default class IslandRemoval {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.fillInternalEdge = false;
|
|
17
|
+
this.maxInternalRemove = 128;
|
|
18
|
+
this.maxInternalRemove =
|
|
19
|
+
options?.maxInternalRemove ?? this.maxInternalRemove;
|
|
20
|
+
this.fillInternalEdge = options?.fillInternalEdge ?? this.fillInternalEdge;
|
|
21
|
+
}
|
|
22
|
+
initialize(viewport, segmentationVoxels, options) {
|
|
23
|
+
const hasSource = !!segmentationVoxels.sourceVoxelManager;
|
|
24
|
+
const segmentationVoxelManager = hasSource
|
|
25
|
+
? segmentationVoxels.sourceVoxelManager
|
|
26
|
+
: segmentationVoxels;
|
|
27
|
+
const previewVoxelManager = hasSource
|
|
28
|
+
? segmentationVoxels
|
|
29
|
+
: VoxelManager.createRLEHistoryVoxelManager(segmentationVoxelManager);
|
|
30
|
+
const { segmentIndex = 1, previewSegmentIndex = 1 } = options;
|
|
31
|
+
const clickedPoints = options.points || previewVoxelManager.getPoints();
|
|
32
|
+
if (!clickedPoints?.length) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const boundsIJK = previewVoxelManager
|
|
36
|
+
.getBoundsIJK()
|
|
37
|
+
.map((bound, i) => [
|
|
38
|
+
Math.min(bound[0], ...clickedPoints.map((point) => point[i])),
|
|
39
|
+
Math.max(bound[1], ...clickedPoints.map((point) => point[i])),
|
|
40
|
+
]);
|
|
41
|
+
if (boundsIJK.find((it) => it[0] < 0 || it[1] > MAX_IMAGE_SIZE)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { toIJK, fromIJK, boundsIJKPrime, error } = normalizeViewportPlane(viewport, boundsIJK);
|
|
45
|
+
if (error) {
|
|
46
|
+
console.warn('Not performing island removal for planes not orthogonal to acquisition plane', error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const [width, height, depth] = fromIJK(segmentationVoxelManager.dimensions);
|
|
50
|
+
const segmentSet = new RLEVoxelMap(width, height, depth);
|
|
51
|
+
const getter = (i, j, k) => {
|
|
52
|
+
const index = segmentationVoxelManager.toIndex(toIJK([i, j, k]));
|
|
53
|
+
const oldVal = segmentationVoxelManager.getAtIndex(index);
|
|
54
|
+
if (oldVal === previewSegmentIndex || oldVal === segmentIndex) {
|
|
55
|
+
return SegmentationEnum.SEGMENT;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
segmentSet.fillFrom(getter, boundsIJKPrime);
|
|
59
|
+
segmentSet.normalizer = { toIJK, fromIJK, boundsIJKPrime };
|
|
60
|
+
this.segmentSet = segmentSet;
|
|
61
|
+
this.previewVoxelManager = previewVoxelManager;
|
|
62
|
+
this.segmentIndex = segmentIndex;
|
|
63
|
+
this.previewSegmentIndex = previewSegmentIndex ?? segmentIndex;
|
|
64
|
+
this.selectedPoints = clickedPoints;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
floodFillSegmentIsland() {
|
|
68
|
+
const { selectedPoints: clickedPoints, segmentSet } = this;
|
|
69
|
+
let floodedCount = 0;
|
|
70
|
+
const { fromIJK } = segmentSet.normalizer;
|
|
71
|
+
clickedPoints.forEach((clickedPoint) => {
|
|
72
|
+
const ijkPrime = fromIJK(clickedPoint);
|
|
73
|
+
const index = segmentSet.toIndex(ijkPrime);
|
|
74
|
+
const [iPrime, jPrime, kPrime] = ijkPrime;
|
|
75
|
+
if (segmentSet.get(index) === SegmentationEnum.SEGMENT) {
|
|
76
|
+
floodedCount += segmentSet.floodFill(iPrime, jPrime, kPrime, SegmentationEnum.ISLAND);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return floodedCount;
|
|
80
|
+
}
|
|
81
|
+
removeExternalIslands() {
|
|
82
|
+
const { previewVoxelManager, segmentSet } = this;
|
|
83
|
+
const { toIJK } = segmentSet.normalizer;
|
|
84
|
+
const callback = (index, rle) => {
|
|
85
|
+
const [, jPrime, kPrime] = segmentSet.toIJK(index);
|
|
86
|
+
if (rle.value !== SegmentationEnum.ISLAND) {
|
|
87
|
+
for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
|
|
88
|
+
const clearPoint = toIJK([iPrime, jPrime, kPrime]);
|
|
89
|
+
const v = previewVoxelManager.getAtIJKPoint(clearPoint);
|
|
90
|
+
previewVoxelManager.setAtIJKPoint(clearPoint, v === undefined ? 0 : null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
segmentSet.forEach(callback, { rowModified: true });
|
|
95
|
+
}
|
|
96
|
+
removeInternalIslands() {
|
|
97
|
+
const { segmentSet, previewVoxelManager, previewSegmentIndex } = this;
|
|
98
|
+
const { height, normalizer, width } = segmentSet;
|
|
99
|
+
const { toIJK } = normalizer;
|
|
100
|
+
segmentSet.forEachRow((baseIndex, row) => {
|
|
101
|
+
let lastRle;
|
|
102
|
+
for (const rle of [...row]) {
|
|
103
|
+
if (rle.value !== SegmentationEnum.ISLAND) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!lastRle) {
|
|
107
|
+
if (this.fillInternalEdge && rle.start > 0) {
|
|
108
|
+
for (let iPrime = 0; iPrime < rle.start; iPrime++) {
|
|
109
|
+
segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
lastRle = rle;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (let iPrime = lastRle.end; iPrime < rle.start; iPrime++) {
|
|
116
|
+
segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
|
|
117
|
+
}
|
|
118
|
+
lastRle = rle;
|
|
119
|
+
}
|
|
120
|
+
if (this.fillInternalEdge && lastRle?.end < width) {
|
|
121
|
+
for (let iPrime = lastRle.end; iPrime < width; iPrime++) {
|
|
122
|
+
segmentSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
segmentSet.forEach((baseIndex, rle) => {
|
|
127
|
+
if (rle.value !== SegmentationEnum.INTERIOR) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const [, jPrime, kPrime] = segmentSet.toIJK(baseIndex);
|
|
131
|
+
const rowPrev = jPrime > 0 ? segmentSet.getRun(jPrime - 1, kPrime) : null;
|
|
132
|
+
const rowNext = jPrime + 1 < height ? segmentSet.getRun(jPrime + 1, kPrime) : null;
|
|
133
|
+
const isLast = jPrime === height - 1;
|
|
134
|
+
const isFirst = jPrime === 0;
|
|
135
|
+
const prevCovers = IslandRemoval.covers(rle, rowPrev) ||
|
|
136
|
+
(isFirst && this.fillInternalEdge);
|
|
137
|
+
const nextCovers = IslandRemoval.covers(rle, rowNext) || (isLast && this.fillInternalEdge);
|
|
138
|
+
if (rle.end - rle.start > 2 && (!prevCovers || !nextCovers)) {
|
|
139
|
+
segmentSet.floodFill(rle.start, jPrime, kPrime, SegmentationEnum.EXTERIOR, { singlePlane: true });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
segmentSet.forEach((baseIndex, rle) => {
|
|
143
|
+
if (rle.value !== SegmentationEnum.INTERIOR) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const [, jPrime, kPrime] = segmentSet.toIJK(baseIndex);
|
|
147
|
+
const size = segmentSet.floodFill(rle.start, jPrime, kPrime, SegmentationEnum.INTERIOR_TEST);
|
|
148
|
+
const isBig = size > this.maxInternalRemove;
|
|
149
|
+
const newType = isBig
|
|
150
|
+
? SegmentationEnum.EXTERIOR
|
|
151
|
+
: SegmentationEnum.INTERIOR_SMALL;
|
|
152
|
+
segmentSet.floodFill(rle.start, jPrime, kPrime, newType);
|
|
153
|
+
});
|
|
154
|
+
segmentSet.forEach((baseIndex, rle) => {
|
|
155
|
+
if (rle.value !== SegmentationEnum.INTERIOR_SMALL) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
|
|
159
|
+
const clearPoint = toIJK(segmentSet.toIJK(baseIndex + iPrime));
|
|
160
|
+
previewVoxelManager.setAtIJKPoint(clearPoint, previewSegmentIndex);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return previewVoxelManager.getArrayOfModifiedSlices();
|
|
164
|
+
}
|
|
165
|
+
static covers(rle, row) {
|
|
166
|
+
if (!row) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
let { start } = rle;
|
|
170
|
+
const { end } = rle;
|
|
171
|
+
for (const rowRle of row) {
|
|
172
|
+
if (start >= rowRle.start && start < rowRle.end) {
|
|
173
|
+
start = rowRle.end;
|
|
174
|
+
if (start >= end) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"canvas": "^2.11.2"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@cornerstonejs/core": "^2.
|
|
107
|
+
"@cornerstonejs/core": "^2.9.0",
|
|
108
108
|
"@kitware/vtk.js": "32.1.1",
|
|
109
109
|
"@types/d3-array": "^3.0.4",
|
|
110
110
|
"@types/d3-interpolate": "^3.0.1",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"type": "individual",
|
|
124
124
|
"url": "https://ohif.org/donate"
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "06611b57ab0630cae3487040cc3034d544d63a98"
|
|
127
127
|
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { utilities } from '@cornerstonejs/core';
|
|
2
|
-
import type { InitializedOperationData } from '../BrushStrategy';
|
|
3
|
-
export declare enum SegmentationEnum {
|
|
4
|
-
SEGMENT = 1,
|
|
5
|
-
ISLAND = 2,
|
|
6
|
-
INTERIOR = 3,
|
|
7
|
-
EXTERIOR = 4,
|
|
8
|
-
INTERIOR_SMALL = -5,
|
|
9
|
-
INTERIOR_TEST = -6
|
|
10
|
-
}
|
|
11
|
-
declare const _default: {
|
|
12
|
-
onInteractionEnd: (operationData: InitializedOperationData) => void;
|
|
13
|
-
};
|
|
14
|
-
export default _default;
|
|
15
|
-
export declare function createSegmentSet(operationData: InitializedOperationData): utilities.RLEVoxelMap<SegmentationEnum>;
|
|
16
|
-
export declare function covers(rle: any, row: any): boolean;
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { utilities } from '@cornerstonejs/core';
|
|
2
|
-
import { triggerSegmentationDataModified } from '../../../../stateManagement/segmentation/triggerSegmentationEvents';
|
|
3
|
-
import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
|
|
4
|
-
import normalizeViewportPlane from '../utils/normalizeViewportPlane';
|
|
5
|
-
const { RLEVoxelMap } = utilities;
|
|
6
|
-
const MAX_IMAGE_SIZE = 65535;
|
|
7
|
-
export var SegmentationEnum;
|
|
8
|
-
(function (SegmentationEnum) {
|
|
9
|
-
SegmentationEnum[SegmentationEnum["SEGMENT"] = 1] = "SEGMENT";
|
|
10
|
-
SegmentationEnum[SegmentationEnum["ISLAND"] = 2] = "ISLAND";
|
|
11
|
-
SegmentationEnum[SegmentationEnum["INTERIOR"] = 3] = "INTERIOR";
|
|
12
|
-
SegmentationEnum[SegmentationEnum["EXTERIOR"] = 4] = "EXTERIOR";
|
|
13
|
-
SegmentationEnum[SegmentationEnum["INTERIOR_SMALL"] = -5] = "INTERIOR_SMALL";
|
|
14
|
-
SegmentationEnum[SegmentationEnum["INTERIOR_TEST"] = -6] = "INTERIOR_TEST";
|
|
15
|
-
})(SegmentationEnum || (SegmentationEnum = {}));
|
|
16
|
-
export default {
|
|
17
|
-
[StrategyCallbacks.OnInteractionEnd]: (operationData) => {
|
|
18
|
-
const { strategySpecificConfiguration, previewSegmentIndex, segmentIndex } = operationData;
|
|
19
|
-
if (!strategySpecificConfiguration.THRESHOLD ||
|
|
20
|
-
segmentIndex === null ||
|
|
21
|
-
previewSegmentIndex === undefined) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const segmentSet = createSegmentSet(operationData);
|
|
25
|
-
if (!segmentSet) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const externalRemoved = removeExternalIslands(operationData, segmentSet);
|
|
29
|
-
if (externalRemoved === undefined) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const arrayOfSlices = removeInternalIslands(operationData, segmentSet);
|
|
33
|
-
if (!arrayOfSlices) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
triggerSegmentationDataModified(operationData.segmentationId, arrayOfSlices, previewSegmentIndex);
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
export function createSegmentSet(operationData) {
|
|
40
|
-
const { segmentationVoxelManager, previewSegmentIndex, previewVoxelManager, segmentIndex, viewport, } = operationData;
|
|
41
|
-
const clickedPoints = previewVoxelManager.getPoints();
|
|
42
|
-
if (!clickedPoints?.length) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const boundsIJK = previewVoxelManager
|
|
46
|
-
.getBoundsIJK()
|
|
47
|
-
.map((bound, i) => [
|
|
48
|
-
Math.min(bound[0], ...clickedPoints.map((point) => point[i])),
|
|
49
|
-
Math.max(bound[1], ...clickedPoints.map((point) => point[i])),
|
|
50
|
-
]);
|
|
51
|
-
if (boundsIJK.find((it) => it[0] < 0 || it[1] > MAX_IMAGE_SIZE)) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const { toIJK, fromIJK, boundsIJKPrime, error } = normalizeViewportPlane(viewport, boundsIJK);
|
|
55
|
-
if (error) {
|
|
56
|
-
console.warn('Not performing island removal for planes not orthogonal to acquisition plane', error);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const [width, height, depth] = fromIJK(segmentationVoxelManager.dimensions);
|
|
60
|
-
const floodedSet = new RLEVoxelMap(width, height, depth);
|
|
61
|
-
const getter = (i, j, k) => {
|
|
62
|
-
const index = segmentationVoxelManager.toIndex(toIJK([i, j, k]));
|
|
63
|
-
const oldVal = segmentationVoxelManager.getAtIndex(index);
|
|
64
|
-
if (oldVal === previewSegmentIndex || oldVal === segmentIndex) {
|
|
65
|
-
return SegmentationEnum.SEGMENT;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
floodedSet.fillFrom(getter, boundsIJKPrime);
|
|
69
|
-
floodedSet.normalizer = { toIJK, fromIJK, boundsIJKPrime };
|
|
70
|
-
return floodedSet;
|
|
71
|
-
}
|
|
72
|
-
function removeInternalIslands(operationData, floodedSet) {
|
|
73
|
-
const { height, normalizer } = floodedSet;
|
|
74
|
-
const { toIJK } = normalizer;
|
|
75
|
-
const { previewVoxelManager, previewSegmentIndex } = operationData;
|
|
76
|
-
floodedSet.forEachRow((baseIndex, row) => {
|
|
77
|
-
let lastRle;
|
|
78
|
-
for (const rle of [...row]) {
|
|
79
|
-
if (rle.value !== SegmentationEnum.ISLAND) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (!lastRle) {
|
|
83
|
-
lastRle = rle;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
for (let iPrime = lastRle.end; iPrime < rle.start; iPrime++) {
|
|
87
|
-
floodedSet.set(baseIndex + iPrime, SegmentationEnum.INTERIOR);
|
|
88
|
-
}
|
|
89
|
-
lastRle = rle;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
floodedSet.forEach((baseIndex, rle) => {
|
|
93
|
-
if (rle.value !== SegmentationEnum.INTERIOR) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const [, jPrime, kPrime] = floodedSet.toIJK(baseIndex);
|
|
97
|
-
const rowPrev = jPrime > 0 ? floodedSet.getRun(jPrime - 1, kPrime) : null;
|
|
98
|
-
const rowNext = jPrime + 1 < height ? floodedSet.getRun(jPrime + 1, kPrime) : null;
|
|
99
|
-
const prevCovers = covers(rle, rowPrev);
|
|
100
|
-
const nextCovers = covers(rle, rowNext);
|
|
101
|
-
if (rle.end - rle.start > 2 && (!prevCovers || !nextCovers)) {
|
|
102
|
-
floodedSet.floodFill(rle.start, jPrime, kPrime, SegmentationEnum.EXTERIOR, { singlePlane: true });
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
floodedSet.forEach((baseIndex, rle) => {
|
|
106
|
-
if (rle.value !== SegmentationEnum.INTERIOR) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
|
|
110
|
-
const clearPoint = toIJK(floodedSet.toIJK(baseIndex + iPrime));
|
|
111
|
-
previewVoxelManager.setAtIJKPoint(clearPoint, previewSegmentIndex);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
return previewVoxelManager.getArrayOfModifiedSlices();
|
|
115
|
-
}
|
|
116
|
-
function removeExternalIslands(operationData, floodedSet) {
|
|
117
|
-
const { previewVoxelManager } = operationData;
|
|
118
|
-
const { toIJK, fromIJK } = floodedSet.normalizer;
|
|
119
|
-
const clickedPoints = previewVoxelManager.getPoints();
|
|
120
|
-
let floodedCount = 0;
|
|
121
|
-
clickedPoints.forEach((clickedPoint) => {
|
|
122
|
-
const ijkPrime = fromIJK(clickedPoint);
|
|
123
|
-
const index = floodedSet.toIndex(ijkPrime);
|
|
124
|
-
const [iPrime, jPrime, kPrime] = ijkPrime;
|
|
125
|
-
if (floodedSet.get(index) === SegmentationEnum.SEGMENT) {
|
|
126
|
-
floodedCount += floodedSet.floodFill(iPrime, jPrime, kPrime, SegmentationEnum.ISLAND);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
if (floodedCount === 0) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const callback = (index, rle) => {
|
|
133
|
-
const [, jPrime, kPrime] = floodedSet.toIJK(index);
|
|
134
|
-
if (rle.value !== SegmentationEnum.ISLAND) {
|
|
135
|
-
for (let iPrime = rle.start; iPrime < rle.end; iPrime++) {
|
|
136
|
-
const clearPoint = toIJK([iPrime, jPrime, kPrime]);
|
|
137
|
-
previewVoxelManager.setAtIJKPoint(clearPoint, null);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
floodedSet.forEach(callback, { rowModified: true });
|
|
142
|
-
return floodedCount;
|
|
143
|
-
}
|
|
144
|
-
export function covers(rle, row) {
|
|
145
|
-
if (!row) {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
let { start } = rle;
|
|
149
|
-
const { end } = rle;
|
|
150
|
-
for (const rowRle of row) {
|
|
151
|
-
if (start >= rowRle.start && start < rowRle.end) {
|
|
152
|
-
start = rowRle.end;
|
|
153
|
-
if (start >= end) {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return false;
|
|
159
|
-
}
|
/package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.d.ts
RENAMED
|
File without changes
|
/package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.js
RENAMED
|
File without changes
|