@cornerstonejs/tools 2.6.5 → 2.7.1
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/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +2 -1
- package/dist/esm/tools/annotation/LengthTool.js +6 -3
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.d.ts +15 -0
- package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +40 -0
- package/dist/esm/tools/annotation/RegionSegmentTool.d.ts +21 -0
- package/dist/esm/tools/annotation/RegionSegmentTool.js +103 -0
- package/dist/esm/tools/annotation/WholeBodySegmentTool.d.ts +28 -0
- package/dist/esm/tools/annotation/WholeBodySegmentTool.js +188 -0
- package/dist/esm/tools/base/ContourSegmentationBaseTool.js +5 -46
- package/dist/esm/tools/base/GrowCutBaseTool.d.ts +49 -0
- package/dist/esm/tools/base/GrowCutBaseTool.js +106 -0
- package/dist/esm/tools/index.d.ts +4 -1
- package/dist/esm/tools/index.js +4 -1
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +2 -2
- package/dist/esm/utilities/getSphereBoundsInfo.d.ts +5 -2
- package/dist/esm/utilities/getSphereBoundsInfo.js +36 -13
- package/dist/esm/utilities/segmentation/createLabelmapVolumeForViewport.js +1 -1
- package/dist/esm/utilities/segmentation/getSVGStyleForSegment.d.ts +17 -0
- package/dist/esm/utilities/segmentation/getSVGStyleForSegment.js +68 -0
- package/dist/esm/utilities/segmentation/growCut/growCutShader.d.ts +2 -0
- package/dist/esm/utilities/segmentation/growCut/growCutShader.js +106 -0
- package/dist/esm/utilities/segmentation/growCut/index.d.ts +7 -0
- package/dist/esm/utilities/segmentation/growCut/index.js +4 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCut.d.ts +16 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCut.js +269 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.d.ts +17 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.js +111 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForSphere.d.ts +9 -0
- package/dist/esm/utilities/segmentation/growCut/runGrowCutForSphere.js +164 -0
- package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.d.ts +13 -0
- package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.js +176 -0
- package/dist/esm/utilities/segmentation/index.d.ts +2 -1
- package/dist/esm/utilities/segmentation/index.js +2 -1
- package/package.json +3 -3
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getEnabledElement, utilities as csUtils, cache, } from '@cornerstonejs/core';
|
|
2
|
+
import { BaseTool } from '../base';
|
|
3
|
+
import { SegmentationRepresentations } from '../../enums';
|
|
4
|
+
import { segmentIndex as segmentIndexController, state as segmentationState, activeSegmentation, } from '../../stateManagement/segmentation';
|
|
5
|
+
import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
|
|
6
|
+
import { getSVGStyleForSegment } from '../../utilities/segmentation/getSVGStyleForSegment';
|
|
7
|
+
const { transformWorldToIndex, transformIndexToWorld } = csUtils;
|
|
8
|
+
class GrowCutBaseTool extends BaseTool {
|
|
9
|
+
constructor(toolProps, defaultToolProps) {
|
|
10
|
+
super(toolProps, defaultToolProps);
|
|
11
|
+
}
|
|
12
|
+
async preMouseDownCallback(evt) {
|
|
13
|
+
const eventData = evt.detail;
|
|
14
|
+
const { element, currentPoints } = eventData;
|
|
15
|
+
const { world: worldPoint } = currentPoints;
|
|
16
|
+
const enabledElement = getEnabledElement(element);
|
|
17
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
18
|
+
const { viewUp } = viewport.getCamera();
|
|
19
|
+
const { segmentationId, segmentIndex, labelmapVolumeId, referencedVolumeId, } = this.getLabelmapSegmentationData(viewport);
|
|
20
|
+
if (!this._isOrthogonalView(viewport, referencedVolumeId)) {
|
|
21
|
+
throw new Error('Oblique view is not supported yet');
|
|
22
|
+
}
|
|
23
|
+
this.growCutData = {
|
|
24
|
+
metadata: {
|
|
25
|
+
...viewport.getViewReference({ points: [worldPoint] }),
|
|
26
|
+
viewUp,
|
|
27
|
+
},
|
|
28
|
+
segmentation: {
|
|
29
|
+
segmentationId,
|
|
30
|
+
segmentIndex,
|
|
31
|
+
labelmapVolumeId,
|
|
32
|
+
referencedVolumeId,
|
|
33
|
+
},
|
|
34
|
+
viewportId: viewport.id,
|
|
35
|
+
renderingEngineId: renderingEngine.id,
|
|
36
|
+
};
|
|
37
|
+
evt.preventDefault();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
async getGrowCutLabelmap() {
|
|
41
|
+
throw new Error('Not implemented');
|
|
42
|
+
}
|
|
43
|
+
async runGrowCut() {
|
|
44
|
+
const { segmentation: { segmentationId, segmentIndex, labelmapVolumeId }, } = this.growCutData;
|
|
45
|
+
const labelmap = cache.getVolume(labelmapVolumeId);
|
|
46
|
+
const growcutLabelmap = await this.getGrowCutLabelmap();
|
|
47
|
+
this.applyGrowCutLabelmap(segmentationId, segmentIndex, labelmap, growcutLabelmap);
|
|
48
|
+
}
|
|
49
|
+
applyGrowCutLabelmap(segmentationId, segmentIndex, targetLabelmap, sourceLabelmap) {
|
|
50
|
+
const srcLabelmapData = sourceLabelmap.voxelManager.getCompleteScalarDataArray();
|
|
51
|
+
const targetLabelmapData = targetLabelmap.voxelManager.getCompleteScalarDataArray();
|
|
52
|
+
const [srcColumns, srcRows, srcNumSlices] = sourceLabelmap.dimensions;
|
|
53
|
+
const [tgtColumns, tgtRows] = targetLabelmap.dimensions;
|
|
54
|
+
const srcPixelsPerSlice = srcColumns * srcRows;
|
|
55
|
+
const tgtPixelsPerSlice = tgtColumns * tgtRows;
|
|
56
|
+
for (let srcSlice = 0; srcSlice < srcNumSlices; srcSlice++) {
|
|
57
|
+
for (let srcRow = 0; srcRow < srcRows; srcRow++) {
|
|
58
|
+
const srcRowIJK = [0, srcRow, srcSlice];
|
|
59
|
+
const rowVoxelWorld = transformIndexToWorld(sourceLabelmap.imageData, srcRowIJK);
|
|
60
|
+
const tgtRowIJK = transformWorldToIndex(targetLabelmap.imageData, rowVoxelWorld);
|
|
61
|
+
const [tgtColumn, tgtRow, tgtSlice] = tgtRowIJK;
|
|
62
|
+
const srcOffset = srcRow * srcColumns + srcSlice * srcPixelsPerSlice;
|
|
63
|
+
const tgtOffset = tgtColumn + tgtRow * tgtColumns + tgtSlice * tgtPixelsPerSlice;
|
|
64
|
+
for (let column = 0; column < srcColumns; column++) {
|
|
65
|
+
targetLabelmapData[tgtOffset + column] =
|
|
66
|
+
srcLabelmapData[srcOffset + column] === segmentIndex
|
|
67
|
+
? segmentIndex
|
|
68
|
+
: 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
targetLabelmap.voxelManager.setCompleteScalarDataArray(targetLabelmapData);
|
|
73
|
+
triggerSegmentationDataModified(segmentationId);
|
|
74
|
+
}
|
|
75
|
+
getSegmentStyle({ segmentationId, viewportId, segmentIndex }) {
|
|
76
|
+
return getSVGStyleForSegment({
|
|
77
|
+
segmentationId,
|
|
78
|
+
segmentIndex,
|
|
79
|
+
viewportId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
getLabelmapSegmentationData(viewport) {
|
|
83
|
+
const { segmentationId } = activeSegmentation.getActiveSegmentation(viewport.id);
|
|
84
|
+
const segmentIndex = segmentIndexController.getActiveSegmentIndex(segmentationId);
|
|
85
|
+
const { representationData } = segmentationState.getSegmentation(segmentationId);
|
|
86
|
+
const labelmapData = representationData[SegmentationRepresentations.Labelmap];
|
|
87
|
+
const { volumeId: labelmapVolumeId, referencedVolumeId } = labelmapData;
|
|
88
|
+
return {
|
|
89
|
+
segmentationId,
|
|
90
|
+
segmentIndex,
|
|
91
|
+
labelmapVolumeId,
|
|
92
|
+
referencedVolumeId,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
_isOrthogonalView(viewport, referencedVolumeId) {
|
|
96
|
+
const volume = cache.getVolume(referencedVolumeId);
|
|
97
|
+
const volumeImageData = volume.imageData;
|
|
98
|
+
const camera = viewport.getCamera();
|
|
99
|
+
const { ijkVecColDir, ijkVecSliceDir } = csUtils.getVolumeDirectionVectors(volumeImageData, camera);
|
|
100
|
+
return [ijkVecColDir, ijkVecSliceDir].every((vec) => csUtils.isEqual(Math.abs(vec[0]), 1) ||
|
|
101
|
+
csUtils.isEqual(Math.abs(vec[1]), 1) ||
|
|
102
|
+
csUtils.isEqual(Math.abs(vec[2]), 1));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
GrowCutBaseTool.toolName = 'GrowCutBaseTool';
|
|
106
|
+
export default GrowCutBaseTool;
|
|
@@ -38,6 +38,9 @@ import CobbAngleTool from './annotation/CobbAngleTool';
|
|
|
38
38
|
import UltrasoundDirectionalTool from './annotation/UltrasoundDirectionalTool';
|
|
39
39
|
import KeyImageTool from './annotation/KeyImageTool';
|
|
40
40
|
import AnnotationEraserTool from './AnnotationEraserTool';
|
|
41
|
+
import RegionSegmentTool from './annotation/RegionSegmentTool';
|
|
42
|
+
import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool';
|
|
43
|
+
import WholeBodySegmentTool from './annotation/WholeBodySegmentTool';
|
|
41
44
|
import RectangleScissorsTool from './segmentation/RectangleScissorsTool';
|
|
42
45
|
import CircleScissorsTool from './segmentation/CircleScissorsTool';
|
|
43
46
|
import SphereScissorsTool from './segmentation/SphereScissorsTool';
|
|
@@ -48,4 +51,4 @@ import BrushTool from './segmentation/BrushTool';
|
|
|
48
51
|
import PaintFillTool from './segmentation/PaintFillTool';
|
|
49
52
|
import OrientationMarkerTool from './OrientationMarkerTool';
|
|
50
53
|
import SegmentSelectTool from './segmentation/SegmentSelectTool';
|
|
51
|
-
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, };
|
|
54
|
+
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, };
|
package/dist/esm/tools/index.js
CHANGED
|
@@ -38,6 +38,9 @@ import CobbAngleTool from './annotation/CobbAngleTool';
|
|
|
38
38
|
import UltrasoundDirectionalTool from './annotation/UltrasoundDirectionalTool';
|
|
39
39
|
import KeyImageTool from './annotation/KeyImageTool';
|
|
40
40
|
import AnnotationEraserTool from './AnnotationEraserTool';
|
|
41
|
+
import RegionSegmentTool from './annotation/RegionSegmentTool';
|
|
42
|
+
import RegionSegmentPlusTool from './annotation/RegionSegmentPlusTool';
|
|
43
|
+
import WholeBodySegmentTool from './annotation/WholeBodySegmentTool';
|
|
41
44
|
import RectangleScissorsTool from './segmentation/RectangleScissorsTool';
|
|
42
45
|
import CircleScissorsTool from './segmentation/CircleScissorsTool';
|
|
43
46
|
import SphereScissorsTool from './segmentation/SphereScissorsTool';
|
|
@@ -48,4 +51,4 @@ import BrushTool from './segmentation/BrushTool';
|
|
|
48
51
|
import PaintFillTool from './segmentation/PaintFillTool';
|
|
49
52
|
import OrientationMarkerTool from './OrientationMarkerTool';
|
|
50
53
|
import SegmentSelectTool from './segmentation/SegmentSelectTool';
|
|
51
|
-
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, };
|
|
54
|
+
export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, };
|
|
@@ -5,7 +5,7 @@ import compositions from './compositions';
|
|
|
5
5
|
import StrategyCallbacks from '../../../enums/StrategyCallbacks';
|
|
6
6
|
import { createEllipseInPoint } from './fillCircle';
|
|
7
7
|
const { transformWorldToIndex } = csUtils;
|
|
8
|
-
import {
|
|
8
|
+
import { getSphereBoundsInfoFromViewport } from '../../../utilities/getSphereBoundsInfo';
|
|
9
9
|
const sphereComposition = {
|
|
10
10
|
[StrategyCallbacks.Initialize]: (operationData) => {
|
|
11
11
|
const { points, viewport, segmentationImageData } = operationData;
|
|
@@ -19,7 +19,7 @@ const sphereComposition = {
|
|
|
19
19
|
vec3.scale(center, center, 1 / points.length);
|
|
20
20
|
operationData.centerWorld = center;
|
|
21
21
|
operationData.centerIJK = transformWorldToIndex(segmentationImageData, center);
|
|
22
|
-
const { boundsIJK: newBoundsIJK, topLeftWorld, bottomRightWorld, } =
|
|
22
|
+
const { boundsIJK: newBoundsIJK, topLeftWorld, bottomRightWorld, } = getSphereBoundsInfoFromViewport(points.slice(0, 2), segmentationImageData, viewport);
|
|
23
23
|
operationData.isInObjectBoundsIJK = newBoundsIJK;
|
|
24
24
|
operationData.isInObject = createEllipseInPoint({
|
|
25
25
|
topLeftWorld,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
3
3
|
import type { BoundsIJK } from '../types';
|
|
4
|
-
|
|
4
|
+
type SphereBoundsInfo = {
|
|
5
5
|
boundsIJK: BoundsIJK;
|
|
6
6
|
centerWorld: Types.Point3;
|
|
7
7
|
radiusWorld: number;
|
|
8
8
|
topLeftWorld: Types.Point3;
|
|
9
9
|
bottomRightWorld: Types.Point3;
|
|
10
10
|
};
|
|
11
|
-
|
|
11
|
+
declare function getSphereBoundsInfo(circlePoints: [Types.Point3, Types.Point3], imageData: vtkImageData): SphereBoundsInfo;
|
|
12
|
+
declare function getSphereBoundsInfoFromViewport(circlePoints: [Types.Point3, Types.Point3], imageData: vtkImageData, viewport: any): SphereBoundsInfo;
|
|
13
|
+
export { getSphereBoundsInfo, getSphereBoundsInfoFromViewport };
|
|
14
|
+
export type { SphereBoundsInfo };
|
|
@@ -2,14 +2,11 @@ import { utilities as csUtils } from '@cornerstonejs/core';
|
|
|
2
2
|
import { vec3 } from 'gl-matrix';
|
|
3
3
|
import { getBoundingBoxAroundShapeIJK } from './boundingBox';
|
|
4
4
|
const { transformWorldToIndex } = csUtils;
|
|
5
|
-
function
|
|
5
|
+
function _getSphereBoundsInfo(circlePoints, imageData, directionVectors) {
|
|
6
6
|
const [bottom, top] = circlePoints;
|
|
7
7
|
const centerWorld = vec3.fromValues((bottom[0] + top[0]) / 2, (bottom[1] + top[1]) / 2, (bottom[2] + top[2]) / 2);
|
|
8
8
|
const radiusWorld = vec3.distance(bottom, top) / 2;
|
|
9
|
-
|
|
10
|
-
throw new Error('viewport is required in order to calculate the sphere bounds');
|
|
11
|
-
}
|
|
12
|
-
const { boundsIJK, topLeftWorld, bottomRightWorld } = _computeBoundsIJKWithCamera(imageData, viewport, circlePoints, centerWorld, radiusWorld);
|
|
9
|
+
const { boundsIJK, topLeftWorld, bottomRightWorld } = _computeBoundsIJK(imageData, directionVectors, circlePoints, centerWorld, radiusWorld);
|
|
13
10
|
return {
|
|
14
11
|
boundsIJK,
|
|
15
12
|
centerWorld: centerWorld,
|
|
@@ -18,24 +15,50 @@ function getSphereBoundsInfo(circlePoints, imageData, viewport) {
|
|
|
18
15
|
bottomRightWorld: bottomRightWorld,
|
|
19
16
|
};
|
|
20
17
|
}
|
|
21
|
-
function
|
|
22
|
-
const
|
|
23
|
-
const
|
|
18
|
+
function getSphereBoundsInfo(circlePoints, imageData) {
|
|
19
|
+
const direction = imageData.getDirection();
|
|
20
|
+
const rowCosine = vec3.fromValues(direction[0], direction[1], direction[2]);
|
|
21
|
+
const columnCosine = vec3.fromValues(direction[3], direction[4], direction[5]);
|
|
22
|
+
const scanAxis = vec3.fromValues(direction[6], direction[7], direction[8]);
|
|
23
|
+
const viewPlaneNormal = vec3.negate(vec3.create(), scanAxis);
|
|
24
|
+
const directionVectors = {
|
|
25
|
+
row: rowCosine,
|
|
26
|
+
column: columnCosine,
|
|
27
|
+
normal: viewPlaneNormal,
|
|
28
|
+
};
|
|
29
|
+
return _getSphereBoundsInfo(circlePoints, imageData, directionVectors);
|
|
30
|
+
}
|
|
31
|
+
function getSphereBoundsInfoFromViewport(circlePoints, imageData, viewport) {
|
|
32
|
+
if (!viewport) {
|
|
33
|
+
throw new Error('viewport is required in order to calculate the sphere bounds');
|
|
34
|
+
}
|
|
24
35
|
const camera = viewport.getCamera();
|
|
25
36
|
const viewUp = vec3.fromValues(camera.viewUp[0], camera.viewUp[1], camera.viewUp[2]);
|
|
26
37
|
const viewPlaneNormal = vec3.fromValues(camera.viewPlaneNormal[0], camera.viewPlaneNormal[1], camera.viewPlaneNormal[2]);
|
|
27
38
|
const viewRight = vec3.create();
|
|
28
39
|
vec3.cross(viewRight, viewUp, viewPlaneNormal);
|
|
40
|
+
const directionVectors = {
|
|
41
|
+
row: viewRight,
|
|
42
|
+
normal: viewPlaneNormal,
|
|
43
|
+
column: vec3.negate(vec3.create(), viewUp),
|
|
44
|
+
};
|
|
45
|
+
return _getSphereBoundsInfo(circlePoints, imageData, directionVectors);
|
|
46
|
+
}
|
|
47
|
+
function _computeBoundsIJK(imageData, directionVectors, circlePoints, centerWorld, radiusWorld) {
|
|
48
|
+
const dimensions = imageData.getDimensions();
|
|
49
|
+
const { row: rowCosine, column: columnCosine, normal: vecNormal, } = directionVectors;
|
|
29
50
|
const topLeftWorld = vec3.create();
|
|
30
51
|
const bottomRightWorld = vec3.create();
|
|
31
|
-
vec3.scaleAndAdd(topLeftWorld,
|
|
32
|
-
vec3.scaleAndAdd(bottomRightWorld,
|
|
33
|
-
vec3.scaleAndAdd(topLeftWorld, topLeftWorld,
|
|
34
|
-
vec3.scaleAndAdd(bottomRightWorld, bottomRightWorld,
|
|
52
|
+
vec3.scaleAndAdd(topLeftWorld, centerWorld, vecNormal, radiusWorld);
|
|
53
|
+
vec3.scaleAndAdd(bottomRightWorld, centerWorld, vecNormal, -radiusWorld);
|
|
54
|
+
vec3.scaleAndAdd(topLeftWorld, topLeftWorld, columnCosine, -radiusWorld);
|
|
55
|
+
vec3.scaleAndAdd(bottomRightWorld, bottomRightWorld, columnCosine, radiusWorld);
|
|
56
|
+
vec3.scaleAndAdd(topLeftWorld, topLeftWorld, rowCosine, -radiusWorld);
|
|
57
|
+
vec3.scaleAndAdd(bottomRightWorld, bottomRightWorld, rowCosine, radiusWorld);
|
|
35
58
|
const topLeftIJK = transformWorldToIndex(imageData, topLeftWorld);
|
|
36
59
|
const bottomRightIJK = transformWorldToIndex(imageData, bottomRightWorld);
|
|
37
60
|
const pointsIJK = circlePoints.map((p) => transformWorldToIndex(imageData, p));
|
|
38
61
|
const boundsIJK = getBoundingBoxAroundShapeIJK([topLeftIJK, bottomRightIJK, ...pointsIJK], dimensions);
|
|
39
62
|
return { boundsIJK, topLeftWorld, bottomRightWorld };
|
|
40
63
|
}
|
|
41
|
-
export { getSphereBoundsInfo };
|
|
64
|
+
export { getSphereBoundsInfo, getSphereBoundsInfoFromViewport };
|
|
@@ -20,7 +20,7 @@ export default async function createLabelmapVolumeForViewport(input) {
|
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
const volumeId = viewport.getVolumeId();
|
|
23
|
-
|
|
23
|
+
volumeLoader.createAndCacheDerivedLabelmapVolume(volumeId, {
|
|
24
24
|
volumeId: segmentationId,
|
|
25
25
|
});
|
|
26
26
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SVGStyleForSegmentParams {
|
|
2
|
+
segmentationId: string;
|
|
3
|
+
segmentIndex: number;
|
|
4
|
+
viewportId: string;
|
|
5
|
+
autoGenerated?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function getSVGStyleForSegment({ segmentationId, segmentIndex, viewportId, autoGenerated, }: SVGStyleForSegmentParams): {
|
|
8
|
+
color: string;
|
|
9
|
+
fillColor: string;
|
|
10
|
+
lineWidth: number;
|
|
11
|
+
fillOpacity: number;
|
|
12
|
+
lineDash: any;
|
|
13
|
+
textbox: {
|
|
14
|
+
color: string;
|
|
15
|
+
};
|
|
16
|
+
visibility: boolean;
|
|
17
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { SegmentationRepresentations } from '../../enums';
|
|
2
|
+
import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/segmentationColor';
|
|
3
|
+
import { getActiveSegmentation } from '../../stateManagement/segmentation/getActiveSegmentation';
|
|
4
|
+
import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
|
|
5
|
+
import { getSegmentationRepresentationVisibility } from '../../stateManagement/segmentation/getSegmentationRepresentationVisibility';
|
|
6
|
+
import { internalGetHiddenSegmentIndices } from '../../stateManagement/segmentation/helpers/internalGetHiddenSegmentIndices';
|
|
7
|
+
import { segmentationStyle } from '../../stateManagement/segmentation/SegmentationStyle';
|
|
8
|
+
export function getSVGStyleForSegment({ segmentationId, segmentIndex, viewportId, autoGenerated = false, }) {
|
|
9
|
+
const segmentColor = getSegmentIndexColor(viewportId, segmentationId, segmentIndex);
|
|
10
|
+
const segmentationVisible = getSegmentationRepresentationVisibility(viewportId, {
|
|
11
|
+
segmentationId,
|
|
12
|
+
type: SegmentationRepresentations.Contour,
|
|
13
|
+
});
|
|
14
|
+
const activeSegmentation = getActiveSegmentation(viewportId);
|
|
15
|
+
const isActive = activeSegmentation?.segmentationId === segmentationId;
|
|
16
|
+
const style = segmentationStyle.getStyle({
|
|
17
|
+
viewportId,
|
|
18
|
+
segmentationId,
|
|
19
|
+
type: SegmentationRepresentations.Contour,
|
|
20
|
+
segmentIndex,
|
|
21
|
+
});
|
|
22
|
+
const mergedConfig = style;
|
|
23
|
+
let lineWidth = 1;
|
|
24
|
+
let lineDash = undefined;
|
|
25
|
+
let lineOpacity = 1;
|
|
26
|
+
let fillOpacity = 0;
|
|
27
|
+
if (autoGenerated) {
|
|
28
|
+
lineWidth = mergedConfig.outlineWidthAutoGenerated ?? lineWidth;
|
|
29
|
+
lineDash = mergedConfig.outlineDashAutoGenerated ?? lineDash;
|
|
30
|
+
lineOpacity = mergedConfig.outlineOpacity ?? lineOpacity;
|
|
31
|
+
fillOpacity = mergedConfig.fillAlphaAutoGenerated ?? fillOpacity;
|
|
32
|
+
}
|
|
33
|
+
else if (isActive) {
|
|
34
|
+
lineWidth = mergedConfig.outlineWidth ?? lineWidth;
|
|
35
|
+
lineDash = mergedConfig.outlineDash ?? lineDash;
|
|
36
|
+
lineOpacity = mergedConfig.outlineOpacity ?? lineOpacity;
|
|
37
|
+
fillOpacity = mergedConfig.fillAlpha ?? fillOpacity;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lineWidth = mergedConfig.outlineWidthInactive ?? lineWidth;
|
|
41
|
+
lineDash = mergedConfig.outlineDashInactive ?? lineDash;
|
|
42
|
+
lineOpacity = mergedConfig.outlineOpacityInactive ?? lineOpacity;
|
|
43
|
+
fillOpacity = mergedConfig.fillAlphaInactive ?? fillOpacity;
|
|
44
|
+
}
|
|
45
|
+
if (getActiveSegmentIndex(segmentationId) === segmentIndex) {
|
|
46
|
+
lineWidth += mergedConfig.activeSegmentOutlineWidthDelta;
|
|
47
|
+
}
|
|
48
|
+
lineWidth = mergedConfig.renderOutline ? lineWidth : 0;
|
|
49
|
+
fillOpacity = mergedConfig.renderFill ? fillOpacity : 0;
|
|
50
|
+
const color = `rgba(${segmentColor[0]}, ${segmentColor[1]}, ${segmentColor[2]}, ${lineOpacity})`;
|
|
51
|
+
const fillColor = `rgb(${segmentColor[0]}, ${segmentColor[1]}, ${segmentColor[2]})`;
|
|
52
|
+
const hiddenSegments = internalGetHiddenSegmentIndices(viewportId, {
|
|
53
|
+
segmentationId,
|
|
54
|
+
type: SegmentationRepresentations.Contour,
|
|
55
|
+
});
|
|
56
|
+
const isVisible = !hiddenSegments.has(segmentIndex);
|
|
57
|
+
return {
|
|
58
|
+
color,
|
|
59
|
+
fillColor,
|
|
60
|
+
lineWidth,
|
|
61
|
+
fillOpacity,
|
|
62
|
+
lineDash,
|
|
63
|
+
textbox: {
|
|
64
|
+
color,
|
|
65
|
+
},
|
|
66
|
+
visibility: segmentationVisible && isVisible,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const shader = "\nconst MAX_STRENGTH = 65535f;\n\n// Workgroup soze - X*Y*Z must be multiple of 32 for better performance\n// otherwise warps are sub allocated and some threads will not process anything\noverride workGroupSizeX = 1u;\noverride workGroupSizeY = 1u;\noverride workGroupSizeZ = 1u;\n\n// Compare the current voxel to neighbors using a 9x9x9 window\noverride windowSize = 9i;\n\nstruct Params {\n size: vec3u,\n iteration: u32,\n}\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage> volumePixelData: array<f32>;\n@group(0) @binding(2) var<storage, read_write> labelmap: array<u32>;\n@group(0) @binding(3) var<storage, read_write> strengthData: array<f32>;\n@group(0) @binding(4) var<storage> prevLabelmap: array<u32>;\n@group(0) @binding(5) var<storage> prevStrengthData: array<f32>;\n@group(0) @binding(6) var<storage, read_write> updatedVoxelsCounter: array<atomic<u32>>;\n\nfn getPixelIndex(ijkPos: vec3u) -> u32 {\n let numPixelsPerSlice = params.size.x * params.size.y;\n return ijkPos.x + ijkPos.y * params.size.x + ijkPos.z * numPixelsPerSlice;\n}\n\n@compute @workgroup_size(workGroupSizeX, workGroupSizeY, workGroupSizeZ)\nfn main(\n @builtin(global_invocation_id) globalId: vec3u,\n) {\n // Make sure it will not get out of bounds for volume with sizes that\n // are not multiple of workGroupSize\n if (\n globalId.x >= params.size.x ||\n globalId.y >= params.size.y ||\n globalId.z >= params.size.z\n ) {\n return;\n }\n\n let currentCoord = vec3i(globalId);\n let currentPixelIndex = getPixelIndex(globalId);\n\n let numPixels = arrayLength(&volumePixelData);\n let currentPixelValue = volumePixelData[currentPixelIndex];\n\n if (params.iteration == 0) {\n // All non-zero initial labels are given maximum strength\n strengthData[currentPixelIndex] = select(MAX_STRENGTH, 0., labelmap[currentPixelIndex] == 0);\n return;\n }\n\n // It should at least copy the values from previous state\n var newLabel = prevLabelmap[currentPixelIndex];\n var newStrength = prevStrengthData[currentPixelIndex];\n\n let window = i32(ceil(f32(windowSize - 1) * .5));\n let minWindow = -1i * window;\n let maxWindow = 1i * window;\n\n for (var k = minWindow; k <= maxWindow; k++) {\n for (var j = minWindow; j <= maxWindow; j++) {\n for (var i = minWindow; i <= maxWindow; i++) {\n // Skip current voxel\n if (i == 0 && j == 0 && k == 0) {\n continue;\n }\n\n let neighborCoord = currentCoord + vec3i(i, j, k);\n\n // Boundary conditions. Do not grow outside of the volume\n if (\n neighborCoord.x < 0i || neighborCoord.x >= i32(params.size.x) ||\n neighborCoord.y < 0i || neighborCoord.y >= i32(params.size.y) ||\n neighborCoord.z < 0i || neighborCoord.z >= i32(params.size.z)\n ) {\n continue;\n }\n\n let neighborIndex = getPixelIndex(vec3u(neighborCoord));\n let neighborPixelValue = volumePixelData[neighborIndex];\n let prevNeighborStrength = prevStrengthData[neighborIndex];\n let strengthCost = abs(neighborPixelValue - currentPixelValue);\n let takeoverStrength = prevNeighborStrength - strengthCost;\n\n if (takeoverStrength > newStrength) {\n newLabel = prevLabelmap[neighborIndex];\n newStrength = takeoverStrength;\n }\n }\n }\n }\n\n if (labelmap[currentPixelIndex] != newLabel) {\n atomicAdd(&updatedVoxelsCounter[params.iteration], 1u);\n }\n\n labelmap[currentPixelIndex] = newLabel;\n strengthData[currentPixelIndex] = newStrength;\n}\n";
|
|
2
|
+
export default shader;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const shader = `
|
|
2
|
+
const MAX_STRENGTH = 65535f;
|
|
3
|
+
|
|
4
|
+
// Workgroup soze - X*Y*Z must be multiple of 32 for better performance
|
|
5
|
+
// otherwise warps are sub allocated and some threads will not process anything
|
|
6
|
+
override workGroupSizeX = 1u;
|
|
7
|
+
override workGroupSizeY = 1u;
|
|
8
|
+
override workGroupSizeZ = 1u;
|
|
9
|
+
|
|
10
|
+
// Compare the current voxel to neighbors using a 9x9x9 window
|
|
11
|
+
override windowSize = 9i;
|
|
12
|
+
|
|
13
|
+
struct Params {
|
|
14
|
+
size: vec3u,
|
|
15
|
+
iteration: u32,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
19
|
+
@group(0) @binding(1) var<storage> volumePixelData: array<f32>;
|
|
20
|
+
@group(0) @binding(2) var<storage, read_write> labelmap: array<u32>;
|
|
21
|
+
@group(0) @binding(3) var<storage, read_write> strengthData: array<f32>;
|
|
22
|
+
@group(0) @binding(4) var<storage> prevLabelmap: array<u32>;
|
|
23
|
+
@group(0) @binding(5) var<storage> prevStrengthData: array<f32>;
|
|
24
|
+
@group(0) @binding(6) var<storage, read_write> updatedVoxelsCounter: array<atomic<u32>>;
|
|
25
|
+
|
|
26
|
+
fn getPixelIndex(ijkPos: vec3u) -> u32 {
|
|
27
|
+
let numPixelsPerSlice = params.size.x * params.size.y;
|
|
28
|
+
return ijkPos.x + ijkPos.y * params.size.x + ijkPos.z * numPixelsPerSlice;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@compute @workgroup_size(workGroupSizeX, workGroupSizeY, workGroupSizeZ)
|
|
32
|
+
fn main(
|
|
33
|
+
@builtin(global_invocation_id) globalId: vec3u,
|
|
34
|
+
) {
|
|
35
|
+
// Make sure it will not get out of bounds for volume with sizes that
|
|
36
|
+
// are not multiple of workGroupSize
|
|
37
|
+
if (
|
|
38
|
+
globalId.x >= params.size.x ||
|
|
39
|
+
globalId.y >= params.size.y ||
|
|
40
|
+
globalId.z >= params.size.z
|
|
41
|
+
) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let currentCoord = vec3i(globalId);
|
|
46
|
+
let currentPixelIndex = getPixelIndex(globalId);
|
|
47
|
+
|
|
48
|
+
let numPixels = arrayLength(&volumePixelData);
|
|
49
|
+
let currentPixelValue = volumePixelData[currentPixelIndex];
|
|
50
|
+
|
|
51
|
+
if (params.iteration == 0) {
|
|
52
|
+
// All non-zero initial labels are given maximum strength
|
|
53
|
+
strengthData[currentPixelIndex] = select(MAX_STRENGTH, 0., labelmap[currentPixelIndex] == 0);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// It should at least copy the values from previous state
|
|
58
|
+
var newLabel = prevLabelmap[currentPixelIndex];
|
|
59
|
+
var newStrength = prevStrengthData[currentPixelIndex];
|
|
60
|
+
|
|
61
|
+
let window = i32(ceil(f32(windowSize - 1) * .5));
|
|
62
|
+
let minWindow = -1i * window;
|
|
63
|
+
let maxWindow = 1i * window;
|
|
64
|
+
|
|
65
|
+
for (var k = minWindow; k <= maxWindow; k++) {
|
|
66
|
+
for (var j = minWindow; j <= maxWindow; j++) {
|
|
67
|
+
for (var i = minWindow; i <= maxWindow; i++) {
|
|
68
|
+
// Skip current voxel
|
|
69
|
+
if (i == 0 && j == 0 && k == 0) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let neighborCoord = currentCoord + vec3i(i, j, k);
|
|
74
|
+
|
|
75
|
+
// Boundary conditions. Do not grow outside of the volume
|
|
76
|
+
if (
|
|
77
|
+
neighborCoord.x < 0i || neighborCoord.x >= i32(params.size.x) ||
|
|
78
|
+
neighborCoord.y < 0i || neighborCoord.y >= i32(params.size.y) ||
|
|
79
|
+
neighborCoord.z < 0i || neighborCoord.z >= i32(params.size.z)
|
|
80
|
+
) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let neighborIndex = getPixelIndex(vec3u(neighborCoord));
|
|
85
|
+
let neighborPixelValue = volumePixelData[neighborIndex];
|
|
86
|
+
let prevNeighborStrength = prevStrengthData[neighborIndex];
|
|
87
|
+
let strengthCost = abs(neighborPixelValue - currentPixelValue);
|
|
88
|
+
let takeoverStrength = prevNeighborStrength - strengthCost;
|
|
89
|
+
|
|
90
|
+
if (takeoverStrength > newStrength) {
|
|
91
|
+
newLabel = prevLabelmap[neighborIndex];
|
|
92
|
+
newStrength = takeoverStrength;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (labelmap[currentPixelIndex] != newLabel) {
|
|
99
|
+
atomicAdd(&updatedVoxelsCounter[params.iteration], 1u);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
labelmap[currentPixelIndex] = newLabel;
|
|
103
|
+
strengthData[currentPixelIndex] = newStrength;
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
export default shader;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { run } from './runGrowCut';
|
|
2
|
+
export { runGrowCutForSphere } from './runGrowCutForSphere';
|
|
3
|
+
export { runGrowCutForBoundingBox } from './runGrowCutForBoundingBox';
|
|
4
|
+
export { runOneClickGrowCut } from './runOneClickGrowCut';
|
|
5
|
+
export type { SphereInfo, GrowCutSphereOptions } from './runGrowCutForSphere';
|
|
6
|
+
export type { GrowCutBoundingBoxOptions } from './runGrowCutForBoundingBox';
|
|
7
|
+
export type { GrowCutOneClickOptions } from './runOneClickGrowCut';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type GrowCutOptions = {
|
|
2
|
+
maxProcessingTime?: number;
|
|
3
|
+
windowSize?: number;
|
|
4
|
+
positiveSeedValue?: number;
|
|
5
|
+
negativeSeedValue?: number;
|
|
6
|
+
positiveSeedVariance?: number;
|
|
7
|
+
negativeSeedVariance?: number;
|
|
8
|
+
inspection?: {
|
|
9
|
+
numCyclesInterval?: number;
|
|
10
|
+
numCyclesBelowThreashold?: number;
|
|
11
|
+
threshold?: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
declare function runGrowCut(referenceVolumeId: string, labelmapVolumeId: string, options?: GrowCutOptions): Promise<void>;
|
|
15
|
+
export { runGrowCut as default, runGrowCut as run };
|
|
16
|
+
export type { GrowCutOptions };
|