@cornerstonejs/tools 5.4.12 → 5.4.14
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/segmentationDataModifiedEventListener.js +2 -2
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +2 -1
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +45 -1
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.d.ts +12 -2
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapEditTransaction.js +21 -1
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.d.ts +5 -0
- package/dist/esm/stateManagement/segmentation/labelmapModel/labelmapImageReferenceResolver.js +47 -1
- package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.d.ts +5 -1
- package/dist/esm/stateManagement/segmentation/labelmapModel/privateLabelmap.js +49 -4
- package/dist/esm/tools/annotation/ClickSegmentTool.d.ts +51 -0
- package/dist/esm/tools/annotation/ClickSegmentTool.js +748 -0
- package/dist/esm/tools/annotation/regionSegmentHoverCursors.d.ts +4 -0
- package/dist/esm/tools/annotation/regionSegmentHoverCursors.js +4 -0
- package/dist/esm/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.js +42 -11
- package/dist/esm/tools/index.d.ts +2 -1
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/segmentation/BrushTool.js +1 -0
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +21 -0
- package/dist/esm/tools/segmentation/strategies/utils/crossLayerErase.js +20 -0
- package/dist/esm/utilities/segmentation/commitSliceMasksToLabelmap.d.ts +12 -0
- package/dist/esm/utilities/segmentation/commitSliceMasksToLabelmap.js +128 -0
- package/dist/esm/utilities/segmentation/createEnsureSliceLoadedForVolume.d.ts +2 -0
- package/dist/esm/utilities/segmentation/createEnsureSliceLoadedForVolume.js +36 -0
- package/dist/esm/utilities/segmentation/createLabelmapMemo.d.ts +7 -0
- package/dist/esm/utilities/segmentation/createLabelmapMemo.js +41 -8
- package/dist/esm/utilities/segmentation/floodFillIslandRemoval.d.ts +20 -0
- package/dist/esm/utilities/segmentation/floodFillIslandRemoval.js +118 -0
- package/dist/esm/utilities/segmentation/floodFillSliceLazy.d.ts +33 -0
- package/dist/esm/utilities/segmentation/floodFillSliceLazy.js +167 -0
- package/dist/esm/utilities/segmentation/growCut/floodFillIntensityRangeTypes.d.ts +51 -0
- package/dist/esm/utilities/segmentation/growCut/floodFillIntensityRangeTypes.js +0 -0
- package/dist/esm/utilities/segmentation/growCut/getViewportVoiMappingForVolume.d.ts +10 -0
- package/dist/esm/utilities/segmentation/growCut/getViewportVoiMappingForVolume.js +21 -0
- package/dist/esm/utilities/segmentation/growCut/intensityRange/adaptiveRegionIntensityRange.d.ts +57 -0
- package/dist/esm/utilities/segmentation/growCut/intensityRange/adaptiveRegionIntensityRange.js +559 -0
- package/dist/esm/utilities/segmentation/growCut/neighborhoodStats.d.ts +10 -0
- package/dist/esm/utilities/segmentation/growCut/neighborhoodStats.js +44 -0
- package/dist/esm/utilities/segmentation/growCut/runFloodFillSegmentation.d.ts +61 -0
- package/dist/esm/utilities/segmentation/growCut/runFloodFillSegmentation.js +524 -0
- package/dist/esm/utilities/segmentation/islandRemoval.d.ts +3 -2
- package/dist/esm/utilities/segmentation/islandRemoval.js +3 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const CIRCLE_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3C/svg%3E\") 12 12, crosshair";
|
|
2
|
+
export declare const PLUS_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3Cpath%20d='M12%208v8M8%2012h8'%20stroke='%2300dc82'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, copy";
|
|
3
|
+
export declare const BLOCKED_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23ff5a5a'%20stroke-width='2'/%3E%3Cpath%20d='M5.7%205.7L18.3%2018.3'%20stroke='%23ff5a5a'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, not-allowed";
|
|
4
|
+
export declare const PENDING_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23a0a6ad'%20stroke-width='2'%20stroke-dasharray='4%203'/%3E%3C/svg%3E\") 12 12, progress";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const CIRCLE_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3C/svg%3E\") 12 12, crosshair";
|
|
2
|
+
export const PLUS_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%2300dc82'%20stroke-width='2'/%3E%3Cpath%20d='M12%208v8M8%2012h8'%20stroke='%2300dc82'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, copy";
|
|
3
|
+
export const BLOCKED_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23ff5a5a'%20stroke-width='2'/%3E%3Cpath%20d='M5.7%205.7L18.3%2018.3'%20stroke='%23ff5a5a'%20stroke-width='2'%20stroke-linecap='round'/%3E%3C/svg%3E\") 12 12, not-allowed";
|
|
4
|
+
export const PENDING_CURSOR = "url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'%20fill='none'%20stroke='%23a0a6ad'%20stroke-width='2'%20stroke-dasharray='4%203'/%3E%3C/svg%3E\") 12 12, progress";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cache, Enums, convertMapperToNotSharedMapper, volumeLoader, eventTarget,
|
|
1
|
+
import { cache, Enums, convertMapperToNotSharedMapper, volumeLoader, eventTarget, } from '@cornerstonejs/core';
|
|
2
2
|
import { Events, SegmentationRepresentations } from '../../../enums/index.js';
|
|
3
3
|
import { getSegmentation } from '../../../stateManagement/segmentation/getSegmentation.js';
|
|
4
4
|
const internalCache = new Map();
|
|
@@ -52,6 +52,8 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
52
52
|
}
|
|
53
53
|
viewport.removeActors([uid]);
|
|
54
54
|
const oldMapper = actor.getMapper();
|
|
55
|
+
const sharedImageData = oldMapper.getInputData();
|
|
56
|
+
const originalScalars = sharedImageData.getPointData().getScalars() ?? null;
|
|
55
57
|
const mapper = convertMapperToNotSharedMapper(oldMapper);
|
|
56
58
|
actor.setMapper(mapper);
|
|
57
59
|
mapper.setBlendMode(Enums.BlendModes.LABELMAP_EDGE_PROJECTION_BLEND);
|
|
@@ -68,9 +70,16 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
68
70
|
.getProperty()
|
|
69
71
|
.getIndependentComponents();
|
|
70
72
|
actor.getProperty().setIndependentComponents(true);
|
|
73
|
+
const oldUseLabelOutline = actor.getProperty().getUseLabelOutline();
|
|
74
|
+
const oldLabelOutlineOpacity = actor.getProperty().getLabelOutlineOpacity();
|
|
75
|
+
const oldLabelOutlineThickness = actor
|
|
76
|
+
.getProperty()
|
|
77
|
+
.getLabelOutlineThickness();
|
|
78
|
+
const representationUID = volumeInputArray[0].representationUID ??
|
|
79
|
+
`${segmentationId}-${SegmentationRepresentations.Labelmap}`;
|
|
71
80
|
viewport.addActor({
|
|
72
81
|
...defaultActor,
|
|
73
|
-
representationUID
|
|
82
|
+
representationUID,
|
|
74
83
|
});
|
|
75
84
|
internalCache.set(uid, {
|
|
76
85
|
added: true,
|
|
@@ -82,7 +91,7 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
82
91
|
preLoad: load,
|
|
83
92
|
});
|
|
84
93
|
function onSegmentationDataModified(evt) {
|
|
85
|
-
const { segmentationId } = evt.detail;
|
|
94
|
+
const { segmentationId, modifiedSlicesToUse } = evt.detail;
|
|
86
95
|
const { representationData } = getSegmentation(segmentationId);
|
|
87
96
|
const { volumeId: segVolumeId } = representationData.Labelmap;
|
|
88
97
|
if (segVolumeId !== segImageVolume.volumeId) {
|
|
@@ -92,19 +101,30 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
92
101
|
const segVoxelManager = segmentationVolume.voxelManager;
|
|
93
102
|
const imageData = mapper.getInputData();
|
|
94
103
|
const array = imageData.getPointData().getArray(0);
|
|
95
|
-
const
|
|
104
|
+
const combinedData = array.getData();
|
|
96
105
|
const newComp = 2;
|
|
97
106
|
const dims = segImageData.getDimensions();
|
|
98
|
-
const
|
|
107
|
+
const sliceSize = dims[0] * dims[1];
|
|
108
|
+
const slices = modifiedSlicesToUse?.length
|
|
109
|
+
? modifiedSlicesToUse
|
|
110
|
+
: Array.from({ length: dims[2] }, (_, i) => i);
|
|
99
111
|
for (const z of slices) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
const sliceStart = z * sliceSize;
|
|
113
|
+
const sliceImage = cache.getImage(segmentationVolume.imageIds?.[z]);
|
|
114
|
+
const sliceData = sliceImage?.voxelManager?.getScalarData?.();
|
|
115
|
+
if (sliceData?.length === sliceSize) {
|
|
116
|
+
for (let i = 0; i < sliceSize; ++i) {
|
|
117
|
+
combinedData[(sliceStart + i) * newComp + 1] = sliceData[i];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
for (let i = 0; i < sliceSize; ++i) {
|
|
122
|
+
const iTuple = sliceStart + i;
|
|
123
|
+
combinedData[iTuple * newComp + 1] = segVoxelManager.getAtIndex(iTuple);
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
|
-
array.setData(
|
|
127
|
+
array.setData(combinedData);
|
|
108
128
|
imageData.modified();
|
|
109
129
|
viewport.render();
|
|
110
130
|
}
|
|
@@ -113,7 +133,7 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
113
133
|
if (evt.detail.viewportId !== viewport.id) {
|
|
114
134
|
return;
|
|
115
135
|
}
|
|
116
|
-
eventTarget.
|
|
136
|
+
eventTarget.removeEventListenerDebounced(Events.SEGMENTATION_DATA_MODIFIED, onSegmentationDataModified);
|
|
117
137
|
eventTarget.removeEventListener(Events.SEGMENTATION_REPRESENTATION_REMOVED, onSegmentationRepresentationRemoved);
|
|
118
138
|
const actorEntry = viewport.getActor(uid);
|
|
119
139
|
if (actorEntry) {
|
|
@@ -124,11 +144,22 @@ export async function addVolumesAsIndependentComponents({ viewport, volumeInputs
|
|
|
124
144
|
return;
|
|
125
145
|
}
|
|
126
146
|
actor.setMapper(oldMapper);
|
|
147
|
+
const pointData = sharedImageData.getPointData();
|
|
148
|
+
if (originalScalars) {
|
|
149
|
+
pointData.setScalars(originalScalars);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
pointData.removeArray('Pixels');
|
|
153
|
+
}
|
|
154
|
+
sharedImageData.modified();
|
|
127
155
|
actor.getProperty().setColorMixPreset(oldColorMixPreset);
|
|
128
156
|
actor
|
|
129
157
|
.getProperty()
|
|
130
158
|
.setForceNearestInterpolation(1, oldForceNearestInterpolation);
|
|
131
159
|
actor.getProperty().setIndependentComponents(oldIndependentComponents);
|
|
160
|
+
actor.getProperty().setUseLabelOutline(oldUseLabelOutline);
|
|
161
|
+
actor.getProperty().setLabelOutlineOpacity(oldLabelOutlineOpacity);
|
|
162
|
+
actor.getProperty().setLabelOutlineThickness(oldLabelOutlineThickness);
|
|
132
163
|
viewport.addActor({
|
|
133
164
|
...defaultActor,
|
|
134
165
|
});
|
|
@@ -44,6 +44,7 @@ import KeyImageTool from './annotation/KeyImageTool.js';
|
|
|
44
44
|
import AnnotationEraserTool from './AnnotationEraserTool.js';
|
|
45
45
|
import RegionSegmentTool from './annotation/RegionSegmentTool.js';
|
|
46
46
|
import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool.js';
|
|
47
|
+
import ClickSegmentTool from './annotation/ClickSegmentTool.js';
|
|
47
48
|
import WholeBodySegmentTool from './annotation/WholeBodySegmentTool.js';
|
|
48
49
|
import LabelmapBaseTool from './segmentation/LabelmapBaseTool.js';
|
|
49
50
|
import RectangleScissorsTool from './segmentation/RectangleScissorsTool.js';
|
|
@@ -61,4 +62,4 @@ import SegmentBidirectionalTool from './segmentation/SegmentBidirectionalTool.js
|
|
|
61
62
|
import * as strategies from './segmentation/strategies/index.js';
|
|
62
63
|
import SegmentLabelTool from './segmentation/SegmentLabelTool.js';
|
|
63
64
|
import LabelMapEditWithContourTool from './segmentation/LabelmapEditWithContour.js';
|
|
64
|
-
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, OrientationControllerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
|
|
65
|
+
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, OrientationControllerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, ClickSegmentTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
|
package/dist/esm/tools/index.js
CHANGED
|
@@ -44,6 +44,7 @@ import KeyImageTool from './annotation/KeyImageTool.js';
|
|
|
44
44
|
import AnnotationEraserTool from './AnnotationEraserTool.js';
|
|
45
45
|
import RegionSegmentTool from './annotation/RegionSegmentTool.js';
|
|
46
46
|
import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool.js';
|
|
47
|
+
import ClickSegmentTool from './annotation/ClickSegmentTool.js';
|
|
47
48
|
import WholeBodySegmentTool from './annotation/WholeBodySegmentTool.js';
|
|
48
49
|
import LabelmapBaseTool from './segmentation/LabelmapBaseTool.js';
|
|
49
50
|
import RectangleScissorsTool from './segmentation/RectangleScissorsTool.js';
|
|
@@ -61,4 +62,4 @@ import SegmentBidirectionalTool from './segmentation/SegmentBidirectionalTool.js
|
|
|
61
62
|
import * as strategies from './segmentation/strategies/index.js';
|
|
62
63
|
import SegmentLabelTool from './segmentation/SegmentLabelTool.js';
|
|
63
64
|
import LabelMapEditWithContourTool from './segmentation/LabelmapEditWithContour.js';
|
|
64
|
-
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, OrientationControllerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
|
|
65
|
+
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, OrientationControllerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, ClickSegmentTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
|
|
@@ -2,6 +2,7 @@ import { triggerSegmentationDataModified } from '../../../stateManagement/segmen
|
|
|
2
2
|
import compositions from './compositions/index.js';
|
|
3
3
|
import { getStrategyData } from './utils/getStrategyData.js';
|
|
4
4
|
import { StrategyCallbacks } from '../../../enums/index.js';
|
|
5
|
+
import { memoAsStep } from '../../../utilities/segmentation/createLabelmapMemo.js';
|
|
5
6
|
import { eraseCrossLayerOverwrites, prepareOverlapOperationData, } from './utils/labelmapOverlap.js';
|
|
6
7
|
import { shouldUseLazyLabelmapEditing } from '../utils/shouldUseLazyLabelmapEditing.js';
|
|
7
8
|
class BrushStrategy {
|
|
@@ -23,7 +24,27 @@ class BrushStrategy {
|
|
|
23
24
|
}
|
|
24
25
|
if (initializedData.memo?.segmentationVoxelManager !==
|
|
25
26
|
initializedData.segmentationVoxelManager) {
|
|
27
|
+
const previousMemo = initializedData.memo;
|
|
26
28
|
initializedData.memo = initializedData.createMemo(initializedData.segmentationId, initializedData.segmentationVoxelManager);
|
|
29
|
+
if (previousMemo &&
|
|
30
|
+
previousMemo !== initializedData.memo &&
|
|
31
|
+
previousMemo.commitMemo?.()) {
|
|
32
|
+
(initializedData.memo.priorSteps ||= []).push(memoAsStep(previousMemo));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const moveStep = initializedData.labelmapEditTransaction?.moveStep;
|
|
36
|
+
if (moveStep && initializedData.memo) {
|
|
37
|
+
const { segmentationId } = initializedData;
|
|
38
|
+
(initializedData.memo.priorSteps ||= []).push({
|
|
39
|
+
undo: () => {
|
|
40
|
+
moveStep.undo();
|
|
41
|
+
triggerSegmentationDataModified(segmentationId);
|
|
42
|
+
},
|
|
43
|
+
redo: () => {
|
|
44
|
+
moveStep.redo();
|
|
45
|
+
triggerSegmentationDataModified(segmentationId);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
27
48
|
}
|
|
28
49
|
if (initializedData.segmentationVoxelManager !==
|
|
29
50
|
originalSegmentationVoxelManager ||
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSegmentation } from '../../../../stateManagement/segmentation/getSegmentation.js';
|
|
2
|
+
import { triggerSegmentationDataModified } from '../../../../stateManagement/segmentation/triggerSegmentationEvents.js';
|
|
2
3
|
import { collectCrossLayerEraseBindings, eraseLabelmapEditTransactionOverwrites, } from '../../../../stateManagement/segmentation/helpers/labelmapSegmentationState.js';
|
|
3
4
|
function collectCrossLayerEraseBindingsForOperation(operationData) {
|
|
4
5
|
const { segmentationId, labelmapId, overwriteSegmentIndices } = operationData;
|
|
@@ -12,12 +13,31 @@ function eraseCrossLayerOverwrites(operationData) {
|
|
|
12
13
|
if (!segmentation) {
|
|
13
14
|
return [];
|
|
14
15
|
}
|
|
16
|
+
const { memo } = operationData;
|
|
15
17
|
return eraseLabelmapEditTransactionOverwrites(segmentation, operationData.labelmapEditTransaction, {
|
|
16
18
|
viewport: operationData.viewport,
|
|
17
19
|
referenceImageData: operationData.segmentationImageData,
|
|
18
20
|
isInObject: operationData.isInObject,
|
|
19
21
|
isInObjectBoundsIJK: operationData.isInObjectBoundsIJK,
|
|
20
22
|
imageId: operationData.imageId,
|
|
23
|
+
crossLayerEraseCallback: memo
|
|
24
|
+
? ({ voxelManager, labelValue, indices }) => {
|
|
25
|
+
(memo.postSteps ||= []).push({
|
|
26
|
+
undo: () => {
|
|
27
|
+
for (const index of indices) {
|
|
28
|
+
voxelManager.setAtIndex(index, labelValue);
|
|
29
|
+
}
|
|
30
|
+
triggerSegmentationDataModified(operationData.segmentationId);
|
|
31
|
+
},
|
|
32
|
+
redo: () => {
|
|
33
|
+
for (const index of indices) {
|
|
34
|
+
voxelManager.setAtIndex(index, 0);
|
|
35
|
+
}
|
|
36
|
+
triggerSegmentationDataModified(operationData.segmentationId);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
: undefined,
|
|
21
41
|
});
|
|
22
42
|
}
|
|
23
43
|
export { collectCrossLayerEraseBindingsForOperation as collectCrossLayerEraseBindings, eraseCrossLayerOverwrites, };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Types } from '@cornerstonejs/core';
|
|
2
|
+
export declare function commitSliceMasksToLabelmapVolume({ labelmapVolume, sliceMasks, width: w, height: h, paintIndex, historyVoxelManager, }: {
|
|
3
|
+
labelmapVolume: Types.IImageVolume;
|
|
4
|
+
sliceMasks: Map<number, Uint8Array>;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
paintIndex: number;
|
|
8
|
+
historyVoxelManager?: Types.IVoxelManager<number>;
|
|
9
|
+
}): {
|
|
10
|
+
floodedPoints: Types.Point3[];
|
|
11
|
+
voxelCount: number;
|
|
12
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { cache, utilities } from '@cornerstonejs/core';
|
|
2
|
+
import { FLOOD_SLICE_FLAG_VISITED } from './floodFillSliceLazy.js';
|
|
3
|
+
const { VoxelManager } = utilities;
|
|
4
|
+
export function commitSliceMasksToLabelmapVolume({ labelmapVolume, sliceMasks, width: w, height: h, paintIndex, historyVoxelManager, }) {
|
|
5
|
+
const floodedPoints = [];
|
|
6
|
+
let voxelCount = 0;
|
|
7
|
+
const vm = labelmapVolume.voxelManager;
|
|
8
|
+
const historyWriter = historyVoxelManager;
|
|
9
|
+
const [labelmapWidth, labelmapHeight, depth] = labelmapVolume.dimensions;
|
|
10
|
+
if (labelmapWidth !== w || labelmapHeight !== h) {
|
|
11
|
+
throw new Error(`commitSliceMasksToLabelmapVolume: labelmap in-plane dimensions ` +
|
|
12
|
+
`${labelmapWidth}x${labelmapHeight} do not match mask dimensions ${w}x${h}`);
|
|
13
|
+
}
|
|
14
|
+
const frameSize = w * h;
|
|
15
|
+
const expectedLen = frameSize * depth;
|
|
16
|
+
const zs = Array.from(sliceMasks.keys()).sort((a, b) => a - b);
|
|
17
|
+
for (let zi = 0; zi < zs.length; zi++) {
|
|
18
|
+
const z = zs[zi];
|
|
19
|
+
if (z < 0 || z >= depth) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const flags = sliceMasks.get(z);
|
|
23
|
+
if (!flags || flags.length !== frameSize) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let sliceTouched = false;
|
|
27
|
+
let minX = Infinity;
|
|
28
|
+
let minY = Infinity;
|
|
29
|
+
let maxX = -Infinity;
|
|
30
|
+
let maxY = -Infinity;
|
|
31
|
+
const appendRunFlooded = (y, x0, x1) => {
|
|
32
|
+
for (let xi = x0; xi < x1; xi++) {
|
|
33
|
+
floodedPoints.push([xi, y, z]);
|
|
34
|
+
}
|
|
35
|
+
voxelCount += x1 - x0;
|
|
36
|
+
sliceTouched = true;
|
|
37
|
+
minX = Math.min(minX, x0);
|
|
38
|
+
maxX = Math.max(maxX, x1 - 1);
|
|
39
|
+
minY = Math.min(minY, y);
|
|
40
|
+
maxY = Math.max(maxY, y);
|
|
41
|
+
};
|
|
42
|
+
const denseScalar = !historyWriter &&
|
|
43
|
+
vm.scalarData &&
|
|
44
|
+
vm.scalarData.length >= expectedLen &&
|
|
45
|
+
typeof vm.scalarData.fill === 'function';
|
|
46
|
+
if (denseScalar) {
|
|
47
|
+
const base = z * frameSize;
|
|
48
|
+
const data = vm.scalarData;
|
|
49
|
+
for (let y = 0; y < h; y++) {
|
|
50
|
+
const row = y * w;
|
|
51
|
+
for (let x = 0; x < w;) {
|
|
52
|
+
const li = row + x;
|
|
53
|
+
if (!(flags[li] & FLOOD_SLICE_FLAG_VISITED)) {
|
|
54
|
+
x++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const x0 = x;
|
|
58
|
+
while (x < w && flags[row + x] & FLOOD_SLICE_FLAG_VISITED) {
|
|
59
|
+
x++;
|
|
60
|
+
}
|
|
61
|
+
data.fill(paintIndex, base + row + x0, base + row + x);
|
|
62
|
+
appendRunFlooded(y, x0, x);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const imageIds = labelmapVolume.imageIds;
|
|
68
|
+
const image = imageIds?.length && z < imageIds.length
|
|
69
|
+
? cache.getImage(imageIds[z])
|
|
70
|
+
: null;
|
|
71
|
+
const svm = image?.voxelManager;
|
|
72
|
+
if (!historyWriter &&
|
|
73
|
+
svm?.scalarData &&
|
|
74
|
+
svm.scalarData.length >= frameSize &&
|
|
75
|
+
typeof svm.scalarData.fill === 'function') {
|
|
76
|
+
const data = svm.scalarData;
|
|
77
|
+
for (let y = 0; y < h; y++) {
|
|
78
|
+
const row = y * w;
|
|
79
|
+
for (let x = 0; x < w;) {
|
|
80
|
+
const li = row + x;
|
|
81
|
+
if (!(flags[li] & FLOOD_SLICE_FLAG_VISITED)) {
|
|
82
|
+
x++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const x0 = x;
|
|
86
|
+
while (x < w && flags[row + x] & FLOOD_SLICE_FLAG_VISITED) {
|
|
87
|
+
x++;
|
|
88
|
+
}
|
|
89
|
+
data.fill(paintIndex, row + x0, row + x);
|
|
90
|
+
appendRunFlooded(y, x0, x);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
svm.modifiedSlices.add(z);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const writer = historyWriter ?? vm;
|
|
97
|
+
for (let y = 0; y < h; y++) {
|
|
98
|
+
const row = y * w;
|
|
99
|
+
for (let x = 0; x < w; x++) {
|
|
100
|
+
if (!(flags[row + x] & FLOOD_SLICE_FLAG_VISITED)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const index = z * frameSize + row + x;
|
|
104
|
+
writer.setAtIndex(index, paintIndex);
|
|
105
|
+
floodedPoints.push([x, y, z]);
|
|
106
|
+
voxelCount++;
|
|
107
|
+
sliceTouched = true;
|
|
108
|
+
minX = Math.min(minX, x);
|
|
109
|
+
maxX = Math.max(maxX, x);
|
|
110
|
+
minY = Math.min(minY, y);
|
|
111
|
+
maxY = Math.max(maxY, y);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (sliceTouched) {
|
|
117
|
+
vm.modifiedSlices.add(z);
|
|
118
|
+
if (Number.isFinite(minX) &&
|
|
119
|
+
Number.isFinite(minY) &&
|
|
120
|
+
Number.isFinite(maxX) &&
|
|
121
|
+
Number.isFinite(maxY)) {
|
|
122
|
+
VoxelManager.addBounds(vm.boundsIJK, [minX, minY, z]);
|
|
123
|
+
VoxelManager.addBounds(vm.boundsIJK, [maxX, maxY, z]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { floodedPoints, voxelCount };
|
|
128
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { imageLoader } from '@cornerstonejs/core';
|
|
2
|
+
export function createEnsureSliceLoadedForVolume(volume) {
|
|
3
|
+
const numSlices = volume.dimensions[2];
|
|
4
|
+
const imageIds = volume.imageIds;
|
|
5
|
+
if (!imageIds?.length) {
|
|
6
|
+
return async () => undefined;
|
|
7
|
+
}
|
|
8
|
+
const loadedSlices = new Set();
|
|
9
|
+
const inFlight = new Map();
|
|
10
|
+
return async function ensureSliceLoaded(z) {
|
|
11
|
+
if (!Number.isFinite(z) || z < 0 || z >= numSlices) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (loadedSlices.has(z)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const existing = inFlight.get(z);
|
|
18
|
+
if (existing) {
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
const imageId = imageIds[z];
|
|
22
|
+
if (!imageId) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const promise = imageLoader
|
|
26
|
+
.loadImage(imageId)
|
|
27
|
+
.then(() => {
|
|
28
|
+
loadedSlices.add(z);
|
|
29
|
+
})
|
|
30
|
+
.finally(() => {
|
|
31
|
+
inFlight.delete(z);
|
|
32
|
+
});
|
|
33
|
+
inFlight.set(z, promise);
|
|
34
|
+
return promise;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { utilities } from '@cornerstonejs/core';
|
|
2
2
|
import type { Types } from '@cornerstonejs/core';
|
|
3
|
+
export type LabelmapRestoreStep = {
|
|
4
|
+
undo: () => void;
|
|
5
|
+
redo: () => void;
|
|
6
|
+
};
|
|
3
7
|
export type LabelmapMemo = Types.Memo & {
|
|
4
8
|
segmentationVoxelManager: Types.IVoxelManager<number>;
|
|
5
9
|
voxelManager: Types.IVoxelManager<number>;
|
|
@@ -7,6 +11,8 @@ export type LabelmapMemo = Types.Memo & {
|
|
|
7
11
|
undoVoxelManager?: Types.IVoxelManager<number>;
|
|
8
12
|
memo?: LabelmapMemo;
|
|
9
13
|
id: string;
|
|
14
|
+
priorSteps?: LabelmapRestoreStep[];
|
|
15
|
+
postSteps?: LabelmapRestoreStep[];
|
|
10
16
|
};
|
|
11
17
|
export declare function createLabelmapMemo<T>(segmentationId: string, segmentationVoxelManager: Types.IVoxelManager<T>): {
|
|
12
18
|
segmentationId: string;
|
|
@@ -17,6 +23,7 @@ export declare function createLabelmapMemo<T>(segmentationId: string, segmentati
|
|
|
17
23
|
id: string;
|
|
18
24
|
operationType: string;
|
|
19
25
|
};
|
|
26
|
+
export declare function memoAsStep(memo: LabelmapMemo): LabelmapRestoreStep;
|
|
20
27
|
export declare function restoreMemo(isUndo?: boolean): void;
|
|
21
28
|
export declare function createRleMemo<T>(segmentationId: string, segmentationVoxelManager: Types.IVoxelManager<T>): {
|
|
22
29
|
segmentationId: string;
|
|
@@ -4,14 +4,46 @@ const { VoxelManager, RLEVoxelMap } = utilities;
|
|
|
4
4
|
export function createLabelmapMemo(segmentationId, segmentationVoxelManager) {
|
|
5
5
|
return createRleMemo(segmentationId, segmentationVoxelManager);
|
|
6
6
|
}
|
|
7
|
+
export function memoAsStep(memo) {
|
|
8
|
+
return {
|
|
9
|
+
undo: () => memo.restoreMemo(true),
|
|
10
|
+
redo: () => memo.restoreMemo(false),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
7
13
|
export function restoreMemo(isUndo) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const undo = isUndo !== false;
|
|
15
|
+
const priorSteps = this.priorSteps ?? [];
|
|
16
|
+
const postSteps = this.postSteps ?? [];
|
|
17
|
+
const restoreVoxelChanges = () => {
|
|
18
|
+
const { segmentationVoxelManager, undoVoxelManager, redoVoxelManager } = this;
|
|
19
|
+
const useVoxelManager = undo ? undoVoxelManager : redoVoxelManager;
|
|
20
|
+
if (!useVoxelManager) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
useVoxelManager.forEach(({ value, pointIJK }) => {
|
|
24
|
+
segmentationVoxelManager.setAtIJKPoint(pointIJK, value);
|
|
25
|
+
});
|
|
26
|
+
const slices = useVoxelManager.getArrayOfModifiedSlices();
|
|
27
|
+
triggerSegmentationDataModified(this.segmentationId, slices);
|
|
28
|
+
};
|
|
29
|
+
if (undo) {
|
|
30
|
+
for (let i = postSteps.length - 1; i >= 0; i--) {
|
|
31
|
+
postSteps[i].undo();
|
|
32
|
+
}
|
|
33
|
+
restoreVoxelChanges();
|
|
34
|
+
for (let i = priorSteps.length - 1; i >= 0; i--) {
|
|
35
|
+
priorSteps[i].undo();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const step of priorSteps) {
|
|
40
|
+
step.redo();
|
|
41
|
+
}
|
|
42
|
+
restoreVoxelChanges();
|
|
43
|
+
for (const step of postSteps) {
|
|
44
|
+
step.redo();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
15
47
|
}
|
|
16
48
|
export function createRleMemo(segmentationId, segmentationVoxelManager) {
|
|
17
49
|
const voxelManager = VoxelManager.createRLEHistoryVoxelManager(segmentationVoxelManager);
|
|
@@ -30,8 +62,9 @@ function commitMemo() {
|
|
|
30
62
|
if (this.redoVoxelManager) {
|
|
31
63
|
return true;
|
|
32
64
|
}
|
|
65
|
+
const hasSteps = !!(this.priorSteps?.length || this.postSteps?.length);
|
|
33
66
|
if (!this.voxelManager.modifiedSlices.size) {
|
|
34
|
-
return
|
|
67
|
+
return hasSteps;
|
|
35
68
|
}
|
|
36
69
|
const { segmentationVoxelManager } = this;
|
|
37
70
|
const undoVoxelManager = VoxelManager.createRLEHistoryVoxelManager(segmentationVoxelManager);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import IslandRemoval, { SegmentationEnum } from './islandRemoval.js';
|
|
3
|
+
export { SegmentationEnum };
|
|
4
|
+
export default class FloodFillIslandRemoval extends IslandRemoval {
|
|
5
|
+
private verboseLogging;
|
|
6
|
+
private usingPreviewLayer;
|
|
7
|
+
private internalFilledPoints;
|
|
8
|
+
constructor(options?: {
|
|
9
|
+
maxInternalRemove?: number;
|
|
10
|
+
fillInternalEdge?: boolean;
|
|
11
|
+
verboseLogging?: boolean;
|
|
12
|
+
});
|
|
13
|
+
initialize(viewport: any, segmentationVoxels: any, options: any): boolean;
|
|
14
|
+
private static countRleValueVolume;
|
|
15
|
+
floodFillSegmentIsland(): number;
|
|
16
|
+
removeExternalIslands(): number;
|
|
17
|
+
removeInternalIslands(): number[];
|
|
18
|
+
protected onInternalPointFilled(point: Types.Point3): void;
|
|
19
|
+
getInternalFilledPoints(): Types.Point3[];
|
|
20
|
+
}
|