@cornerstonejs/tools 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/drawingSvg/drawPolyline.d.ts +2 -0
- package/dist/cjs/drawingSvg/drawPolyline.js +5 -2
- package/dist/cjs/drawingSvg/drawPolyline.js.map +1 -1
- package/dist/cjs/enums/SegmentationRepresentations.d.ts +2 -1
- package/dist/cjs/enums/SegmentationRepresentations.js +1 -0
- package/dist/cjs/enums/SegmentationRepresentations.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/stateManagement/segmentation/addSegmentationRepresentations.js +5 -0
- package/dist/cjs/stateManagement/segmentation/addSegmentationRepresentations.js.map +1 -1
- package/dist/cjs/tools/SegmentationIntersectionTool.d.ts +18 -0
- package/dist/cjs/tools/SegmentationIntersectionTool.js +184 -0
- package/dist/cjs/tools/SegmentationIntersectionTool.js.map +1 -0
- package/dist/cjs/tools/displayTools/Contour/addContourSetsToElement.js.map +1 -1
- package/dist/cjs/tools/displayTools/SegmentationDisplayTool.js +7 -3
- package/dist/cjs/tools/displayTools/SegmentationDisplayTool.js.map +1 -1
- package/dist/cjs/tools/displayTools/Surface/addSurfaceToElement.d.ts +2 -0
- package/dist/cjs/tools/displayTools/Surface/addSurfaceToElement.js +84 -0
- package/dist/cjs/tools/displayTools/Surface/addSurfaceToElement.js.map +1 -0
- package/dist/cjs/tools/displayTools/Surface/index.d.ts +2 -0
- package/dist/cjs/tools/displayTools/Surface/index.js +9 -0
- package/dist/cjs/tools/displayTools/Surface/index.js.map +1 -0
- package/dist/cjs/tools/displayTools/Surface/removeSurfaceFromElement.d.ts +2 -0
- package/dist/cjs/tools/displayTools/Surface/removeSurfaceFromElement.js +14 -0
- package/dist/cjs/tools/displayTools/Surface/removeSurfaceFromElement.js.map +1 -0
- package/dist/cjs/tools/displayTools/Surface/surfaceDisplay.d.ts +11 -0
- package/dist/cjs/tools/displayTools/Surface/surfaceDisplay.js +140 -0
- package/dist/cjs/tools/displayTools/Surface/surfaceDisplay.js.map +1 -0
- package/dist/cjs/tools/index.d.ts +2 -1
- package/dist/cjs/tools/index.js +3 -1
- package/dist/cjs/tools/index.js.map +1 -1
- package/dist/cjs/types/SegmentationStateTypes.d.ts +9 -1
- package/dist/cjs/types/SurfaceTypes.d.ts +4 -0
- package/dist/cjs/types/SurfaceTypes.js +3 -0
- package/dist/cjs/types/SurfaceTypes.js.map +1 -0
- package/dist/cjs/utilities/dynamicVolume/getDataInTime.js +61 -21
- package/dist/cjs/utilities/dynamicVolume/getDataInTime.js.map +1 -1
- package/dist/cjs/utilities/index.d.ts +3 -1
- package/dist/cjs/utilities/index.js +5 -1
- package/dist/cjs/utilities/index.js.map +1 -1
- package/dist/cjs/utilities/math/point/distanceToPoint.d.ts +3 -1
- package/dist/cjs/utilities/math/point/distanceToPoint.js +5 -5
- package/dist/cjs/utilities/math/point/distanceToPoint.js.map +1 -1
- package/dist/cjs/utilities/pointToString.d.ts +1 -0
- package/dist/cjs/utilities/pointToString.js +13 -0
- package/dist/cjs/utilities/pointToString.js.map +1 -0
- package/dist/cjs/utilities/polyData/utils.d.ts +4 -0
- package/dist/cjs/utilities/polyData/utils.js +58 -0
- package/dist/cjs/utilities/polyData/utils.js.map +1 -0
- package/dist/cjs/utilities/segmentation/utilities.js +2 -2
- package/dist/cjs/utilities/segmentation/utilities.js.map +1 -1
- package/dist/esm/drawingSvg/drawPolyline.d.ts +2 -0
- package/dist/esm/drawingSvg/drawPolyline.js +5 -2
- package/dist/esm/drawingSvg/drawPolyline.js.map +1 -1
- package/dist/esm/enums/SegmentationRepresentations.d.ts +2 -1
- package/dist/esm/enums/SegmentationRepresentations.js +1 -0
- package/dist/esm/enums/SegmentationRepresentations.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/stateManagement/segmentation/addSegmentationRepresentations.js +5 -0
- package/dist/esm/stateManagement/segmentation/addSegmentationRepresentations.js.map +1 -1
- package/dist/esm/tools/SegmentationIntersectionTool.d.ts +18 -0
- package/dist/esm/tools/SegmentationIntersectionTool.js +177 -0
- package/dist/esm/tools/SegmentationIntersectionTool.js.map +1 -0
- package/dist/esm/tools/displayTools/Contour/addContourSetsToElement.js.map +1 -1
- package/dist/esm/tools/displayTools/SegmentationDisplayTool.js +7 -3
- package/dist/esm/tools/displayTools/SegmentationDisplayTool.js.map +1 -1
- package/dist/esm/tools/displayTools/Surface/addSurfaceToElement.d.ts +2 -0
- package/dist/esm/tools/displayTools/Surface/addSurfaceToElement.js +79 -0
- package/dist/esm/tools/displayTools/Surface/addSurfaceToElement.js.map +1 -0
- package/dist/esm/tools/displayTools/Surface/index.d.ts +2 -0
- package/dist/esm/tools/displayTools/Surface/index.js +3 -0
- package/dist/esm/tools/displayTools/Surface/index.js.map +1 -0
- package/dist/esm/tools/displayTools/Surface/removeSurfaceFromElement.d.ts +2 -0
- package/dist/esm/tools/displayTools/Surface/removeSurfaceFromElement.js +12 -0
- package/dist/esm/tools/displayTools/Surface/removeSurfaceFromElement.js.map +1 -0
- package/dist/esm/tools/displayTools/Surface/surfaceDisplay.d.ts +11 -0
- package/dist/esm/tools/displayTools/Surface/surfaceDisplay.js +101 -0
- package/dist/esm/tools/displayTools/Surface/surfaceDisplay.js.map +1 -0
- package/dist/esm/tools/index.d.ts +2 -1
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/types/SegmentationStateTypes.d.ts +9 -1
- package/dist/esm/types/SurfaceTypes.d.ts +4 -0
- package/dist/esm/types/SurfaceTypes.js +2 -0
- package/dist/esm/types/SurfaceTypes.js.map +1 -0
- package/dist/esm/utilities/dynamicVolume/getDataInTime.js +58 -21
- package/dist/esm/utilities/dynamicVolume/getDataInTime.js.map +1 -1
- package/dist/esm/utilities/index.d.ts +3 -1
- package/dist/esm/utilities/index.js +3 -1
- package/dist/esm/utilities/index.js.map +1 -1
- package/dist/esm/utilities/math/point/distanceToPoint.d.ts +3 -1
- package/dist/esm/utilities/math/point/distanceToPoint.js +5 -5
- package/dist/esm/utilities/math/point/distanceToPoint.js.map +1 -1
- package/dist/esm/utilities/pointToString.d.ts +1 -0
- package/dist/esm/utilities/pointToString.js +9 -0
- package/dist/esm/utilities/pointToString.js.map +1 -0
- package/dist/esm/utilities/polyData/utils.d.ts +4 -0
- package/dist/esm/utilities/polyData/utils.js +52 -0
- package/dist/esm/utilities/polyData/utils.js.map +1 -0
- package/dist/esm/utilities/segmentation/utilities.js +2 -2
- package/dist/esm/utilities/segmentation/utilities.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +4 -4
- package/src/drawingSvg/drawPolyline.ts +17 -11
- package/src/enums/SegmentationRepresentations.ts +1 -1
- package/src/index.ts +2 -0
- package/src/stateManagement/segmentation/addSegmentationRepresentations.ts +8 -0
- package/src/tools/SegmentationIntersectionTool.ts +300 -0
- package/src/tools/displayTools/Contour/addContourSetsToElement.ts +1 -1
- package/src/tools/displayTools/SegmentationDisplayTool.ts +10 -4
- package/src/tools/displayTools/Surface/addSurfaceToElement.ts +115 -0
- package/src/tools/displayTools/Surface/index.ts +3 -0
- package/src/tools/displayTools/Surface/removeSurfaceFromElement.ts +35 -0
- package/src/tools/displayTools/Surface/surfaceDisplay.ts +215 -0
- package/src/tools/index.ts +2 -0
- package/src/types/SegmentationStateTypes.ts +18 -1
- package/src/types/SurfaceTypes.ts +10 -0
- package/src/utilities/dynamicVolume/generateImageFromTimeData.ts +3 -3
- package/src/utilities/dynamicVolume/getDataInTime.ts +115 -25
- package/src/utilities/index.ts +4 -0
- package/src/utilities/math/point/distanceToPoint.ts +12 -11
- package/src/utilities/pointToString.ts +10 -0
- package/src/utilities/polyData/utils.ts +82 -0
- package/src/utilities/segmentation/utilities.ts +2 -3
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cache,
|
|
3
|
+
getEnabledElementByIds,
|
|
4
|
+
Types,
|
|
5
|
+
utilities,
|
|
6
|
+
Enums,
|
|
7
|
+
} from '@cornerstonejs/core';
|
|
8
|
+
|
|
9
|
+
import * as SegmentationState from '../../../stateManagement/segmentation/segmentationState';
|
|
10
|
+
import * as SegmentationConfig from '../../../stateManagement/segmentation/config/segmentationConfig';
|
|
11
|
+
import Representations from '../../../enums/SegmentationRepresentations';
|
|
12
|
+
import { getToolGroup } from '../../../store/ToolGroupManager';
|
|
13
|
+
import {
|
|
14
|
+
RepresentationPublicInput,
|
|
15
|
+
SegmentationRepresentationConfig,
|
|
16
|
+
ToolGroupSpecificRepresentation,
|
|
17
|
+
} from '../../../types/SegmentationStateTypes';
|
|
18
|
+
|
|
19
|
+
import removeSurfaceFromElement from './removeSurfaceFromElement';
|
|
20
|
+
import addSurfaceToElement from './addSurfaceToElement';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* It adds a new segmentation representation to the segmentation state
|
|
24
|
+
* @param toolGroupId - The id of the toolGroup that the segmentation
|
|
25
|
+
* belongs to
|
|
26
|
+
* @param representationInput - RepresentationPublicInput
|
|
27
|
+
* @param toolGroupSpecificConfig - The configuration that is specific to the toolGroup.
|
|
28
|
+
* @returns The segmentationRepresentationUID
|
|
29
|
+
*/
|
|
30
|
+
async function addSegmentationRepresentation(
|
|
31
|
+
toolGroupId: string,
|
|
32
|
+
representationInput: RepresentationPublicInput,
|
|
33
|
+
toolGroupSpecificConfig?: SegmentationRepresentationConfig
|
|
34
|
+
): Promise<string> {
|
|
35
|
+
const { segmentationId } = representationInput;
|
|
36
|
+
const segmentationRepresentationUID = utilities.uuidv4();
|
|
37
|
+
// Todo: make these configurable during representation input by user
|
|
38
|
+
const segmentsHidden = new Set() as Set<number>;
|
|
39
|
+
const colorLUTIndex = 0;
|
|
40
|
+
const active = true;
|
|
41
|
+
const toolGroupSpecificRepresentation: ToolGroupSpecificRepresentation = {
|
|
42
|
+
segmentationId,
|
|
43
|
+
segmentationRepresentationUID,
|
|
44
|
+
type: Representations.Surface,
|
|
45
|
+
segmentsHidden,
|
|
46
|
+
colorLUTIndex,
|
|
47
|
+
active,
|
|
48
|
+
segmentationRepresentationSpecificConfig: {},
|
|
49
|
+
segmentSpecificConfig: {},
|
|
50
|
+
config: {},
|
|
51
|
+
};
|
|
52
|
+
// Update the toolGroup specific configuration
|
|
53
|
+
if (toolGroupSpecificConfig) {
|
|
54
|
+
// Since setting configuration on toolGroup will trigger a segmentationRepresentation
|
|
55
|
+
// update event, we don't want to trigger the event twice, so we suppress
|
|
56
|
+
// the first one
|
|
57
|
+
const currentToolGroupConfig =
|
|
58
|
+
SegmentationConfig.getToolGroupSpecificConfig(toolGroupId);
|
|
59
|
+
const mergedConfig = utilities.deepMerge(
|
|
60
|
+
currentToolGroupConfig,
|
|
61
|
+
toolGroupSpecificConfig
|
|
62
|
+
);
|
|
63
|
+
SegmentationConfig.setToolGroupSpecificConfig(toolGroupId, {
|
|
64
|
+
renderInactiveSegmentations:
|
|
65
|
+
mergedConfig.renderInactiveSegmentations || true,
|
|
66
|
+
representations: {
|
|
67
|
+
...mergedConfig.representations,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
SegmentationState.addSegmentationRepresentation(
|
|
72
|
+
toolGroupId,
|
|
73
|
+
toolGroupSpecificRepresentation
|
|
74
|
+
);
|
|
75
|
+
return segmentationRepresentationUID;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* It removes a segmentation representation from the tool group's viewports and
|
|
80
|
+
* from the segmentation state
|
|
81
|
+
* @param toolGroupId - The toolGroupId of the toolGroup that the
|
|
82
|
+
* segmentationRepresentation belongs to.
|
|
83
|
+
* @param segmentationRepresentationUID - This is the unique identifier
|
|
84
|
+
* for the segmentation representation.
|
|
85
|
+
* @param renderImmediate - If true, the viewport will be rendered
|
|
86
|
+
* immediately after the segmentation representation is removed.
|
|
87
|
+
*/
|
|
88
|
+
function removeSegmentationRepresentation(
|
|
89
|
+
toolGroupId: string,
|
|
90
|
+
segmentationRepresentationUID: string,
|
|
91
|
+
renderImmediate = false
|
|
92
|
+
): void {
|
|
93
|
+
_removeSurfaceFromToolGroupViewports(
|
|
94
|
+
toolGroupId,
|
|
95
|
+
segmentationRepresentationUID
|
|
96
|
+
);
|
|
97
|
+
SegmentationState.removeSegmentationRepresentation(
|
|
98
|
+
toolGroupId,
|
|
99
|
+
segmentationRepresentationUID
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (renderImmediate) {
|
|
103
|
+
const viewportsInfo = getToolGroup(toolGroupId).getViewportsInfo();
|
|
104
|
+
viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
|
|
105
|
+
const enabledElement = getEnabledElementByIds(
|
|
106
|
+
viewportId,
|
|
107
|
+
renderingEngineId
|
|
108
|
+
);
|
|
109
|
+
enabledElement.viewport.render();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* It renders the Surface for the given segmentation
|
|
116
|
+
* @param viewport - The viewport object
|
|
117
|
+
* @param representation - ToolGroupSpecificRepresentation
|
|
118
|
+
* @param toolGroupConfig - This is the configuration object for the tool group
|
|
119
|
+
*/
|
|
120
|
+
async function render(
|
|
121
|
+
viewport: Types.IVolumeViewport,
|
|
122
|
+
representation: ToolGroupSpecificRepresentation,
|
|
123
|
+
toolGroupConfig: SegmentationRepresentationConfig
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const {
|
|
126
|
+
colorLUTIndex,
|
|
127
|
+
active,
|
|
128
|
+
segmentationId,
|
|
129
|
+
segmentationRepresentationUID,
|
|
130
|
+
segmentsHidden,
|
|
131
|
+
} = representation;
|
|
132
|
+
|
|
133
|
+
const segmentation = SegmentationState.getSegmentation(segmentationId);
|
|
134
|
+
const SurfaceData = segmentation.representationData[Representations.Surface];
|
|
135
|
+
const { geometryId } = SurfaceData;
|
|
136
|
+
|
|
137
|
+
if (!geometryId) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`No Surfaces found for segmentationId ${segmentationId}. Skipping render.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const geometry = cache.getGeometry(geometryId);
|
|
144
|
+
if (!geometry) {
|
|
145
|
+
throw new Error(`No Surfaces found for geometryId ${geometryId}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (geometry.type !== Enums.GeometryType.SURFACE) {
|
|
149
|
+
// Todo: later we can support converting other geometries to Surfaces
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Geometry type ${geometry.type} not supported for rendering.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!geometry.data) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`No Surfaces found for geometryId ${geometryId}. Skipping render.`
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const surface = geometry.data;
|
|
163
|
+
|
|
164
|
+
const surfaceUID = `${segmentationRepresentationUID}_${surface.id}}`;
|
|
165
|
+
_renderSurface(viewport, surface, surfaceUID);
|
|
166
|
+
|
|
167
|
+
viewport.resetCamera();
|
|
168
|
+
viewport.render();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function _renderSurface(
|
|
172
|
+
viewport: Types.IVolumeViewport,
|
|
173
|
+
surface: any,
|
|
174
|
+
surfaceUID: string
|
|
175
|
+
): void {
|
|
176
|
+
const actorUID = surfaceUID;
|
|
177
|
+
const actorEntry = viewport.getActor(actorUID);
|
|
178
|
+
|
|
179
|
+
if (!actorEntry) {
|
|
180
|
+
addSurfaceToElement(viewport.element, surface, actorUID);
|
|
181
|
+
} else {
|
|
182
|
+
throw new Error('Not implemented yet. (Update surface)');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _removeSurfaceFromToolGroupViewports(
|
|
187
|
+
toolGroupId: string,
|
|
188
|
+
segmentationRepresentationUID: string
|
|
189
|
+
): void {
|
|
190
|
+
const toolGroup = getToolGroup(toolGroupId);
|
|
191
|
+
|
|
192
|
+
if (toolGroup === undefined) {
|
|
193
|
+
throw new Error(`ToolGroup with ToolGroupId ${toolGroupId} does not exist`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { viewportsInfo } = toolGroup;
|
|
197
|
+
|
|
198
|
+
for (const viewportInfo of viewportsInfo) {
|
|
199
|
+
const { viewportId, renderingEngineId } = viewportInfo;
|
|
200
|
+
const enabledElement = getEnabledElementByIds(
|
|
201
|
+
viewportId,
|
|
202
|
+
renderingEngineId
|
|
203
|
+
);
|
|
204
|
+
removeSurfaceFromElement(
|
|
205
|
+
enabledElement.viewport.element,
|
|
206
|
+
segmentationRepresentationUID
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default {
|
|
212
|
+
render,
|
|
213
|
+
addSegmentationRepresentation,
|
|
214
|
+
removeSegmentationRepresentation,
|
|
215
|
+
};
|
package/src/tools/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import MagnifyTool from './MagnifyTool';
|
|
|
13
13
|
import AdvancedMagnifyTool from './AdvancedMagnifyTool';
|
|
14
14
|
import ReferenceLinesTool from './ReferenceLinesTool';
|
|
15
15
|
import OverlayGridTool from './OverlayGridTool';
|
|
16
|
+
import SegmentationIntersectionTool from './SegmentationIntersectionTool';
|
|
16
17
|
//
|
|
17
18
|
import BidirectionalTool from './annotation/BidirectionalTool';
|
|
18
19
|
import LengthTool from './annotation/LengthTool';
|
|
@@ -62,6 +63,7 @@ export {
|
|
|
62
63
|
CrosshairsTool,
|
|
63
64
|
ReferenceLinesTool,
|
|
64
65
|
OverlayGridTool,
|
|
66
|
+
SegmentationIntersectionTool,
|
|
65
67
|
BidirectionalTool,
|
|
66
68
|
LengthTool,
|
|
67
69
|
ProbeTool,
|
|
@@ -9,6 +9,10 @@ import type {
|
|
|
9
9
|
LabelmapRenderingConfig,
|
|
10
10
|
LabelmapSegmentationData,
|
|
11
11
|
} from './LabelmapTypes';
|
|
12
|
+
import {
|
|
13
|
+
SurfaceSegmentationData,
|
|
14
|
+
SurfaceRenderingConfig,
|
|
15
|
+
} from './SurfaceTypes';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Four elements RGBA as 0-255
|
|
@@ -30,6 +34,8 @@ export type RepresentationConfig = {
|
|
|
30
34
|
LABELMAP?: LabelmapConfig;
|
|
31
35
|
/** contour configuration */
|
|
32
36
|
CONTOUR?: ContourConfig;
|
|
37
|
+
/** surface configuration */
|
|
38
|
+
SURFACE?: any;
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
export type SegmentationRepresentationConfig = {
|
|
@@ -42,6 +48,7 @@ export type SegmentationRepresentationConfig = {
|
|
|
42
48
|
export type SegmentationRepresentationData = {
|
|
43
49
|
LABELMAP?: LabelmapSegmentationData;
|
|
44
50
|
CONTOUR?: ContourSegmentationData;
|
|
51
|
+
SURFACE?: SurfaceSegmentationData;
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
/**
|
|
@@ -132,6 +139,13 @@ export type ToolGroupSpecificContourRepresentation =
|
|
|
132
139
|
segmentSpecificConfig?: SegmentSpecificRepresentationConfig;
|
|
133
140
|
};
|
|
134
141
|
|
|
142
|
+
export type ToolGroupSpecificSurfaceRepresentation =
|
|
143
|
+
ToolGroupSpecificRepresentationState & {
|
|
144
|
+
config: SurfaceRenderingConfig;
|
|
145
|
+
segmentationRepresentationSpecificConfig?: RepresentationConfig;
|
|
146
|
+
segmentSpecificConfig?: SegmentSpecificRepresentationConfig;
|
|
147
|
+
};
|
|
148
|
+
|
|
135
149
|
export type ToolGroupSpecificRepresentation =
|
|
136
150
|
| ToolGroupSpecificLabelmapRepresentation
|
|
137
151
|
| ToolGroupSpecificContourRepresentation;
|
|
@@ -252,7 +266,10 @@ export type SegmentationPublicInput = {
|
|
|
252
266
|
segmentationId: string;
|
|
253
267
|
representation: {
|
|
254
268
|
type: Enums.SegmentationRepresentations;
|
|
255
|
-
data:
|
|
269
|
+
data:
|
|
270
|
+
| LabelmapSegmentationData
|
|
271
|
+
| ContourSegmentationData
|
|
272
|
+
| SurfaceSegmentationData;
|
|
256
273
|
};
|
|
257
274
|
};
|
|
258
275
|
|
|
@@ -5,10 +5,10 @@ import { Enums, Types } from '@cornerstonejs/core';
|
|
|
5
5
|
* array of scalar data after performing AVERAGE, SUM or SUBTRACT to be used to
|
|
6
6
|
* create a 3D volume
|
|
7
7
|
*
|
|
8
|
-
* @param dynamicVolume4D
|
|
9
|
-
* @param operation
|
|
8
|
+
* @param dynamicVolume4D - volume to compute time frame data from
|
|
9
|
+
* @param operation - operation to perform on time frame data, operations include
|
|
10
10
|
* SUM, AVERAGE, and SUBTRACT (can only be used with 2 time frames provided)
|
|
11
|
-
* @param frameNumbers
|
|
11
|
+
* @param frameNumbers - an array of frame indices to perform the operation on, if
|
|
12
12
|
* left empty, all frames will be used
|
|
13
13
|
* @returns
|
|
14
14
|
*/
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { utilities, cache, Types } from '@cornerstonejs/core';
|
|
2
|
+
import { getVoxelOverlap } from '../segmentation/utilities';
|
|
3
|
+
import pointInShapeCallback from '../pointInShapeCallback';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Gets the scalar data for a series of time points for either a single
|
|
5
7
|
* coordinate or a segmentation mask, it will return the an array of scalar
|
|
6
8
|
* data for a single coordinate or an array of arrays for a segmentation.
|
|
7
9
|
*
|
|
8
|
-
* @param dynamicVolume
|
|
9
|
-
* @param options
|
|
10
|
+
* @param dynamicVolume - 4D volume to compute time point data from
|
|
11
|
+
* @param options - frameNumbers: which frames to use as timepoints, if left
|
|
10
12
|
* blank, gets data timepoints over all frames
|
|
11
13
|
* maskVolumeId: segmentationId to get timepoint data of
|
|
12
14
|
* imageCoordinate: world coordinate to get timepoint data of
|
|
@@ -31,26 +33,23 @@ function getDataInTime(
|
|
|
31
33
|
// Throws error if neither maskVolumeId or imageCoordinate is given,
|
|
32
34
|
// throws error if BOTH maskVolumeId and imageCoordinate is given
|
|
33
35
|
if (!options.maskVolumeId && !options.imageCoordinate) {
|
|
34
|
-
throw new Error(
|
|
36
|
+
throw new Error(
|
|
37
|
+
'You should provide either maskVolumeId or imageCoordinate'
|
|
38
|
+
);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
if (options.maskVolumeId && options.imageCoordinate) {
|
|
38
|
-
throw new Error('
|
|
42
|
+
throw new Error('You can only use one of maskVolumeId or imageCoordinate');
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
if (options.maskVolumeId) {
|
|
42
46
|
const segmentationVolume = cache.getVolume(options.maskVolumeId);
|
|
43
|
-
const segScalarData = segmentationVolume.getScalarData();
|
|
44
|
-
const indexArray = [];
|
|
45
|
-
|
|
46
|
-
// Get the index of every non-zero voxel in mask
|
|
47
|
-
for (let i = 0, len = segScalarData.length; i < len; i++) {
|
|
48
|
-
if (segScalarData[i] !== 0) {
|
|
49
|
-
indexArray.push(i);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
47
|
|
|
53
|
-
const dataInTime = _getTimePointDataMask(
|
|
48
|
+
const dataInTime = _getTimePointDataMask(
|
|
49
|
+
frames,
|
|
50
|
+
dynamicVolume,
|
|
51
|
+
segmentationVolume
|
|
52
|
+
);
|
|
54
53
|
|
|
55
54
|
return dataInTime;
|
|
56
55
|
}
|
|
@@ -95,19 +94,110 @@ function _getTimePointDataCoordinate(frames, coordinate, volume) {
|
|
|
95
94
|
return value;
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
function _getTimePointDataMask(frames,
|
|
99
|
-
const
|
|
100
|
-
const
|
|
97
|
+
function _getTimePointDataMask(frames, dynamicVolume, segmentationVolume) {
|
|
98
|
+
const { imageData: maskImageData } = segmentationVolume;
|
|
99
|
+
const segScalarData = segmentationVolume.getScalarData();
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
const len = segScalarData.length;
|
|
102
|
+
|
|
103
|
+
// Pre-allocate memory for array
|
|
104
|
+
const nonZeroVoxelIndices = [];
|
|
105
|
+
nonZeroVoxelIndices.length = len;
|
|
106
|
+
|
|
107
|
+
// Get the index of every non-zero voxel in mask
|
|
108
|
+
let actualLen = 0;
|
|
109
|
+
for (let i = 0, len = segScalarData.length; i < len; i++) {
|
|
110
|
+
if (segScalarData[i] !== 0) {
|
|
111
|
+
nonZeroVoxelIndices[actualLen++] = i;
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
|
-
|
|
114
|
+
|
|
115
|
+
// Trim the array to actual size
|
|
116
|
+
nonZeroVoxelIndices.length = actualLen;
|
|
117
|
+
|
|
118
|
+
const dynamicVolumeScalarDataArray = dynamicVolume.getScalarDataArrays();
|
|
119
|
+
const values = [];
|
|
120
|
+
const isSameVolume =
|
|
121
|
+
dynamicVolumeScalarDataArray[0].length === len &&
|
|
122
|
+
JSON.stringify(dynamicVolume.spacing) ===
|
|
123
|
+
JSON.stringify(segmentationVolume.spacing);
|
|
124
|
+
|
|
125
|
+
// if the segmentation mask is the same size as the dynamic volume (one frame)
|
|
126
|
+
// means we can just return the scalar data for the non-zero voxels
|
|
127
|
+
if (isSameVolume) {
|
|
128
|
+
for (let i = 0; i < nonZeroVoxelIndices.length; i++) {
|
|
129
|
+
const indexValues = [];
|
|
130
|
+
frames.forEach((frame) => {
|
|
131
|
+
const activeScalarData = dynamicVolumeScalarDataArray[frame];
|
|
132
|
+
indexValues.push(activeScalarData[nonZeroVoxelIndices[i]]);
|
|
133
|
+
});
|
|
134
|
+
values.push(indexValues);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return values;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// In case that the segmentation mask is not the same size as the dynamic volume (one frame)
|
|
141
|
+
// then we need to consider each voxel in the segmentation mask and check if it
|
|
142
|
+
// overlaps with the other volume, and if so we need to average the values of the
|
|
143
|
+
// overlapping voxels.
|
|
144
|
+
const callback = ({ pointLPS: segPointLPS, value: segValue }) => {
|
|
145
|
+
// see if the value is non-zero
|
|
146
|
+
if (segValue === 0) {
|
|
147
|
+
// not interested
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Then for each non-zero voxel in the segmentation mask, we should
|
|
152
|
+
// again perform the pointInShapeCallback to run the averaging callback
|
|
153
|
+
// function to get the average value of the overlapping voxels.
|
|
154
|
+
const overlapIJKMinMax = getVoxelOverlap(
|
|
155
|
+
dynamicVolume.imageData,
|
|
156
|
+
dynamicVolume.dimensions,
|
|
157
|
+
dynamicVolume.spacing,
|
|
158
|
+
segPointLPS
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// count represents the number of voxels of the dynamic volume that represents
|
|
162
|
+
// one voxel of the segmentation mask
|
|
163
|
+
let count = 0;
|
|
164
|
+
const perFrameSum = new Map();
|
|
165
|
+
|
|
166
|
+
// Pre-initialize the Map
|
|
167
|
+
frames.forEach((frame) => perFrameSum.set(frame, 0));
|
|
168
|
+
|
|
169
|
+
const averageCallback = ({ index }) => {
|
|
170
|
+
for (let i = 0; i < frames.length; i++) {
|
|
171
|
+
const value = dynamicVolumeScalarDataArray[i][index];
|
|
172
|
+
const frame = frames[i];
|
|
173
|
+
perFrameSum.set(frame, perFrameSum.get(frame) + value);
|
|
174
|
+
}
|
|
175
|
+
count++;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
pointInShapeCallback(
|
|
179
|
+
dynamicVolume.imageData,
|
|
180
|
+
() => true,
|
|
181
|
+
averageCallback,
|
|
182
|
+
overlapIJKMinMax
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// average the values
|
|
186
|
+
const averageValues = [];
|
|
187
|
+
perFrameSum.forEach((sum) => {
|
|
188
|
+
averageValues.push(sum / count);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
values.push(averageValues);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Since we have the non-zero voxel indices of the segmentation mask,
|
|
195
|
+
// we theoretically can use them, however, we kind of need to compute the
|
|
196
|
+
// pointLPS for each of the non-zero voxel indices, which is a bit of a pain.
|
|
197
|
+
// Todo: consider using the nonZeroVoxelIndices to compute the pointLPS
|
|
198
|
+
pointInShapeCallback(maskImageData, () => true, callback);
|
|
199
|
+
|
|
200
|
+
return values;
|
|
111
201
|
}
|
|
112
202
|
|
|
113
203
|
export default getDataInTime;
|
package/src/utilities/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import pointInShapeCallback from './pointInShapeCallback';
|
|
|
17
17
|
import pointInSurroundingSphereCallback from './pointInSurroundingSphereCallback';
|
|
18
18
|
import scroll from './scroll';
|
|
19
19
|
import roundNumber from './roundNumber';
|
|
20
|
+
import { pointToString } from './pointToString';
|
|
20
21
|
|
|
21
22
|
// name spaces
|
|
22
23
|
import * as segmentation from './segmentation';
|
|
@@ -33,6 +34,7 @@ import { stackPrefetch, stackContextPrefetch } from './stackPrefetch';
|
|
|
33
34
|
import * as viewport from './viewport';
|
|
34
35
|
import * as touch from './touch';
|
|
35
36
|
import * as dynamicVolume from './dynamicVolume';
|
|
37
|
+
import * as polyDataUtils from './polyData/utils';
|
|
36
38
|
|
|
37
39
|
// Events
|
|
38
40
|
import { triggerEvent } from '@cornerstonejs/core';
|
|
@@ -68,4 +70,6 @@ export {
|
|
|
68
70
|
stackContextPrefetch,
|
|
69
71
|
scroll,
|
|
70
72
|
roundNumber,
|
|
73
|
+
pointToString,
|
|
74
|
+
polyDataUtils,
|
|
71
75
|
};
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
|
|
3
|
+
type Point = Types.Point2 | Types.Point3;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Calculates the distance of a point to another point
|
|
5
7
|
*
|
|
6
|
-
* @param p1 - x,y of the point
|
|
7
|
-
* @param p2 - x,y of the point
|
|
8
|
+
* @param p1 - x,y or x,y,z of the point
|
|
9
|
+
* @param p2 - x,y or x,y,z of the point
|
|
8
10
|
* @returns distance
|
|
9
11
|
*/
|
|
10
|
-
export default function distanceToPoint(
|
|
11
|
-
p1
|
|
12
|
-
|
|
13
|
-
): number {
|
|
14
|
-
if (p1?.length !== 2 || p2?.length !== 2) {
|
|
15
|
-
throw Error('points should have 2 elements of [x, y]');
|
|
12
|
+
export default function distanceToPoint(p1: Point, p2: Point): number {
|
|
13
|
+
if (p1.length !== p2.length) {
|
|
14
|
+
throw Error('Both points should have the same dimensionality');
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
const [x1, y1] = p1;
|
|
19
|
-
const [x2, y2] = p2;
|
|
17
|
+
const [x1, y1, z1 = 0] = p1;
|
|
18
|
+
const [x2, y2, z2 = 0] = p2;
|
|
20
19
|
|
|
21
|
-
return Math.sqrt(
|
|
20
|
+
return Math.sqrt(
|
|
21
|
+
Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2)
|
|
22
|
+
);
|
|
22
23
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets a point from an array of numbers given its index
|
|
5
|
+
* @param points array of number, each point defined by three consecutive numbers
|
|
6
|
+
* @param idx index of the point to retrieve
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function getPoint(points, idx) {
|
|
10
|
+
if (idx < points.length / 3) {
|
|
11
|
+
return [points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2]];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract contour point sets from the outline of a poly data actor
|
|
17
|
+
* @param polyData - vtk polyData
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
export function getPolyDataPointIndexes(polyData: vtkPolyData) {
|
|
21
|
+
const linesData = polyData.getLines().getData();
|
|
22
|
+
let idx = 0;
|
|
23
|
+
const lineSegments = new Map<number, number[]>();
|
|
24
|
+
|
|
25
|
+
// Populate lineSegments map
|
|
26
|
+
while (idx < linesData.length) {
|
|
27
|
+
const segmentSize = linesData[idx++];
|
|
28
|
+
const segment = [];
|
|
29
|
+
for (let i = 0; i < segmentSize; i++) {
|
|
30
|
+
segment.push(linesData[idx + i]);
|
|
31
|
+
}
|
|
32
|
+
lineSegments.set(segment[0], segment);
|
|
33
|
+
idx += segmentSize;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const contours = [];
|
|
37
|
+
|
|
38
|
+
// Function to find an available starting point
|
|
39
|
+
const findStartingPoint = (map) => {
|
|
40
|
+
for (const [key, value] of map.entries()) {
|
|
41
|
+
if (value !== undefined) {
|
|
42
|
+
return key;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return -1;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Build contours
|
|
49
|
+
let startPoint = findStartingPoint(lineSegments);
|
|
50
|
+
while (startPoint !== -1) {
|
|
51
|
+
const contour = [startPoint];
|
|
52
|
+
while (lineSegments.has(startPoint)) {
|
|
53
|
+
const nextPoint = lineSegments.get(startPoint)[1];
|
|
54
|
+
if (lineSegments.has(nextPoint)) {
|
|
55
|
+
contour.push(nextPoint);
|
|
56
|
+
}
|
|
57
|
+
lineSegments.delete(startPoint);
|
|
58
|
+
startPoint = nextPoint;
|
|
59
|
+
}
|
|
60
|
+
contours.push(contour);
|
|
61
|
+
startPoint = findStartingPoint(lineSegments);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return contours.length ? contours : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract contour points from a poly data object
|
|
69
|
+
* @param polyData - vtk polyData
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
export function getPolyDataPoints(polyData: vtkPolyData) {
|
|
73
|
+
const contoursIndexes = getPolyDataPointIndexes(polyData);
|
|
74
|
+
if (!contoursIndexes) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rawPointsData = polyData.getPoints().getData();
|
|
79
|
+
return contoursIndexes.map((contourIndexes) =>
|
|
80
|
+
contourIndexes.map((index) => getPoint(rawPointsData, index))
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -56,7 +56,7 @@ export function getVoxelOverlap(
|
|
|
56
56
|
for (let i = 0; i < 2; i++) {
|
|
57
57
|
for (let j = 0; j < 2; j++) {
|
|
58
58
|
for (let k = 0; k < 2; k++) {
|
|
59
|
-
const point = voxelCenter;
|
|
59
|
+
const point = [...voxelCenter]; // Create a new point from voxelCenter
|
|
60
60
|
point[0] = point[0] + ((i * 2 - 1) * voxelSpacing[0]) / 2;
|
|
61
61
|
point[1] = point[1] + ((j * 2 - 1) * voxelSpacing[1]) / 2;
|
|
62
62
|
point[2] = point[2] + ((k * 2 - 1) * voxelSpacing[2]) / 2;
|
|
@@ -79,8 +79,7 @@ export function processVolumes(
|
|
|
79
79
|
segmentationVolume: Types.IImageVolume,
|
|
80
80
|
thresholdVolumeInformation: ThresholdInformation[]
|
|
81
81
|
) {
|
|
82
|
-
const { spacing: segmentationSpacing
|
|
83
|
-
segmentationVolume;
|
|
82
|
+
const { spacing: segmentationSpacing } = segmentationVolume;
|
|
84
83
|
const scalarData = segmentationVolume.getScalarData();
|
|
85
84
|
|
|
86
85
|
// prepare a list of volume information objects for callback functions
|