@cornerstonejs/polymorphic-segmentation 3.0.0-beta.3 → 3.0.0-beta.5
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/Contour/contourComputationStrategies.d.ts +8 -0
- package/dist/esm/Contour/contourComputationStrategies.js +104 -0
- package/dist/esm/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.d.ts +3 -0
- package/dist/esm/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.js +71 -0
- package/dist/esm/Contour/utils/extractContourData.d.ts +3 -0
- package/dist/esm/Contour/utils/extractContourData.js +16 -0
- package/dist/esm/Contour/utils/updateContoursOnCameraModified.d.ts +1 -0
- package/dist/esm/Contour/utils/updateContoursOnCameraModified.js +25 -0
- package/dist/esm/Labelmap/convertContourToLabelmap.d.ts +8 -0
- package/dist/esm/Labelmap/convertContourToLabelmap.js +144 -0
- package/dist/esm/Labelmap/convertSurfaceToLabelmap.d.ts +6 -0
- package/dist/esm/Labelmap/convertSurfaceToLabelmap.js +50 -0
- package/dist/esm/Labelmap/labelmapComputationStrategies.d.ts +6 -0
- package/dist/esm/Labelmap/labelmapComputationStrategies.js +97 -0
- package/dist/esm/Surface/convertContourToSurface.d.ts +3 -0
- package/dist/esm/Surface/convertContourToSurface.js +37 -0
- package/dist/esm/Surface/convertLabelmapToSurface.d.ts +3 -0
- package/dist/esm/Surface/convertLabelmapToSurface.js +45 -0
- package/dist/esm/Surface/createAndCacheSurfacesFromRaw.d.ts +5 -0
- package/dist/esm/Surface/createAndCacheSurfacesFromRaw.js +34 -0
- package/dist/esm/Surface/surfaceComputationStrategies.d.ts +12 -0
- package/dist/esm/Surface/surfaceComputationStrategies.js +76 -0
- package/dist/esm/Surface/updateSurfaceData.d.ts +1 -0
- package/dist/esm/Surface/updateSurfaceData.js +55 -0
- package/dist/esm/canComputeRequestedRepresentation.d.ts +4 -0
- package/dist/esm/canComputeRequestedRepresentation.js +59 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/registerPolySegWorker.d.ts +1 -0
- package/dist/esm/registerPolySegWorker.js +23 -0
- package/dist/esm/types/PolySegConversionOptions.d.ts +6 -0
- package/dist/esm/types/PolySegConversionOptions.js +0 -0
- package/dist/esm/types/index.d.ts +2 -0
- package/dist/esm/types/index.js +0 -0
- package/dist/esm/utilities/clipAndCacheSurfacesForViewport.d.ts +16 -0
- package/dist/esm/utilities/clipAndCacheSurfacesForViewport.js +88 -0
- package/dist/esm/utilities/index.d.ts +2 -0
- package/dist/esm/utilities/index.js +2 -0
- package/dist/esm/workers/polySegConverters.d.ts +1 -0
- package/dist/esm/workers/polySegConverters.js +391 -0
- package/package.json +9 -6
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PolySegConversionOptions } from '../types';
|
|
2
|
+
import type { SurfaceClipResult } from '../utilities/clipAndCacheSurfacesForViewport';
|
|
3
|
+
export type RawContourData = Map<number, SurfaceClipResult[]>;
|
|
4
|
+
export declare function computeContourData(segmentationId: string, options?: PolySegConversionOptions): Promise<{
|
|
5
|
+
annotationUIDsMap: Map<number, Set<string>>;
|
|
6
|
+
}>;
|
|
7
|
+
declare function computeContourFromLabelmapSegmentation(segmentationId: any, options?: PolySegConversionOptions): Promise<RawContourData>;
|
|
8
|
+
export { computeContourFromLabelmapSegmentation };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { cache } from '@cornerstonejs/core';
|
|
2
|
+
import { Enums, segmentation, utilities } from '@cornerstonejs/tools';
|
|
3
|
+
import { extractContourData } from './utils/extractContourData';
|
|
4
|
+
import { computeSurfaceFromLabelmapSegmentation } from '../Surface/surfaceComputationStrategies';
|
|
5
|
+
import { clipAndCacheSurfacesForViewport } from '../utilities/clipAndCacheSurfacesForViewport';
|
|
6
|
+
import { createAndAddContourSegmentationsFromClippedSurfaces } from './utils/createAndAddContourSegmentationsFromClippedSurfaces';
|
|
7
|
+
const { getUniqueSegmentIndices } = utilities.segmentation;
|
|
8
|
+
const { getSegmentation } = segmentation.state;
|
|
9
|
+
const { segmentationStyle } = segmentation;
|
|
10
|
+
const { SegmentationRepresentations } = Enums;
|
|
11
|
+
export async function computeContourData(segmentationId, options = {}) {
|
|
12
|
+
const segmentIndices = options.segmentIndices?.length
|
|
13
|
+
? options.segmentIndices
|
|
14
|
+
: getUniqueSegmentIndices(segmentationId);
|
|
15
|
+
let rawContourData;
|
|
16
|
+
const segmentation = getSegmentation(segmentationId);
|
|
17
|
+
const representationData = segmentation.representationData;
|
|
18
|
+
try {
|
|
19
|
+
if (representationData.Surface) {
|
|
20
|
+
rawContourData = await computeContourFromSurfaceSegmentation(segmentationId, {
|
|
21
|
+
segmentIndices,
|
|
22
|
+
...options,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else if (representationData.Labelmap) {
|
|
26
|
+
rawContourData = await computeContourFromLabelmapSegmentation(segmentationId, {
|
|
27
|
+
segmentIndices,
|
|
28
|
+
...options,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
if (!rawContourData) {
|
|
37
|
+
throw new Error('Not enough data to convert to contour, currently only support converting volume labelmap to contour if available');
|
|
38
|
+
}
|
|
39
|
+
const { viewport } = options;
|
|
40
|
+
const annotationUIDsMap = createAndAddContourSegmentationsFromClippedSurfaces(rawContourData, viewport, segmentationId);
|
|
41
|
+
segmentationStyle.setStyle({ segmentationId, type: SegmentationRepresentations.Contour }, {
|
|
42
|
+
fillAlpha: 0,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
annotationUIDsMap,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function computeContourFromLabelmapSegmentation(segmentationId, options = {}) {
|
|
49
|
+
if (!options.viewport) {
|
|
50
|
+
throw new Error('Viewport is required to compute contour from labelmap');
|
|
51
|
+
}
|
|
52
|
+
const results = await computeSurfaceFromLabelmapSegmentation(segmentationId, options);
|
|
53
|
+
if (!results?.length) {
|
|
54
|
+
console.error('Failed to convert labelmap to surface or labelmap is empty');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { viewport } = options;
|
|
58
|
+
const pointsAndPolys = results.map((surface) => {
|
|
59
|
+
return {
|
|
60
|
+
id: surface.segmentIndex.toString(),
|
|
61
|
+
points: surface.data.points,
|
|
62
|
+
polys: surface.data.polys,
|
|
63
|
+
segmentIndex: surface.segmentIndex,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const polyDataCache = await clipAndCacheSurfacesForViewport(pointsAndPolys, viewport);
|
|
67
|
+
const rawResults = extractContourData(polyDataCache);
|
|
68
|
+
return rawResults;
|
|
69
|
+
}
|
|
70
|
+
async function computeContourFromSurfaceSegmentation(segmentationId, options = {}) {
|
|
71
|
+
if (!options.viewport) {
|
|
72
|
+
throw new Error('Viewport is required to compute contour from surface');
|
|
73
|
+
}
|
|
74
|
+
const { viewport } = options;
|
|
75
|
+
const segmentIndices = options.segmentIndices?.length
|
|
76
|
+
? options.segmentIndices
|
|
77
|
+
: getUniqueSegmentIndices(segmentationId);
|
|
78
|
+
const segmentIndexToSurfaceId = new Map();
|
|
79
|
+
const surfaceIdToSegmentIndex = new Map();
|
|
80
|
+
const segmentation = getSegmentation(segmentationId);
|
|
81
|
+
const representationData = segmentation.representationData.Surface;
|
|
82
|
+
const surfacesInfo = [];
|
|
83
|
+
representationData.geometryIds.forEach((geometryId, segmentIndex) => {
|
|
84
|
+
if (segmentIndices.includes(segmentIndex)) {
|
|
85
|
+
segmentIndexToSurfaceId.set(segmentIndex, geometryId);
|
|
86
|
+
const surface = cache.getGeometry(geometryId)?.data;
|
|
87
|
+
if (surface) {
|
|
88
|
+
surfacesInfo.push({
|
|
89
|
+
id: geometryId,
|
|
90
|
+
points: surface.points,
|
|
91
|
+
polys: surface.polys,
|
|
92
|
+
segmentIndex,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
segmentIndexToSurfaceId.forEach((surfaceId, segmentIndex) => {
|
|
98
|
+
surfaceIdToSegmentIndex.set(surfaceId, segmentIndex);
|
|
99
|
+
});
|
|
100
|
+
const polyDataCache = await clipAndCacheSurfacesForViewport(surfacesInfo, viewport);
|
|
101
|
+
const rawResults = extractContourData(polyDataCache);
|
|
102
|
+
return rawResults;
|
|
103
|
+
}
|
|
104
|
+
export { computeContourFromLabelmapSegmentation };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { RawContourData } from '../contourComputationStrategies';
|
|
3
|
+
export declare function createAndAddContourSegmentationsFromClippedSurfaces(rawContourData: RawContourData, viewport: Types.IViewport, segmentationId: string): Map<number, Set<string>>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { utilities } from '@cornerstonejs/core';
|
|
2
|
+
import { PlanarFreehandContourSegmentationTool, annotation, } from '@cornerstonejs/tools';
|
|
3
|
+
const { addAnnotation } = annotation.state;
|
|
4
|
+
export function createAndAddContourSegmentationsFromClippedSurfaces(rawContourData, viewport, segmentationId) {
|
|
5
|
+
const annotationUIDsMap = new Map();
|
|
6
|
+
for (const [segmentIndex, contoursData] of rawContourData) {
|
|
7
|
+
for (const contourData of contoursData) {
|
|
8
|
+
const { points } = contourData;
|
|
9
|
+
const { lineSegments, linesNumberOfPoints } = _extractLineSegments(contourData);
|
|
10
|
+
for (let i = 0; i < lineSegments.length; i++) {
|
|
11
|
+
const line = lineSegments[i];
|
|
12
|
+
const polyline = [];
|
|
13
|
+
for (let j = 0; j < linesNumberOfPoints[i]; j++) {
|
|
14
|
+
const pointIndex = line[j];
|
|
15
|
+
polyline.push([
|
|
16
|
+
points[3 * pointIndex],
|
|
17
|
+
points[3 * pointIndex + 1],
|
|
18
|
+
points[3 * pointIndex + 2],
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
if (polyline.length < 3) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const contourSegmentationAnnotation = {
|
|
25
|
+
annotationUID: utilities.uuidv4(),
|
|
26
|
+
data: {
|
|
27
|
+
contour: {
|
|
28
|
+
closed: true,
|
|
29
|
+
polyline,
|
|
30
|
+
},
|
|
31
|
+
segmentation: {
|
|
32
|
+
segmentationId,
|
|
33
|
+
segmentIndex,
|
|
34
|
+
},
|
|
35
|
+
handles: {},
|
|
36
|
+
},
|
|
37
|
+
handles: {},
|
|
38
|
+
highlighted: false,
|
|
39
|
+
autoGenerated: false,
|
|
40
|
+
invalidated: false,
|
|
41
|
+
isLocked: false,
|
|
42
|
+
isVisible: true,
|
|
43
|
+
metadata: {
|
|
44
|
+
toolName: PlanarFreehandContourSegmentationTool.toolName,
|
|
45
|
+
...viewport.getViewReference(),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
addAnnotation(contourSegmentationAnnotation, viewport.element);
|
|
49
|
+
const currentSet = annotationUIDsMap?.get(segmentIndex) || new Set();
|
|
50
|
+
currentSet.add(contourSegmentationAnnotation.annotationUID);
|
|
51
|
+
annotationUIDsMap.set(segmentIndex, currentSet);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return annotationUIDsMap;
|
|
56
|
+
}
|
|
57
|
+
const _extractLineSegments = (contourData) => {
|
|
58
|
+
const { numberOfCells, lines } = contourData;
|
|
59
|
+
const lineSegments = [];
|
|
60
|
+
const linesNumberOfPoints = [];
|
|
61
|
+
for (let i = 0; i < lines.length;) {
|
|
62
|
+
const pointsInLine = lines[i];
|
|
63
|
+
linesNumberOfPoints.push(pointsInLine);
|
|
64
|
+
lineSegments.push(lines.slice(i + 1, i + pointsInLine + 1));
|
|
65
|
+
i += pointsInLine + 1;
|
|
66
|
+
if (lineSegments.length === numberOfCells) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { lineSegments, linesNumberOfPoints };
|
|
71
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function extractContourData(polyDataCache) {
|
|
2
|
+
const rawResults = new Map();
|
|
3
|
+
for (const [segmentIndex, intersectionInfo] of polyDataCache) {
|
|
4
|
+
const segmentIndexNumber = Number(segmentIndex);
|
|
5
|
+
for (const [_, result] of intersectionInfo) {
|
|
6
|
+
if (!result) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
if (!rawResults.has(segmentIndexNumber)) {
|
|
10
|
+
rawResults.set(segmentIndexNumber, []);
|
|
11
|
+
}
|
|
12
|
+
rawResults.get(segmentIndexNumber).push(result);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return rawResults;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function updateContoursOnCameraModified(surfacesInfo: any, viewport: any, segmentationRepresentationUID: any): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { utilities, Enums } from '@cornerstonejs/core';
|
|
2
|
+
import { extractContourData } from './extractContourData';
|
|
3
|
+
import { clipAndCacheSurfacesForViewport } from '../../utilities';
|
|
4
|
+
import { createAndAddContourSegmentationsFromClippedSurfaces } from './createAndAddContourSegmentationsFromClippedSurfaces';
|
|
5
|
+
const currentViewportNormal = new Map();
|
|
6
|
+
export function updateContoursOnCameraModified(surfacesInfo, viewport, segmentationRepresentationUID) {
|
|
7
|
+
async function cameraModifiedCallback(evt) {
|
|
8
|
+
const { camera } = evt.detail;
|
|
9
|
+
const { viewPlaneNormal } = camera;
|
|
10
|
+
const surface1 = surfacesInfo[0];
|
|
11
|
+
const currentNormal = currentViewportNormal.get(surface1.id);
|
|
12
|
+
if (utilities.isEqual(viewPlaneNormal, currentNormal)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
currentViewportNormal.set(surface1.id, viewPlaneNormal);
|
|
16
|
+
const polyDataCache = await clipAndCacheSurfacesForViewport(surfacesInfo, viewport);
|
|
17
|
+
const results = extractContourData(polyDataCache);
|
|
18
|
+
createAndAddContourSegmentationsFromClippedSurfaces(results, viewport, segmentationRepresentationUID);
|
|
19
|
+
viewport.render();
|
|
20
|
+
}
|
|
21
|
+
const camera = viewport.getCamera();
|
|
22
|
+
currentViewportNormal.set(surfacesInfo[0].id, camera.viewPlaneNormal);
|
|
23
|
+
viewport.element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedCallback);
|
|
24
|
+
viewport.element.addEventListener(Enums.Events.CAMERA_MODIFIED);
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Types as ToolsTypes } from '@cornerstonejs/tools';
|
|
2
|
+
import type { PolySegConversionOptions } from '../types';
|
|
3
|
+
export declare function convertContourToVolumeLabelmap(contourRepresentationData: ToolsTypes.ContourSegmentationData, options?: PolySegConversionOptions): Promise<{
|
|
4
|
+
volumeId: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function convertContourToStackLabelmap(contourRepresentationData: ToolsTypes.ContourSegmentationData, options?: PolySegConversionOptions): Promise<{
|
|
7
|
+
imageIds: any[];
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import { cache, utilities, getWebWorkerManager, volumeLoader, imageLoader, metaData, Enums, triggerEvent, eventTarget, } from '@cornerstonejs/core';
|
|
3
|
+
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
4
|
+
const { WorkerTypes } = cornerstoneTools.Enums;
|
|
5
|
+
const { getAnnotation } = cornerstoneTools.annotation.state;
|
|
6
|
+
const workerManager = getWebWorkerManager();
|
|
7
|
+
const triggerWorkerProgress = (eventTarget, progress) => {
|
|
8
|
+
triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
|
|
9
|
+
progress,
|
|
10
|
+
type: WorkerTypes.POLYSEG_CONTOUR_TO_LABELMAP,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export async function convertContourToVolumeLabelmap(contourRepresentationData, options = {}) {
|
|
14
|
+
const viewport = options.viewport;
|
|
15
|
+
const volumeId = viewport.getVolumeId();
|
|
16
|
+
const imageIds = utilities.getViewportImageIds(viewport);
|
|
17
|
+
if (!imageIds) {
|
|
18
|
+
throw new Error('No imageIds found, labelmap computation from contour requires viewports with imageIds');
|
|
19
|
+
}
|
|
20
|
+
const segmentationVolumeId = utilities.uuidv4();
|
|
21
|
+
const segmentationVolume = volumeLoader.createAndCacheDerivedLabelmapVolume(volumeId, {
|
|
22
|
+
volumeId: segmentationVolumeId,
|
|
23
|
+
});
|
|
24
|
+
const { dimensions, origin, direction, spacing, voxelManager } = segmentationVolume;
|
|
25
|
+
const { segmentIndices, annotationUIDsInSegmentMap } = _getAnnotationMapFromSegmentation(contourRepresentationData, options);
|
|
26
|
+
triggerWorkerProgress(eventTarget, 0);
|
|
27
|
+
const newScalarData = await workerManager.executeTask('polySeg', 'convertContourToVolumeLabelmap', {
|
|
28
|
+
segmentIndices,
|
|
29
|
+
dimensions,
|
|
30
|
+
scalarData: voxelManager.getCompleteScalarDataArray?.(),
|
|
31
|
+
origin,
|
|
32
|
+
direction,
|
|
33
|
+
spacing,
|
|
34
|
+
annotationUIDsInSegmentMap,
|
|
35
|
+
}, {
|
|
36
|
+
callbacks: [
|
|
37
|
+
(progress) => {
|
|
38
|
+
triggerWorkerProgress(eventTarget, progress);
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
triggerWorkerProgress(eventTarget, 100);
|
|
43
|
+
voxelManager.setCompleteScalarDataArray(newScalarData);
|
|
44
|
+
segmentationVolume.modified();
|
|
45
|
+
return {
|
|
46
|
+
volumeId: segmentationVolume.volumeId,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function convertContourToStackLabelmap(contourRepresentationData, options = {}) {
|
|
50
|
+
if (!options.viewport) {
|
|
51
|
+
throw new Error('No viewport provided, labelmap computation from contour requires viewports');
|
|
52
|
+
}
|
|
53
|
+
const viewport = options.viewport;
|
|
54
|
+
const imageIds = viewport.getImageIds();
|
|
55
|
+
if (!imageIds) {
|
|
56
|
+
throw new Error('No imageIds found, labelmap computation from contour requires viewports with imageIds');
|
|
57
|
+
}
|
|
58
|
+
imageIds.forEach((imageId) => {
|
|
59
|
+
if (!cache.getImageLoadObject(imageId)) {
|
|
60
|
+
throw new Error('ImageIds must be cached before converting contour to labelmap');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const segImages = await imageLoader.createAndCacheDerivedLabelmapImages(imageIds);
|
|
64
|
+
const segmentationImageIds = segImages.map((it) => it.imageId);
|
|
65
|
+
const { segmentIndices, annotationUIDsInSegmentMap } = _getAnnotationMapFromSegmentation(contourRepresentationData, options);
|
|
66
|
+
const segmentationsInfo = new Map();
|
|
67
|
+
segmentationImageIds.forEach((segImageId, index) => {
|
|
68
|
+
const segImage = cache.getImage(segImageId);
|
|
69
|
+
const imagePlaneModule = metaData.get(Enums.MetadataModules.IMAGE_PLANE, segImageId);
|
|
70
|
+
let { columnCosines, rowCosines, rowPixelSpacing, columnPixelSpacing, imagePositionPatient, } = imagePlaneModule;
|
|
71
|
+
columnCosines = columnCosines ?? [0, 1, 0];
|
|
72
|
+
rowCosines = rowCosines ?? [1, 0, 0];
|
|
73
|
+
rowPixelSpacing = rowPixelSpacing ?? 1;
|
|
74
|
+
columnPixelSpacing = columnPixelSpacing ?? 1;
|
|
75
|
+
imagePositionPatient = imagePositionPatient ?? [0, 0, 0];
|
|
76
|
+
const rowCosineVec = vec3.fromValues(rowCosines[0], rowCosines[1], rowCosines[2]);
|
|
77
|
+
const colCosineVec = vec3.fromValues(columnCosines[0], columnCosines[1], columnCosines[2]);
|
|
78
|
+
const scanAxisNormal = vec3.create();
|
|
79
|
+
vec3.cross(scanAxisNormal, rowCosineVec, colCosineVec);
|
|
80
|
+
const direction = [...rowCosineVec, ...colCosineVec, ...scanAxisNormal];
|
|
81
|
+
const spacing = [rowPixelSpacing, columnPixelSpacing, 1];
|
|
82
|
+
const origin = imagePositionPatient;
|
|
83
|
+
segmentationsInfo.set(imageIds[index], {
|
|
84
|
+
direction,
|
|
85
|
+
spacing,
|
|
86
|
+
origin,
|
|
87
|
+
scalarData: segImage.voxelManager.getScalarData(),
|
|
88
|
+
imageId: segImageId,
|
|
89
|
+
dimensions: [segImage.width, segImage.height, 1],
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
triggerWorkerProgress(eventTarget, 0);
|
|
93
|
+
const newSegmentationsScalarData = await workerManager.executeTask('polySeg', 'convertContourToStackLabelmap', {
|
|
94
|
+
segmentationsInfo,
|
|
95
|
+
annotationUIDsInSegmentMap,
|
|
96
|
+
segmentIndices,
|
|
97
|
+
}, {
|
|
98
|
+
callbacks: [
|
|
99
|
+
(progress) => {
|
|
100
|
+
triggerWorkerProgress(eventTarget, progress);
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
triggerWorkerProgress(eventTarget, 100);
|
|
105
|
+
const segImageIds = [];
|
|
106
|
+
newSegmentationsScalarData.forEach(({ scalarData }, referencedImageId) => {
|
|
107
|
+
const segmentationInfo = segmentationsInfo.get(referencedImageId);
|
|
108
|
+
const { imageId: segImageId } = segmentationInfo;
|
|
109
|
+
const segImage = cache.getImage(segImageId);
|
|
110
|
+
segImage.voxelManager.getScalarData().set(scalarData);
|
|
111
|
+
segImage.imageFrame?.pixelData?.set(scalarData);
|
|
112
|
+
segImageIds.push(segImageId);
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
imageIds: segImageIds,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function _getAnnotationMapFromSegmentation(contourRepresentationData, options = {}) {
|
|
119
|
+
const annotationMap = contourRepresentationData.annotationUIDsMap;
|
|
120
|
+
const segmentIndices = options.segmentIndices?.length
|
|
121
|
+
? options.segmentIndices
|
|
122
|
+
: Array.from(annotationMap.keys());
|
|
123
|
+
const annotationUIDsInSegmentMap = new Map();
|
|
124
|
+
segmentIndices.forEach((index) => {
|
|
125
|
+
const annotationUIDsInSegment = annotationMap.get(index);
|
|
126
|
+
let uids = Array.from(annotationUIDsInSegment);
|
|
127
|
+
uids = uids.filter((uid) => !getAnnotation(uid).parentAnnotationUID);
|
|
128
|
+
const annotations = uids.map((uid) => {
|
|
129
|
+
const annotation = getAnnotation(uid);
|
|
130
|
+
const hasChildAnnotations = annotation.childAnnotationUIDs?.length;
|
|
131
|
+
return {
|
|
132
|
+
polyline: annotation.data.contour.polyline,
|
|
133
|
+
referencedImageId: annotation.metadata.referencedImageId,
|
|
134
|
+
holesPolyline: hasChildAnnotations &&
|
|
135
|
+
annotation.childAnnotationUIDs.map((childUID) => {
|
|
136
|
+
const childAnnotation = getAnnotation(childUID);
|
|
137
|
+
return childAnnotation.data.contour.polyline;
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
annotationUIDsInSegmentMap.set(index, annotations);
|
|
142
|
+
});
|
|
143
|
+
return { segmentIndices, annotationUIDsInSegmentMap };
|
|
144
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { Types as ToolsTypes } from '@cornerstonejs/tools';
|
|
3
|
+
export declare function convertSurfaceToVolumeLabelmap(surfaceRepresentationData: ToolsTypes.SurfaceSegmentationData, segmentationVolume: Types.IImageVolume): Promise<{
|
|
4
|
+
volumeId: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function convertSurfaceToStackLabelmap(): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Enums, cache, eventTarget, getWebWorkerManager, triggerEvent, } from '@cornerstonejs/core';
|
|
2
|
+
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
3
|
+
const { WorkerTypes } = cornerstoneTools.Enums;
|
|
4
|
+
const workerManager = getWebWorkerManager();
|
|
5
|
+
const triggerWorkerProgress = (eventTarget, progress) => {
|
|
6
|
+
triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
|
|
7
|
+
progress,
|
|
8
|
+
type: WorkerTypes.POLYSEG_SURFACE_TO_LABELMAP,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
export async function convertSurfaceToVolumeLabelmap(surfaceRepresentationData, segmentationVolume) {
|
|
12
|
+
const { geometryIds } = surfaceRepresentationData;
|
|
13
|
+
if (!geometryIds?.size) {
|
|
14
|
+
throw new Error('No geometry IDs found for surface representation');
|
|
15
|
+
}
|
|
16
|
+
const segmentsInfo = new Map();
|
|
17
|
+
geometryIds.forEach((geometryId, segmentIndex) => {
|
|
18
|
+
const geometry = cache.getGeometry(geometryId);
|
|
19
|
+
const geometryData = geometry.data;
|
|
20
|
+
const points = geometryData.points;
|
|
21
|
+
const polys = geometryData.polys;
|
|
22
|
+
segmentsInfo.set(segmentIndex, {
|
|
23
|
+
points,
|
|
24
|
+
polys,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const { dimensions, direction, origin, spacing, voxelManager } = segmentationVolume;
|
|
28
|
+
triggerWorkerProgress(eventTarget, 0);
|
|
29
|
+
const newScalarData = await workerManager.executeTask('polySeg', 'convertSurfacesToVolumeLabelmap', {
|
|
30
|
+
segmentsInfo,
|
|
31
|
+
dimensions,
|
|
32
|
+
spacing,
|
|
33
|
+
direction,
|
|
34
|
+
origin,
|
|
35
|
+
}, {
|
|
36
|
+
callbacks: [
|
|
37
|
+
(progress) => {
|
|
38
|
+
triggerWorkerProgress(eventTarget, progress);
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
triggerWorkerProgress(eventTarget, 100);
|
|
43
|
+
voxelManager.setCompleteScalarDataArray(newScalarData);
|
|
44
|
+
segmentationVolume.modified();
|
|
45
|
+
return {
|
|
46
|
+
volumeId: segmentationVolume.volumeId,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function convertSurfaceToStackLabelmap() {
|
|
50
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Types as ToolsTypes } from '@cornerstonejs/tools';
|
|
2
|
+
import type { PolySegConversionOptions } from '../types';
|
|
3
|
+
export type RawLabelmapData = ToolsTypes.LabelmapSegmentationDataVolume | ToolsTypes.LabelmapSegmentationDataStack;
|
|
4
|
+
export declare function computeLabelmapData(segmentationId: string, options?: PolySegConversionOptions): Promise<RawLabelmapData>;
|
|
5
|
+
declare function computeLabelmapFromContourSegmentation(segmentationId: any, options?: PolySegConversionOptions): Promise<ToolsTypes.LabelmapSegmentationDataVolume | ToolsTypes.LabelmapSegmentationDataStack>;
|
|
6
|
+
export { computeLabelmapFromContourSegmentation };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { volumeLoader, imageLoader, VolumeViewport } from '@cornerstonejs/core';
|
|
2
|
+
import { utilities } from '@cornerstonejs/tools';
|
|
3
|
+
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
4
|
+
import { convertContourToStackLabelmap, convertContourToVolumeLabelmap, } from './convertContourToLabelmap';
|
|
5
|
+
import { convertSurfaceToVolumeLabelmap } from './convertSurfaceToLabelmap';
|
|
6
|
+
const { computeStackLabelmapFromVolume, getUniqueSegmentIndices } = utilities.segmentation;
|
|
7
|
+
const { getSegmentation } = cornerstoneTools.segmentation.state;
|
|
8
|
+
export async function computeLabelmapData(segmentationId, options = {}) {
|
|
9
|
+
const segmentIndices = options.segmentIndices?.length
|
|
10
|
+
? options.segmentIndices
|
|
11
|
+
: getUniqueSegmentIndices(segmentationId);
|
|
12
|
+
let rawLabelmapData;
|
|
13
|
+
const segmentation = getSegmentation(segmentationId);
|
|
14
|
+
const representationData = segmentation.representationData;
|
|
15
|
+
try {
|
|
16
|
+
if (representationData.Contour) {
|
|
17
|
+
rawLabelmapData = await computeLabelmapFromContourSegmentation(segmentationId, {
|
|
18
|
+
segmentIndices,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else if (representationData.Surface) {
|
|
23
|
+
rawLabelmapData = await computeLabelmapFromSurfaceSegmentation(segmentation.segmentationId, {
|
|
24
|
+
segmentIndices,
|
|
25
|
+
...options,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(error);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
if (!rawLabelmapData) {
|
|
34
|
+
throw new Error('Not enough data to convert to surface, currently only support converting volume labelmap to surface if available');
|
|
35
|
+
}
|
|
36
|
+
return rawLabelmapData;
|
|
37
|
+
}
|
|
38
|
+
async function computeLabelmapFromContourSegmentation(segmentationId, options = {}) {
|
|
39
|
+
const isVolume = options.viewport
|
|
40
|
+
? options.viewport instanceof VolumeViewport
|
|
41
|
+
: true;
|
|
42
|
+
if (isVolume && !options.viewport) {
|
|
43
|
+
throw new Error('Cannot compute labelmap from contour segmentation without providing the viewport');
|
|
44
|
+
}
|
|
45
|
+
const segmentIndices = options.segmentIndices?.length
|
|
46
|
+
? options.segmentIndices
|
|
47
|
+
: getUniqueSegmentIndices(segmentationId);
|
|
48
|
+
const segmentation = getSegmentation(segmentationId);
|
|
49
|
+
const representationData = segmentation.representationData.Contour;
|
|
50
|
+
const convertFunction = isVolume
|
|
51
|
+
? convertContourToVolumeLabelmap
|
|
52
|
+
: convertContourToStackLabelmap;
|
|
53
|
+
const result = await convertFunction(representationData, {
|
|
54
|
+
segmentIndices,
|
|
55
|
+
viewport: options.viewport,
|
|
56
|
+
});
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
async function computeLabelmapFromSurfaceSegmentation(segmentationId, options = {}) {
|
|
60
|
+
const { viewport } = options;
|
|
61
|
+
const isVolume = viewport ? viewport instanceof VolumeViewport : true;
|
|
62
|
+
const segmentIndices = options.segmentIndices?.length
|
|
63
|
+
? options.segmentIndices
|
|
64
|
+
: getUniqueSegmentIndices(segmentationId);
|
|
65
|
+
const segmentation = getSegmentation(segmentationId);
|
|
66
|
+
const segmentsGeometryIds = new Map();
|
|
67
|
+
const representationData = segmentation.representationData.Surface;
|
|
68
|
+
representationData.geometryIds.forEach((geometryId, segmentIndex) => {
|
|
69
|
+
if (segmentIndices.includes(segmentIndex)) {
|
|
70
|
+
segmentsGeometryIds.set(segmentIndex, geometryId);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (isVolume && !viewport) {
|
|
74
|
+
throw new Error('Cannot compute labelmap from surface segmentation without providing the viewport');
|
|
75
|
+
}
|
|
76
|
+
let segmentationVolume;
|
|
77
|
+
if (isVolume) {
|
|
78
|
+
const volumeId = viewport.getVolumeId();
|
|
79
|
+
segmentationVolume =
|
|
80
|
+
volumeLoader.createAndCacheDerivedLabelmapVolume(volumeId);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const imageIds = options.viewport.getImageIds();
|
|
84
|
+
const segImages = imageLoader.createAndCacheDerivedLabelmapImages(imageIds);
|
|
85
|
+
const segImageIds = segImages.map((image) => image.imageId);
|
|
86
|
+
segmentationVolume = await volumeLoader.createAndCacheVolumeFromImages('generatedSegmentationVolumeId', segImageIds);
|
|
87
|
+
}
|
|
88
|
+
const result = await convertSurfaceToVolumeLabelmap({ geometryIds: segmentsGeometryIds }, segmentationVolume);
|
|
89
|
+
if (isVolume) {
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
const stackData = (await computeStackLabelmapFromVolume({
|
|
93
|
+
volumeId: segmentationVolume.volumeId,
|
|
94
|
+
}));
|
|
95
|
+
return stackData;
|
|
96
|
+
}
|
|
97
|
+
export { computeLabelmapFromContourSegmentation };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { Types as ToolsTypes } from '@cornerstonejs/tools';
|
|
3
|
+
export declare function convertContourToSurface(contourRepresentationData: ToolsTypes.ContourSegmentationData, segmentIndex: number): Promise<Types.SurfaceData>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Enums, eventTarget, triggerEvent, getWebWorkerManager, } from '@cornerstonejs/core';
|
|
2
|
+
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
3
|
+
const { WorkerTypes } = cornerstoneTools.Enums;
|
|
4
|
+
const { getAnnotation } = cornerstoneTools.annotation.state;
|
|
5
|
+
const workerManager = getWebWorkerManager();
|
|
6
|
+
const triggerWorkerProgress = (eventTarget, progress, id) => {
|
|
7
|
+
triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
|
|
8
|
+
progress,
|
|
9
|
+
type: WorkerTypes.POLYSEG_CONTOUR_TO_SURFACE,
|
|
10
|
+
id,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export async function convertContourToSurface(contourRepresentationData, segmentIndex) {
|
|
14
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
15
|
+
const polylines = [];
|
|
16
|
+
const numPointsArray = [];
|
|
17
|
+
const annotationUIDs = annotationUIDsMap.get(segmentIndex);
|
|
18
|
+
for (const annotationUID of annotationUIDs) {
|
|
19
|
+
const annotation = getAnnotation(annotationUID);
|
|
20
|
+
const { polyline } = annotation.data.contour;
|
|
21
|
+
numPointsArray.push(polyline.length);
|
|
22
|
+
polyline.forEach((polyline) => polylines.push(...polyline));
|
|
23
|
+
}
|
|
24
|
+
triggerWorkerProgress(eventTarget, 0, segmentIndex);
|
|
25
|
+
const results = await workerManager.executeTask('polySeg', 'convertContourToSurface', {
|
|
26
|
+
polylines,
|
|
27
|
+
numPointsArray,
|
|
28
|
+
}, {
|
|
29
|
+
callbacks: [
|
|
30
|
+
(progress) => {
|
|
31
|
+
triggerWorkerProgress(eventTarget, progress, segmentIndex);
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
triggerWorkerProgress(eventTarget, 100, segmentIndex);
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { Types as ToolsTypes } from '@cornerstonejs/tools';
|
|
3
|
+
export declare function convertLabelmapToSurface(labelmapRepresentationData: ToolsTypes.LabelmapSegmentationData, segmentIndex: number): Promise<Types.SurfaceData>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cache, eventTarget, getWebWorkerManager, triggerEvent, Enums, } from '@cornerstonejs/core';
|
|
2
|
+
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
3
|
+
const { WorkerTypes } = cornerstoneTools.Enums;
|
|
4
|
+
const { computeVolumeLabelmapFromStack } = cornerstoneTools.utilities.segmentation;
|
|
5
|
+
const workerManager = getWebWorkerManager();
|
|
6
|
+
const triggerWorkerProgress = (eventTarget, progress, id) => {
|
|
7
|
+
triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
|
|
8
|
+
progress,
|
|
9
|
+
type: WorkerTypes.POLYSEG_LABELMAP_TO_SURFACE,
|
|
10
|
+
id,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export async function convertLabelmapToSurface(labelmapRepresentationData, segmentIndex) {
|
|
14
|
+
let volumeId;
|
|
15
|
+
if (labelmapRepresentationData
|
|
16
|
+
.volumeId) {
|
|
17
|
+
volumeId = labelmapRepresentationData.volumeId;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const { imageIds } = labelmapRepresentationData;
|
|
21
|
+
({ volumeId } = await computeVolumeLabelmapFromStack({
|
|
22
|
+
imageIds,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
const volume = cache.getVolume(volumeId);
|
|
26
|
+
const scalarData = volume.voxelManager.getCompleteScalarDataArray();
|
|
27
|
+
const { dimensions, spacing, origin, direction } = volume;
|
|
28
|
+
triggerWorkerProgress(eventTarget, 0, segmentIndex);
|
|
29
|
+
const results = await workerManager.executeTask('polySeg', 'convertLabelmapToSurface', {
|
|
30
|
+
scalarData,
|
|
31
|
+
dimensions,
|
|
32
|
+
spacing,
|
|
33
|
+
origin,
|
|
34
|
+
direction,
|
|
35
|
+
segmentIndex,
|
|
36
|
+
}, {
|
|
37
|
+
callbacks: [
|
|
38
|
+
(progress) => {
|
|
39
|
+
triggerWorkerProgress(eventTarget, progress, segmentIndex);
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
triggerWorkerProgress(eventTarget, 100, segmentIndex);
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RawSurfacesData } from './surfaceComputationStrategies';
|
|
2
|
+
import type { PolySegConversionOptions } from '../types';
|
|
3
|
+
export declare function createAndCacheSurfacesFromRaw(segmentationId: string, rawSurfacesData: RawSurfacesData, options?: PolySegConversionOptions): Promise<{
|
|
4
|
+
geometryIds: Map<number, string>;
|
|
5
|
+
}>;
|