@cornerstonejs/tools 2.0.0-beta.23 → 2.0.0-beta.24
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/eventListeners/segmentation/labelmap/onLabelmapSegmentationDataModified.js +27 -23
- package/dist/esm/eventListeners/segmentation/segmentationDataModifiedEventListener.js +2 -2
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +1 -2
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.js +59 -11
- package/dist/esm/stateManagement/segmentation/getStackSegmentationImageIdsForViewport.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/getStackSegmentationImageIdsForViewport.js +5 -0
- package/dist/esm/stateManagement/segmentation/segmentationState.d.ts +2 -1
- package/dist/esm/stateManagement/segmentation/segmentationState.js +2 -1
- package/dist/esm/tools/segmentation/BrushTool.d.ts +38 -0
- package/dist/esm/tools/segmentation/BrushTool.js +40 -7
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +10 -5
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -2
- package/dist/esm/tools/segmentation/strategies/compositions/setValue.js +6 -4
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +5 -14
- package/dist/esm/types/LabelmapToolOperationData.d.ts +5 -0
- package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +7 -7
- package/dist/esm/utilities/segmentation/getSegmentIndexAtLabelmapBorder.js +7 -3
- package/dist/esm/utilities/segmentation/isLineInSegment.js +3 -3
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import { cache, utilities as csUtils, VolumeViewport, getEnabledElementByViewportId, } from '@cornerstonejs/core';
|
|
1
|
+
import { cache, utilities as csUtils, VolumeViewport, getEnabledElementByViewportId, StackViewport, } from '@cornerstonejs/core';
|
|
2
2
|
import * as SegmentationState from '../../../stateManagement/segmentation/segmentationState';
|
|
3
3
|
const onLabelmapSegmentationDataModified = function (evt) {
|
|
4
4
|
const { segmentationId, modifiedSlicesToUse } = evt.detail;
|
|
5
|
-
let modifiedSlices = modifiedSlicesToUse;
|
|
6
5
|
const { representationData, type } = SegmentationState.getSegmentation(segmentationId);
|
|
7
|
-
const labelmapRepresentationData = representationData[type];
|
|
8
|
-
if ('stack' in labelmapRepresentationData &&
|
|
9
|
-
'volumeId' in labelmapRepresentationData) {
|
|
10
|
-
modifiedSlices = [];
|
|
11
|
-
}
|
|
12
|
-
if ('volumeId' in labelmapRepresentationData) {
|
|
13
|
-
performVolumeLabelmapUpdate({
|
|
14
|
-
modifiedSlicesToUse: modifiedSlices,
|
|
15
|
-
representationData,
|
|
16
|
-
type,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
6
|
const viewportIds = SegmentationState.getViewportIdsWithSegmentation(segmentationId);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
7
|
+
const hasVolumeViewport = viewportIds.some((viewportId) => {
|
|
8
|
+
const { viewport } = getEnabledElementByViewportId(viewportId);
|
|
9
|
+
return viewport instanceof VolumeViewport;
|
|
10
|
+
});
|
|
11
|
+
const hasStackViewport = viewportIds.some((viewportId) => {
|
|
12
|
+
const { viewport } = getEnabledElementByViewportId(viewportId);
|
|
13
|
+
return viewport instanceof StackViewport;
|
|
14
|
+
});
|
|
15
|
+
const hasBothStackAndVolume = hasVolumeViewport && hasStackViewport;
|
|
16
|
+
viewportIds.forEach((viewportId) => {
|
|
17
|
+
const { viewport } = getEnabledElementByViewportId(viewportId);
|
|
18
|
+
if (viewport instanceof VolumeViewport) {
|
|
19
|
+
performVolumeLabelmapUpdate({
|
|
20
|
+
modifiedSlicesToUse: hasBothStackAndVolume ? [] : modifiedSlicesToUse,
|
|
21
|
+
representationData,
|
|
22
|
+
type,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (viewport instanceof StackViewport) {
|
|
26
|
+
performStackLabelmapUpdate({
|
|
27
|
+
viewportIds,
|
|
28
|
+
segmentationId,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
28
32
|
};
|
|
29
33
|
function performVolumeLabelmapUpdate({ modifiedSlicesToUse, representationData, type, }) {
|
|
30
34
|
const segmentationVolume = cache.getVolume(representationData[type].volumeId);
|
|
@@ -46,7 +50,7 @@ function performVolumeLabelmapUpdate({ modifiedSlicesToUse, representationData,
|
|
|
46
50
|
});
|
|
47
51
|
imageData.modified();
|
|
48
52
|
}
|
|
49
|
-
function performStackLabelmapUpdate({ viewportIds, segmentationId
|
|
53
|
+
function performStackLabelmapUpdate({ viewportIds, segmentationId }) {
|
|
50
54
|
viewportIds.forEach((viewportId) => {
|
|
51
55
|
const viewportSegReps = SegmentationState.getSegmentationRepresentations(viewportId);
|
|
52
56
|
viewportSegReps.forEach((representation) => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
|
|
2
|
-
import * as SegmentationState from '../../stateManagement/segmentation/segmentationState';
|
|
3
2
|
import { triggerSegmentationRenderBySegmentationId } from '../../stateManagement/segmentation/SegmentationRenderingEngine';
|
|
4
3
|
import onLabelmapSegmentationDataModified from './labelmap/onLabelmapSegmentationDataModified';
|
|
4
|
+
import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
|
|
5
5
|
const onSegmentationDataModified = function (evt) {
|
|
6
6
|
const { segmentationId } = evt.detail;
|
|
7
|
-
const { type } =
|
|
7
|
+
const { type } = getSegmentation(segmentationId);
|
|
8
8
|
if (type === SegmentationRepresentations.Labelmap) {
|
|
9
9
|
onLabelmapSegmentationDataModified(evt);
|
|
10
10
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Types } from '@cornerstonejs/core';
|
|
2
1
|
declare class SegmentationRenderingEngine {
|
|
3
2
|
private _needsRender;
|
|
4
3
|
private _animationFrameSet;
|
|
@@ -6,7 +5,7 @@ declare class SegmentationRenderingEngine {
|
|
|
6
5
|
hasBeenDestroyed: boolean;
|
|
7
6
|
renderSegmentationsForViewport(viewportId?: string): void;
|
|
8
7
|
renderSegmentation(segmentationId: string): void;
|
|
9
|
-
_getAllViewports: () =>
|
|
8
|
+
_getAllViewports: () => import("@cornerstonejs/core").Viewport[];
|
|
10
9
|
_getViewportIdsForSegmentation(segmentationId?: string): string[];
|
|
11
10
|
private _throwIfDestroyed;
|
|
12
11
|
private _setViewportsToBeRenderedNextFrame;
|
|
@@ -15,9 +15,12 @@ export default class SegmentationStateManager {
|
|
|
15
15
|
getSegmentationRepresentation(segmentationRepresentationUID: string): SegmentationRepresentation | undefined;
|
|
16
16
|
addSegmentationRepresentationState(segmentationRepresentation: SegmentationRepresentation): void;
|
|
17
17
|
addSegmentationRepresentationToViewport(viewportId: string, segmentationRepresentationUID: string): void;
|
|
18
|
+
_updateLabelmapSegmentationReferences(segmentationId: any, stackViewport: any, labelmapImageIds: any, updateCallback: any): string;
|
|
18
19
|
updateLabelmapSegmentationImageReferences(viewportId: any, segmentationId: any): string;
|
|
20
|
+
_updateAllLabelmapSegmentationImageReferences(viewportId: any, segmentationId: any): void;
|
|
19
21
|
private getLabelmapImageIds;
|
|
20
22
|
getCurrentLabelmapImageIdForViewport(viewportId: string, segmentationId: string): string | undefined;
|
|
23
|
+
getStackSegmentationImageIdsForViewport(viewportId: string, segmentationId: string): string[];
|
|
21
24
|
getSegmentationRepresentations(viewportId: string): SegmentationRepresentation[];
|
|
22
25
|
removeRepresentation(segmentationRepresentationUID: string): void;
|
|
23
26
|
setActiveSegmentationRepresentation(viewportId: string, segmentationRepresentationUID: string): void;
|
|
@@ -111,6 +111,23 @@ export default class SegmentationStateManager {
|
|
|
111
111
|
}
|
|
112
112
|
this.setActiveSegmentationRepresentation(viewportId, segmentationRepresentationUID);
|
|
113
113
|
}
|
|
114
|
+
_updateLabelmapSegmentationReferences(segmentationId, stackViewport, labelmapImageIds, updateCallback) {
|
|
115
|
+
const currentImageId = stackViewport.getCurrentImageId();
|
|
116
|
+
for (const labelmapImageId of labelmapImageIds) {
|
|
117
|
+
const viewableImageId = stackViewport.isReferenceViewable({ referencedImageId: labelmapImageId }, { asOverlay: true });
|
|
118
|
+
if (viewableImageId) {
|
|
119
|
+
this._stackLabelmapImageIdReferenceMap
|
|
120
|
+
.get(segmentationId)
|
|
121
|
+
.set(currentImageId, labelmapImageId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (updateCallback) {
|
|
125
|
+
updateCallback(stackViewport, segmentationId, labelmapImageIds);
|
|
126
|
+
}
|
|
127
|
+
return this._stackLabelmapImageIdReferenceMap
|
|
128
|
+
.get(segmentationId)
|
|
129
|
+
.get(currentImageId);
|
|
130
|
+
}
|
|
114
131
|
updateLabelmapSegmentationImageReferences(viewportId, segmentationId) {
|
|
115
132
|
const segmentation = this.getSegmentation(segmentationId);
|
|
116
133
|
if (!segmentation) {
|
|
@@ -126,18 +143,36 @@ export default class SegmentationStateManager {
|
|
|
126
143
|
const labelmapImageIds = this.getLabelmapImageIds(representationData);
|
|
127
144
|
const enabledElement = getEnabledElementByViewportId(viewportId);
|
|
128
145
|
const stackViewport = enabledElement.viewport;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.set(currentImageId, labelmapImageId);
|
|
136
|
-
}
|
|
146
|
+
return this._updateLabelmapSegmentationReferences(segmentationId, stackViewport, labelmapImageIds, null);
|
|
147
|
+
}
|
|
148
|
+
_updateAllLabelmapSegmentationImageReferences(viewportId, segmentationId) {
|
|
149
|
+
const segmentation = this.getSegmentation(segmentationId);
|
|
150
|
+
if (!segmentation) {
|
|
151
|
+
return;
|
|
137
152
|
}
|
|
138
|
-
|
|
139
|
-
.
|
|
140
|
-
|
|
153
|
+
if (!this._stackLabelmapImageIdReferenceMap.has(segmentationId)) {
|
|
154
|
+
this._stackLabelmapImageIdReferenceMap.set(segmentationId, new Map());
|
|
155
|
+
}
|
|
156
|
+
const { representationData } = segmentation;
|
|
157
|
+
if (!representationData.LABELMAP) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const labelmapImageIds = this.getLabelmapImageIds(representationData);
|
|
161
|
+
const enabledElement = getEnabledElementByViewportId(viewportId);
|
|
162
|
+
const stackViewport = enabledElement.viewport;
|
|
163
|
+
this._updateLabelmapSegmentationReferences(segmentationId, stackViewport, labelmapImageIds, (stackViewport, segmentationId, labelmapImageIds) => {
|
|
164
|
+
const imageIds = stackViewport.getImageIds();
|
|
165
|
+
imageIds.forEach((imageId, index) => {
|
|
166
|
+
for (const labelmapImageId of labelmapImageIds) {
|
|
167
|
+
const viewableImageId = stackViewport.isReferenceViewable({ referencedImageId: labelmapImageId, sliceIndex: index }, { asOverlay: true, withNavigation: true });
|
|
168
|
+
if (viewableImageId) {
|
|
169
|
+
this._stackLabelmapImageIdReferenceMap
|
|
170
|
+
.get(segmentationId)
|
|
171
|
+
.set(imageId, labelmapImageId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
141
176
|
}
|
|
142
177
|
getLabelmapImageIds(representationData) {
|
|
143
178
|
const labelmapData = representationData.LABELMAP;
|
|
@@ -168,6 +203,19 @@ export default class SegmentationStateManager {
|
|
|
168
203
|
const imageIdReferenceMap = this._stackLabelmapImageIdReferenceMap.get(segmentationId);
|
|
169
204
|
return imageIdReferenceMap.get(currentImageId);
|
|
170
205
|
}
|
|
206
|
+
getStackSegmentationImageIdsForViewport(viewportId, segmentationId) {
|
|
207
|
+
const segmentation = this.getSegmentation(segmentationId);
|
|
208
|
+
if (!segmentation) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
this._updateAllLabelmapSegmentationImageReferences(viewportId, segmentationId);
|
|
212
|
+
const { viewport } = getEnabledElementByViewportId(viewportId);
|
|
213
|
+
const imageIds = viewport.getImageIds();
|
|
214
|
+
const associatedReferenceImageAndLabelmapImageIds = this._stackLabelmapImageIdReferenceMap.get(segmentationId);
|
|
215
|
+
return imageIds.map((imageId) => {
|
|
216
|
+
return associatedReferenceImageAndLabelmapImageIds.get(imageId);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
171
219
|
getSegmentationRepresentations(viewportId) {
|
|
172
220
|
const viewport = this.state.viewports[viewportId];
|
|
173
221
|
if (!viewport) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getStackSegmentationImageIdsForViewport(viewportId: string, segmentationId: string): string[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { defaultSegmentationStateManager } from './SegmentationStateManager';
|
|
2
|
+
export function getStackSegmentationImageIdsForViewport(viewportId, segmentationId) {
|
|
3
|
+
const segmentationStateManager = defaultSegmentationStateManager;
|
|
4
|
+
return segmentationStateManager.getStackSegmentationImageIdsForViewport(viewportId, segmentationId);
|
|
5
|
+
}
|
|
@@ -26,4 +26,5 @@ import { getActiveSegmentationRepresentation } from './getActiveSegmentationRepr
|
|
|
26
26
|
import { setActiveSegmentationRepresentation } from './setActiveSegmentationRepresentation';
|
|
27
27
|
import { getCurrentLabelmapImageIdForViewport } from './getCurrentLabelmapImageIdForViewport';
|
|
28
28
|
import { updateLabelmapSegmentationImageReferences } from './updateLabelmapSegmentationImageReferences';
|
|
29
|
-
|
|
29
|
+
import { getStackSegmentationImageIdsForViewport } from './getStackSegmentationImageIdsForViewport';
|
|
30
|
+
export { getSegmentation, getSegmentations, addSegmentation, removeSegmentation, getAllSegmentationRepresentations, getSegmentationRepresentation, removeRepresentation, getGlobalConfig, setGlobalConfig, getSegmentationRepresentationConfig, setSegmentationRepresentationConfig, getPerSegmentConfig, setPerSegmentConfig, getSegmentationRepresentations, addSegmentationRepresentation, getSegmentationRepresentationViewportStates, addColorLUT, getColorLUT, getNextColorLUTIndex, removeColorLUT, getSegmentationRepresentationsForSegmentation, getSegmentationRepresentationVisibility, setSegmentationRepresentationVisibility, getViewportIdsWithSegmentation, getActiveSegmentationRepresentation, setActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, updateLabelmapSegmentationImageReferences, getStackSegmentationImageIdsForViewport, };
|
|
@@ -26,4 +26,5 @@ import { getActiveSegmentationRepresentation } from './getActiveSegmentationRepr
|
|
|
26
26
|
import { setActiveSegmentationRepresentation } from './setActiveSegmentationRepresentation';
|
|
27
27
|
import { getCurrentLabelmapImageIdForViewport } from './getCurrentLabelmapImageIdForViewport';
|
|
28
28
|
import { updateLabelmapSegmentationImageReferences } from './updateLabelmapSegmentationImageReferences';
|
|
29
|
-
|
|
29
|
+
import { getStackSegmentationImageIdsForViewport } from './getStackSegmentationImageIdsForViewport';
|
|
30
|
+
export { getSegmentation, getSegmentations, addSegmentation, removeSegmentation, getAllSegmentationRepresentations, getSegmentationRepresentation, removeRepresentation, getGlobalConfig, setGlobalConfig, getSegmentationRepresentationConfig, setSegmentationRepresentationConfig, getPerSegmentConfig, setPerSegmentConfig, getSegmentationRepresentations, addSegmentationRepresentation, getSegmentationRepresentationViewportStates, addColorLUT, getColorLUT, getNextColorLUTIndex, removeColorLUT, getSegmentationRepresentationsForSegmentation, getSegmentationRepresentationVisibility, setSegmentationRepresentationVisibility, getViewportIdsWithSegmentation, getActiveSegmentationRepresentation, setActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, updateLabelmapSegmentationImageReferences, getStackSegmentationImageIdsForViewport, };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { PublicToolProps, ToolProps, EventTypes, SVGDrawingHelper } from '../../types';
|
|
3
3
|
import { BaseTool } from '../base';
|
|
4
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
4
5
|
export type PreviewData = {
|
|
5
6
|
preview: unknown;
|
|
6
7
|
timer?: number;
|
|
@@ -24,11 +25,22 @@ declare class BrushTool extends BaseTool {
|
|
|
24
25
|
referencedVolumeId: any;
|
|
25
26
|
segmentsLocked: number[] | [];
|
|
26
27
|
imageId?: undefined;
|
|
28
|
+
override?: undefined;
|
|
27
29
|
} | {
|
|
28
30
|
imageId: string;
|
|
29
31
|
segmentsLocked: number[] | [];
|
|
30
32
|
volumeId?: undefined;
|
|
31
33
|
referencedVolumeId?: undefined;
|
|
34
|
+
override?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
imageId: string;
|
|
37
|
+
segmentsLocked: number[] | [];
|
|
38
|
+
override: {
|
|
39
|
+
voxelManager: Types.IVoxelManager<number> | Types.IVoxelManager<Types.RGB>;
|
|
40
|
+
imageData: vtkImageData;
|
|
41
|
+
};
|
|
42
|
+
volumeId?: undefined;
|
|
43
|
+
referencedVolumeId?: undefined;
|
|
32
44
|
};
|
|
33
45
|
preMouseDownCallback: (evt: EventTypes.MouseDownActivateEventType) => boolean;
|
|
34
46
|
mouseMoveCallback: (evt: EventTypes.InteractionEventType) => void;
|
|
@@ -48,8 +60,13 @@ declare class BrushTool extends BaseTool {
|
|
|
48
60
|
viewUp: any;
|
|
49
61
|
strategySpecificConfiguration: any;
|
|
50
62
|
preview: unknown;
|
|
63
|
+
override: {
|
|
64
|
+
voxelManager: Types.IVoxelManager<number>;
|
|
65
|
+
imageData: vtkImageData;
|
|
66
|
+
};
|
|
51
67
|
segmentsLocked: number[];
|
|
52
68
|
imageId?: string;
|
|
69
|
+
imageIds?: string[];
|
|
53
70
|
volumeId?: string;
|
|
54
71
|
referencedVolumeId?: string;
|
|
55
72
|
} | {
|
|
@@ -67,6 +84,23 @@ declare class BrushTool extends BaseTool {
|
|
|
67
84
|
referencedVolumeId: any;
|
|
68
85
|
segmentsLocked: number[] | [];
|
|
69
86
|
imageId?: undefined;
|
|
87
|
+
override?: undefined;
|
|
88
|
+
} | {
|
|
89
|
+
points: any;
|
|
90
|
+
segmentIndex: number;
|
|
91
|
+
previewColors: any;
|
|
92
|
+
viewPlaneNormal: any;
|
|
93
|
+
toolGroupId: string;
|
|
94
|
+
segmentationId: string;
|
|
95
|
+
segmentationRepresentationUID: string;
|
|
96
|
+
viewUp: any;
|
|
97
|
+
strategySpecificConfiguration: any;
|
|
98
|
+
preview: unknown;
|
|
99
|
+
imageId: string;
|
|
100
|
+
segmentsLocked: number[] | [];
|
|
101
|
+
volumeId?: undefined;
|
|
102
|
+
referencedVolumeId?: undefined;
|
|
103
|
+
override?: undefined;
|
|
70
104
|
} | {
|
|
71
105
|
points: any;
|
|
72
106
|
segmentIndex: number;
|
|
@@ -80,6 +114,10 @@ declare class BrushTool extends BaseTool {
|
|
|
80
114
|
preview: unknown;
|
|
81
115
|
imageId: string;
|
|
82
116
|
segmentsLocked: number[] | [];
|
|
117
|
+
override: {
|
|
118
|
+
voxelManager: Types.IVoxelManager<number> | Types.IVoxelManager<Types.RGB>;
|
|
119
|
+
imageData: vtkImageData;
|
|
120
|
+
};
|
|
83
121
|
volumeId?: undefined;
|
|
84
122
|
referencedVolumeId?: undefined;
|
|
85
123
|
};
|
|
@@ -10,10 +10,11 @@ import { drawCircle as drawCircleSvg } from '../../drawingSvg';
|
|
|
10
10
|
import { resetElementCursor, hideElementCursor, } from '../../cursors/elementCursor';
|
|
11
11
|
import triggerAnnotationRenderForViewportUIDs from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
12
12
|
import { isVolumeSegmentation } from './strategies/utils/stackVolumeCheck';
|
|
13
|
-
import { getActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, getSegmentation, } from '../../stateManagement/segmentation/segmentationState';
|
|
13
|
+
import { getActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, getSegmentation, getStackSegmentationImageIdsForViewport, } from '../../stateManagement/segmentation/segmentationState';
|
|
14
14
|
import { getLockedSegmentIndices } from '../../stateManagement/segmentation/segmentLocking';
|
|
15
15
|
import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
|
|
16
16
|
import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/segmentationColor';
|
|
17
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
17
18
|
class BrushTool extends BaseTool {
|
|
18
19
|
constructor(toolProps = {}, defaultToolProps = {
|
|
19
20
|
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
@@ -141,7 +142,6 @@ class BrushTool extends BaseTool {
|
|
|
141
142
|
const eventData = evt.detail;
|
|
142
143
|
const { element, currentPoints } = eventData;
|
|
143
144
|
const enabledElement = getEnabledElement(element);
|
|
144
|
-
const { renderingEngine } = enabledElement;
|
|
145
145
|
this.updateCursor(evt);
|
|
146
146
|
const { viewportIdsToRender } = this._hoverData;
|
|
147
147
|
triggerAnnotationRenderForViewportUIDs(viewportIdsToRender);
|
|
@@ -235,12 +235,45 @@ class BrushTool extends BaseTool {
|
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
if (this.configuration.activeStrategy.includes('SPHERE')) {
|
|
238
|
-
|
|
238
|
+
const referencedImageIds = viewport.getImageIds();
|
|
239
|
+
const isValidVolumeForSphere = csUtils.isValidVolume(referencedImageIds);
|
|
240
|
+
if (!isValidVolumeForSphere) {
|
|
241
|
+
throw new Error('Volume is not reconstructable for sphere manipulation');
|
|
242
|
+
}
|
|
243
|
+
const labelmapImageIds = getStackSegmentationImageIdsForViewport(viewport.id, segmentationId);
|
|
244
|
+
if (!labelmapImageIds || labelmapImageIds.length === 1) {
|
|
245
|
+
return {
|
|
246
|
+
imageId: segmentationImageId,
|
|
247
|
+
segmentsLocked,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const tempVolumeId = 'tempVolumeId';
|
|
251
|
+
const { dimensions, direction, origin, spacing, numberOfComponents, imageIds: sortedLabelmapImageIds, } = csUtils.generateVolumePropsFromImageIds(labelmapImageIds, tempVolumeId);
|
|
252
|
+
const newVoxelManager = csUtils.VoxelManager.createImageVolumeVoxelManager({
|
|
253
|
+
dimensions,
|
|
254
|
+
imageIds: sortedLabelmapImageIds,
|
|
255
|
+
numberOfComponents,
|
|
256
|
+
});
|
|
257
|
+
const newImageData = vtkImageData.newInstance();
|
|
258
|
+
newImageData.setDimensions(dimensions);
|
|
259
|
+
newImageData.setSpacing(spacing);
|
|
260
|
+
newImageData.setDirection(direction);
|
|
261
|
+
newImageData.setOrigin(origin);
|
|
262
|
+
return {
|
|
263
|
+
imageId: segmentationImageId,
|
|
264
|
+
segmentsLocked,
|
|
265
|
+
override: {
|
|
266
|
+
voxelManager: newVoxelManager,
|
|
267
|
+
imageData: newImageData,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
return {
|
|
273
|
+
imageId: segmentationImageId,
|
|
274
|
+
segmentsLocked,
|
|
275
|
+
};
|
|
239
276
|
}
|
|
240
|
-
return {
|
|
241
|
-
imageId: segmentationImageId,
|
|
242
|
-
segmentsLocked,
|
|
243
|
-
};
|
|
244
277
|
}
|
|
245
278
|
}
|
|
246
279
|
createHoverData(element, centerCanvas) {
|
|
@@ -37,7 +37,8 @@ export default class BrushStrategy {
|
|
|
37
37
|
}
|
|
38
38
|
this._fill.forEach((func) => func(initializedData));
|
|
39
39
|
const { segmentationVoxelManager, previewVoxelManager, previewSegmentIndex, } = initializedData;
|
|
40
|
-
triggerSegmentationDataModified(initializedData.segmentationId, segmentationVoxelManager.
|
|
40
|
+
triggerSegmentationDataModified(initializedData.segmentationId, segmentationVoxelManager.getArrayOfModifiedSlices());
|
|
41
|
+
segmentationVoxelManager.resetModifiedSlices();
|
|
41
42
|
if (!previewSegmentIndex || !previewVoxelManager.modifiedSlices.size) {
|
|
42
43
|
return null;
|
|
43
44
|
}
|
|
@@ -69,7 +70,9 @@ export default class BrushStrategy {
|
|
|
69
70
|
BrushStrategy.childFunctions[key](this, result[key]);
|
|
70
71
|
}
|
|
71
72
|
});
|
|
72
|
-
this.strategyFunction = (enabledElement, operationData) =>
|
|
73
|
+
this.strategyFunction = (enabledElement, operationData) => {
|
|
74
|
+
return this.fill(enabledElement, operationData);
|
|
75
|
+
};
|
|
73
76
|
for (const key of Object.keys(BrushStrategy.childFunctions)) {
|
|
74
77
|
this.strategyFunction[key] = this[key];
|
|
75
78
|
}
|
|
@@ -82,9 +85,11 @@ export default class BrushStrategy {
|
|
|
82
85
|
return operationData.preview;
|
|
83
86
|
}
|
|
84
87
|
const { imageVoxelManager, segmentationVoxelManager, segmentationImageData, } = data;
|
|
88
|
+
const segmentationVoxelManagerToUse = operationData.override?.voxelManager || segmentationVoxelManager;
|
|
89
|
+
const segmentationImageDataToUse = operationData.override?.imageData || segmentationImageData;
|
|
85
90
|
const previewVoxelManager = operationData.preview?.previewVoxelManager ||
|
|
86
91
|
VoxelManager.createHistoryVoxelManager({
|
|
87
|
-
sourceVoxelManager:
|
|
92
|
+
sourceVoxelManager: segmentationVoxelManagerToUse,
|
|
88
93
|
});
|
|
89
94
|
const previewEnabled = !!operationData.previewColors;
|
|
90
95
|
const previewSegmentIndex = previewEnabled ? 255 : undefined;
|
|
@@ -94,8 +99,8 @@ export default class BrushStrategy {
|
|
|
94
99
|
...operationData,
|
|
95
100
|
enabledElement,
|
|
96
101
|
imageVoxelManager,
|
|
97
|
-
segmentationVoxelManager,
|
|
98
|
-
segmentationImageData,
|
|
102
|
+
segmentationVoxelManager: segmentationVoxelManagerToUse,
|
|
103
|
+
segmentationImageData: segmentationImageDataToUse,
|
|
99
104
|
previewVoxelManager,
|
|
100
105
|
viewport,
|
|
101
106
|
centerWorld: null,
|
|
@@ -118,6 +118,6 @@ export default {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
triggerSegmentationDataModified(operationData.segmentationId, previewVoxelManager.
|
|
121
|
+
triggerSegmentationDataModified(operationData.segmentationId, previewVoxelManager.getArrayOfModifiedSlices());
|
|
122
122
|
},
|
|
123
123
|
};
|
|
@@ -66,7 +66,7 @@ export default {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
tracking.forEach(callback, {});
|
|
69
|
-
triggerSegmentationDataModified(operationData.segmentationId, tracking.
|
|
69
|
+
triggerSegmentationDataModified(operationData.segmentationId, tracking.getArrayOfModifiedSlices());
|
|
70
70
|
tracking.clear();
|
|
71
71
|
},
|
|
72
72
|
[StrategyCallbacks.RejectPreview]: (operationData) => {
|
|
@@ -78,7 +78,7 @@ export default {
|
|
|
78
78
|
segmentationVoxelManager.setAtIndex(index, value);
|
|
79
79
|
};
|
|
80
80
|
previewVoxelManager.forEach(callback);
|
|
81
|
-
triggerSegmentationDataModified(operationData.segmentationId, previewVoxelManager.
|
|
81
|
+
triggerSegmentationDataModified(operationData.segmentationId, previewVoxelManager.getArrayOfModifiedSlices());
|
|
82
82
|
previewVoxelManager.clear();
|
|
83
83
|
},
|
|
84
84
|
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
|
|
2
|
+
import { triggerEvent, eventTarget } from '@cornerstonejs/core';
|
|
2
3
|
export default {
|
|
3
4
|
[StrategyCallbacks.INTERNAL_setValue]: (operationData, { value, index }) => {
|
|
4
|
-
const { segmentsLocked, segmentIndex, previewVoxelManager, previewSegmentIndex, segmentationVoxelManager, } = operationData;
|
|
5
|
+
const { segmentsLocked, segmentIndex, previewVoxelManager, previewSegmentIndex, segmentationVoxelManager, segmentationId, } = operationData;
|
|
5
6
|
const existingValue = segmentationVoxelManager.getAtIndex(index);
|
|
7
|
+
let changed = false;
|
|
6
8
|
if (segmentIndex === null) {
|
|
7
9
|
const oldValue = previewVoxelManager.getAtIndex(index);
|
|
8
10
|
if (oldValue !== undefined) {
|
|
9
|
-
previewVoxelManager.setAtIndex(index, oldValue);
|
|
11
|
+
changed = previewVoxelManager.setAtIndex(index, oldValue);
|
|
10
12
|
}
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
@@ -15,13 +17,13 @@ export default {
|
|
|
15
17
|
}
|
|
16
18
|
if (existingValue === previewSegmentIndex) {
|
|
17
19
|
if (previewVoxelManager.getAtIndex(index) === undefined) {
|
|
18
|
-
segmentationVoxelManager.setAtIndex(index, segmentIndex);
|
|
20
|
+
changed = segmentationVoxelManager.setAtIndex(index, segmentIndex);
|
|
19
21
|
}
|
|
20
22
|
else {
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
const useSegmentIndex = previewSegmentIndex ?? segmentIndex;
|
|
25
|
-
previewVoxelManager.setAtIndex(index, useSegmentIndex);
|
|
27
|
+
changed = previewVoxelManager.setAtIndex(index, useSegmentIndex);
|
|
26
28
|
},
|
|
27
29
|
};
|
|
@@ -21,20 +21,11 @@ const sphereComposition = {
|
|
|
21
21
|
operationData.centerIJK = transformWorldToIndex(segmentationImageData, center);
|
|
22
22
|
const { boundsIJK: newBoundsIJK, topLeftWorld, bottomRightWorld, } = getSphereBoundsInfo(points.slice(0, 2), segmentationImageData, viewport);
|
|
23
23
|
segmentationVoxelManager.boundsIJK = newBoundsIJK;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
segmentationVoxelManager.isInObject = createEllipseInPoint({
|
|
33
|
-
topLeftWorld,
|
|
34
|
-
bottomRightWorld,
|
|
35
|
-
center,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
24
|
+
segmentationVoxelManager.isInObject = createEllipseInPoint({
|
|
25
|
+
topLeftWorld,
|
|
26
|
+
bottomRightWorld,
|
|
27
|
+
center,
|
|
28
|
+
});
|
|
38
29
|
},
|
|
39
30
|
};
|
|
40
31
|
const SPHERE_STRATEGY = new BrushStrategy('Sphere', compositions.regionFill, compositions.setValue, sphereComposition, compositions.determineSegmentIndex, compositions.preview);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { LabelmapSegmentationDataStack, LabelmapSegmentationDataVolume } from './LabelmapTypes';
|
|
3
|
+
import type vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
3
4
|
type LabelmapToolOperationData = {
|
|
4
5
|
segmentationId: string;
|
|
5
6
|
segmentIndex: number;
|
|
@@ -11,6 +12,10 @@ type LabelmapToolOperationData = {
|
|
|
11
12
|
segmentationRepresentationUID: string;
|
|
12
13
|
points: Types.Point3[];
|
|
13
14
|
voxelManager: any;
|
|
15
|
+
override: {
|
|
16
|
+
voxelManager: Types.IVoxelManager<number>;
|
|
17
|
+
imageData: vtkImageData;
|
|
18
|
+
};
|
|
14
19
|
preview: any;
|
|
15
20
|
toolGroupId: string;
|
|
16
21
|
};
|
|
@@ -15,13 +15,13 @@ function generateContourSetsFromLabelmap({ segmentations }) {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
const numSlices = vol.dimensions[2];
|
|
18
|
-
const
|
|
18
|
+
const voxelManager = vol.voxelManager;
|
|
19
19
|
const pixelsPerSlice = vol.dimensions[0] * vol.dimensions[1];
|
|
20
20
|
for (let z = 0; z < numSlices; z++) {
|
|
21
21
|
for (let y = 0; y < vol.dimensions[1]; y++) {
|
|
22
22
|
const index = y * vol.dimensions[0] + z * pixelsPerSlice;
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
voxelManager.setAtIndex(index, 0);
|
|
24
|
+
voxelManager.setAtIndex(index + vol.dimensions[0] - 1, 0);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
const ContourSets = [];
|
|
@@ -41,13 +41,13 @@ function generateContourSetsFromLabelmap({ segmentations }) {
|
|
|
41
41
|
});
|
|
42
42
|
const { containedSegmentIndices } = segment;
|
|
43
43
|
for (let sliceIndex = 0; sliceIndex < numSlices; sliceIndex++) {
|
|
44
|
-
if (isSliceEmptyForSegment(sliceIndex,
|
|
44
|
+
if (isSliceEmptyForSegment(sliceIndex, voxelManager, pixelsPerSlice, segIndex)) {
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
47
|
const frameStart = sliceIndex * pixelsPerSlice;
|
|
48
48
|
try {
|
|
49
49
|
for (let i = 0; i < pixelsPerSlice; i++) {
|
|
50
|
-
const value =
|
|
50
|
+
const value = voxelManager.getAtIndex(i + frameStart);
|
|
51
51
|
if (value === segIndex || containedSegmentIndices?.has(value)) {
|
|
52
52
|
scalars.setValue(i + frameStart, 1);
|
|
53
53
|
}
|
|
@@ -96,11 +96,11 @@ function generateContourSetsFromLabelmap({ segmentations }) {
|
|
|
96
96
|
}
|
|
97
97
|
return ContourSets;
|
|
98
98
|
}
|
|
99
|
-
function isSliceEmptyForSegment(sliceIndex,
|
|
99
|
+
function isSliceEmptyForSegment(sliceIndex, voxelManager, pixelsPerSlice, segIndex) {
|
|
100
100
|
const startIdx = sliceIndex * pixelsPerSlice;
|
|
101
101
|
const endIdx = startIdx + pixelsPerSlice;
|
|
102
102
|
for (let i = startIdx; i < endIdx; i++) {
|
|
103
|
-
if (
|
|
103
|
+
if (voxelManager.getAtIndex(i) === segIndex) {
|
|
104
104
|
return false;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -4,14 +4,16 @@ import { isVolumeSegmentation } from '../../tools/segmentation/strategies/utils/
|
|
|
4
4
|
export function getSegmentIndexAtLabelmapBorder(segmentationId, worldPoint, { viewport, searchRadius }) {
|
|
5
5
|
const segmentation = getSegmentation(segmentationId);
|
|
6
6
|
const labelmapData = segmentation.representationData.LABELMAP;
|
|
7
|
-
if (isVolumeSegmentation(labelmapData)) {
|
|
7
|
+
if (isVolumeSegmentation(labelmapData, viewport)) {
|
|
8
8
|
const { volumeId } = labelmapData;
|
|
9
9
|
const segmentationVolume = cache.getVolume(volumeId);
|
|
10
10
|
if (!segmentationVolume) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
+
const voxelManager = segmentationVolume.voxelManager;
|
|
13
14
|
const imageData = segmentationVolume.imageData;
|
|
14
|
-
const
|
|
15
|
+
const indexIJK = utilities.transformWorldToIndex(imageData, worldPoint);
|
|
16
|
+
const segmentIndex = voxelManager.getAtIJK(indexIJK[0], indexIJK[1], indexIJK[2]);
|
|
15
17
|
const canvasPoint = viewport.worldToCanvas(worldPoint);
|
|
16
18
|
const onEdge = isSegmentOnEdgeCanvas(canvasPoint, segmentIndex, viewport, imageData, searchRadius);
|
|
17
19
|
return onEdge ? segmentIndex : undefined;
|
|
@@ -69,7 +71,9 @@ function isSegmentOnEdgeCanvas(canvasPoint, segmentIndex, viewport, imageData, s
|
|
|
69
71
|
const getNeighborIndex = (deltaI, deltaJ) => {
|
|
70
72
|
const neighborCanvas = [canvasPoint[0] + deltaI, canvasPoint[1] + deltaJ];
|
|
71
73
|
const worldPoint = viewport.canvasToWorld(neighborCanvas);
|
|
72
|
-
|
|
74
|
+
const voxelManager = imageData.get('voxelManager').voxelManager;
|
|
75
|
+
const indexIJK = utilities.transformWorldToIndex(imageData, worldPoint);
|
|
76
|
+
return voxelManager.getAtIJK(indexIJK[0], indexIJK[1], indexIJK[2]);
|
|
73
77
|
};
|
|
74
78
|
return isSegmentOnEdge(getNeighborIndex, segmentIndex, searchRadius);
|
|
75
79
|
}
|
|
@@ -25,7 +25,7 @@ function createIsInSegment(segVolumeId, segmentIndex, containedSegmentIndices) {
|
|
|
25
25
|
console.warn(`No volume found for ${segVolumeId}`);
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
const
|
|
28
|
+
const voxelManager = vol.voxelManager;
|
|
29
29
|
const width = vol.dimensions[0];
|
|
30
30
|
const pixelsPerSlice = width * vol.dimensions[1];
|
|
31
31
|
return {
|
|
@@ -34,14 +34,14 @@ function createIsInSegment(segVolumeId, segmentIndex, containedSegmentIndices) {
|
|
|
34
34
|
const ijk = vol.imageData.worldToIndex(point).map(Math.round);
|
|
35
35
|
const [i, j, k] = ijk;
|
|
36
36
|
const index = i + j * width + k * pixelsPerSlice;
|
|
37
|
-
const value =
|
|
37
|
+
const value = voxelManager.getAtIndex(index);
|
|
38
38
|
return value === segmentIndex || containedSegmentIndices?.has(value);
|
|
39
39
|
},
|
|
40
40
|
toIJK: (point) => vol.imageData.worldToIndex(point),
|
|
41
41
|
testIJK: (ijk) => {
|
|
42
42
|
const [i, j, k] = ijk;
|
|
43
43
|
const index = Math.round(i) + Math.round(j) * width + Math.round(k) * pixelsPerSlice;
|
|
44
|
-
const value =
|
|
44
|
+
const value = voxelManager.getAtIndex(index);
|
|
45
45
|
return value === segmentIndex || containedSegmentIndices?.has(value);
|
|
46
46
|
},
|
|
47
47
|
};
|