@cornerstonejs/tools 3.20.2 → 3.21.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/stateManagement/segmentation/index.d.ts +2 -1
- package/dist/esm/stateManagement/segmentation/index.js +2 -1
- package/dist/esm/stateManagement/segmentation/utilities/decimateContours.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/utilities/decimateContours.js +40 -0
- package/dist/esm/stateManagement/segmentation/utilities/extractSegmentPolylines.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/utilities/extractSegmentPolylines.js +36 -0
- package/dist/esm/stateManagement/segmentation/utilities/getAnnotationMapFromSegmentation.d.ts +20 -0
- package/dist/esm/stateManagement/segmentation/utilities/getAnnotationMapFromSegmentation.js +40 -0
- package/dist/esm/stateManagement/segmentation/utilities/getPolylineMap.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/utilities/getPolylineMap.js +34 -0
- package/dist/esm/stateManagement/segmentation/utilities/getViewportAssociatedToSegmentation.d.ts +5 -0
- package/dist/esm/stateManagement/segmentation/utilities/getViewportAssociatedToSegmentation.js +42 -0
- package/dist/esm/stateManagement/segmentation/utilities/index.d.ts +8 -0
- package/dist/esm/stateManagement/segmentation/utilities/index.js +8 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeCompleteContourAnnotation.d.ts +2 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeCompleteContourAnnotation.js +12 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeContourHoles.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeContourHoles.js +33 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeContourIslands.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/utilities/removeContourIslands.js +30 -0
- package/dist/esm/stateManagement/segmentation/utilities/smoothContours.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/utilities/smoothContours.js +38 -0
- package/dist/esm/utilities/contours/findContourHoles.d.ts +7 -0
- package/dist/esm/utilities/contours/findContourHoles.js +39 -0
- package/dist/esm/utilities/contours/findIslands.d.ts +2 -0
- package/dist/esm/utilities/contours/findIslands.js +25 -0
- package/dist/esm/utilities/contours/index.d.ts +3 -1
- package/dist/esm/utilities/contours/index.js +3 -1
- package/dist/esm/utilities/planarFreehandROITool/smoothAnnotation.js +2 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
|
@@ -9,6 +9,7 @@ import * as state from './segmentationState';
|
|
|
9
9
|
import * as config from './config';
|
|
10
10
|
import * as segmentIndex from './segmentIndex';
|
|
11
11
|
import * as triggerSegmentationEvents from './triggerSegmentationEvents';
|
|
12
|
+
import * as utilities from './utilities';
|
|
12
13
|
import { convertStackToVolumeLabelmap } from './helpers/convertStackToVolumeLabelmap';
|
|
13
14
|
import { computeVolumeLabelmapFromStack } from './helpers/computeVolumeLabelmapFromStack';
|
|
14
15
|
import { clearSegmentValue } from './helpers/clearSegmentValue';
|
|
@@ -27,4 +28,4 @@ declare const helpers: {
|
|
|
27
28
|
computeVolumeLabelmapFromStack: typeof computeVolumeLabelmapFromStack;
|
|
28
29
|
convertVolumeToStackLabelmap: typeof convertVolumeToStackLabelmap;
|
|
29
30
|
};
|
|
30
|
-
export { removeSegmentationRepresentation, removeContourRepresentation, removeLabelmapRepresentation, removeSurfaceRepresentation, removeAllSegmentations, removeSegmentation, removeSegmentationRepresentations, addLabelmapRepresentationToViewport, addLabelmapRepresentationToViewportMap, addSegmentationRepresentations, removeAllSegmentationRepresentations, addContourRepresentationToViewport, addContourRepresentationToViewportMap, addSurfaceRepresentationToViewport, addSurfaceRepresentationToViewportMap, addSegmentations, updateSegmentations, state, activeSegmentation, segmentLocking, config, segmentIndex, triggerSegmentationEvents, helpers, removeSegment, getLabelmapImageIds, addRepresentationData, strategies, segmentationStyle, defaultSegmentationStateManager, getCurrentLabelmapImageIdsForViewport, getLabelmapImageIdsForImageId, getActiveSegmentation, };
|
|
31
|
+
export { removeSegmentationRepresentation, removeContourRepresentation, removeLabelmapRepresentation, removeSurfaceRepresentation, removeAllSegmentations, removeSegmentation, removeSegmentationRepresentations, addLabelmapRepresentationToViewport, addLabelmapRepresentationToViewportMap, addSegmentationRepresentations, removeAllSegmentationRepresentations, addContourRepresentationToViewport, addContourRepresentationToViewportMap, addSurfaceRepresentationToViewport, addSurfaceRepresentationToViewportMap, addSegmentations, updateSegmentations, state, activeSegmentation, segmentLocking, config, segmentIndex, triggerSegmentationEvents, utilities, helpers, removeSegment, getLabelmapImageIds, addRepresentationData, strategies, segmentationStyle, defaultSegmentationStateManager, getCurrentLabelmapImageIdsForViewport, getLabelmapImageIdsForImageId, getActiveSegmentation, };
|
|
@@ -9,6 +9,7 @@ import * as state from './segmentationState';
|
|
|
9
9
|
import * as config from './config';
|
|
10
10
|
import * as segmentIndex from './segmentIndex';
|
|
11
11
|
import * as triggerSegmentationEvents from './triggerSegmentationEvents';
|
|
12
|
+
import * as utilities from './utilities';
|
|
12
13
|
import { convertStackToVolumeLabelmap } from './helpers/convertStackToVolumeLabelmap';
|
|
13
14
|
import { computeVolumeLabelmapFromStack } from './helpers/computeVolumeLabelmapFromStack';
|
|
14
15
|
import { clearSegmentValue } from './helpers/clearSegmentValue';
|
|
@@ -27,4 +28,4 @@ const helpers = {
|
|
|
27
28
|
computeVolumeLabelmapFromStack,
|
|
28
29
|
convertVolumeToStackLabelmap,
|
|
29
30
|
};
|
|
30
|
-
export { removeSegmentationRepresentation, removeContourRepresentation, removeLabelmapRepresentation, removeSurfaceRepresentation, removeAllSegmentations, removeSegmentation, removeSegmentationRepresentations, addLabelmapRepresentationToViewport, addLabelmapRepresentationToViewportMap, addSegmentationRepresentations, removeAllSegmentationRepresentations, addContourRepresentationToViewport, addContourRepresentationToViewportMap, addSurfaceRepresentationToViewport, addSurfaceRepresentationToViewportMap, addSegmentations, updateSegmentations, state, activeSegmentation, segmentLocking, config, segmentIndex, triggerSegmentationEvents, helpers, removeSegment, getLabelmapImageIds, addRepresentationData, strategies, segmentationStyle, defaultSegmentationStateManager, getCurrentLabelmapImageIdsForViewport, getLabelmapImageIdsForImageId, getActiveSegmentation, };
|
|
31
|
+
export { removeSegmentationRepresentation, removeContourRepresentation, removeLabelmapRepresentation, removeSurfaceRepresentation, removeAllSegmentations, removeSegmentation, removeSegmentationRepresentations, addLabelmapRepresentationToViewport, addLabelmapRepresentationToViewportMap, addSegmentationRepresentations, removeAllSegmentationRepresentations, addContourRepresentationToViewport, addContourRepresentationToViewportMap, addSurfaceRepresentationToViewport, addSurfaceRepresentationToViewportMap, addSegmentations, updateSegmentations, state, activeSegmentation, segmentLocking, config, segmentIndex, triggerSegmentationEvents, utilities, helpers, removeSegment, getLabelmapImageIds, addRepresentationData, strategies, segmentationStyle, defaultSegmentationStateManager, getCurrentLabelmapImageIdsForViewport, getLabelmapImageIdsForImageId, getActiveSegmentation, };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getAnnotation, invalidateAnnotation, } from '../../annotation/annotationState';
|
|
2
|
+
import { getSegmentation } from '../getSegmentation';
|
|
3
|
+
import { extractSegmentPolylines } from './extractSegmentPolylines';
|
|
4
|
+
import decimate from '../../../utilities/math/polyline/decimate';
|
|
5
|
+
import { getViewportsAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
|
|
6
|
+
export default function decimateContours(segmentationId, segmentIndex, options = { epsilon: 0.1 }) {
|
|
7
|
+
const segmentation = getSegmentation(segmentationId);
|
|
8
|
+
if (!segmentation) {
|
|
9
|
+
console.warn(`Invalid segmentation given ${segmentationId}`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!segmentation.representationData.Contour) {
|
|
13
|
+
console.warn(`No contour representation found for segmentation ${segmentationId}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const viewports = getViewportsAssociatedToSegmentation(segmentationId);
|
|
17
|
+
if (!viewports) {
|
|
18
|
+
console.warn('No viewport associated to the segmentation found');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const polylinesCanvasMap = extractSegmentPolylines(segmentationId, segmentIndex);
|
|
22
|
+
if (!polylinesCanvasMap) {
|
|
23
|
+
console.warn(`Error extracting contour data from segment ${segmentIndex} in segmentation ${segmentationId}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const keys = Array.from(polylinesCanvasMap?.keys());
|
|
27
|
+
for (const annotationUID of keys) {
|
|
28
|
+
const annotation = getAnnotation(annotationUID);
|
|
29
|
+
if (!annotation) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const polylineCanvas = polylinesCanvasMap.get(annotationUID);
|
|
33
|
+
const decimatedPolyline2D = decimate(polylineCanvas, options.epsilon);
|
|
34
|
+
const viewport = getViewportWithMatchingViewPlaneNormal(viewports, annotation);
|
|
35
|
+
if (viewport) {
|
|
36
|
+
annotation.data.contour.polyline = decimatedPolyline2D.map((point2D) => viewport.canvasToWorld(point2D));
|
|
37
|
+
invalidateAnnotation(annotation);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function extractSegmentPolylines(segmentationId: string, segmentIndex: number): Map<any, any>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getSegmentation } from '../getSegmentation';
|
|
2
|
+
import { convertContourPolylineToCanvasSpace } from '../../../utilities/contourSegmentation';
|
|
3
|
+
import { getViewportsAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
|
|
4
|
+
import { getPolylinesMap } from './getPolylineMap';
|
|
5
|
+
import { getAnnotation } from '../../annotation/annotationState';
|
|
6
|
+
export function extractSegmentPolylines(segmentationId, segmentIndex) {
|
|
7
|
+
const viewports = getViewportsAssociatedToSegmentation(segmentationId);
|
|
8
|
+
const segmentation = getSegmentation(segmentationId);
|
|
9
|
+
if (!segmentation) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!segmentation.representationData.Contour) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const contourRepresentationData = segmentation.representationData
|
|
16
|
+
.Contour;
|
|
17
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
18
|
+
if (!annotationUIDsMap) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!annotationUIDsMap.get(segmentIndex)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const polyLinesMap = getPolylinesMap(contourRepresentationData, segmentIndex);
|
|
25
|
+
if (!polyLinesMap) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const keys = Array.from(polyLinesMap?.keys());
|
|
29
|
+
const polylinesCanvasMap = new Map();
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const annotation = getAnnotation(key);
|
|
32
|
+
const viewport = getViewportWithMatchingViewPlaneNormal(viewports, annotation);
|
|
33
|
+
polylinesCanvasMap.set(key, convertContourPolylineToCanvasSpace(polyLinesMap.get(key), viewport));
|
|
34
|
+
}
|
|
35
|
+
return polylinesCanvasMap;
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type * as ToolsTypes from '../../../types';
|
|
3
|
+
export type MapOptions = {
|
|
4
|
+
segmentIndices?: number[];
|
|
5
|
+
segmentationId?: string;
|
|
6
|
+
viewport?: Types.IStackViewport | Types.IVolumeViewport;
|
|
7
|
+
};
|
|
8
|
+
export type AnnotationInfo = {
|
|
9
|
+
polyline: Types.Point3[];
|
|
10
|
+
isClosed: boolean;
|
|
11
|
+
annotationUID: string;
|
|
12
|
+
referencedImageId: string;
|
|
13
|
+
holesPolyline: Types.Point3[][];
|
|
14
|
+
holesUIDs: string[];
|
|
15
|
+
holesClosed: boolean[];
|
|
16
|
+
};
|
|
17
|
+
export declare function getAnnotationMapFromSegmentation(contourRepresentationData: ToolsTypes.ContourSegmentationData, options?: MapOptions): {
|
|
18
|
+
segmentIndices: number[];
|
|
19
|
+
annotationUIDsInSegmentMap: Map<number, unknown>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getAnnotation } from '../../annotation/annotationState';
|
|
2
|
+
export function getAnnotationMapFromSegmentation(contourRepresentationData, options = {}) {
|
|
3
|
+
const annotationMap = contourRepresentationData.annotationUIDsMap;
|
|
4
|
+
const segmentIndices = options.segmentIndices?.length
|
|
5
|
+
? options.segmentIndices
|
|
6
|
+
: Array.from(annotationMap.keys());
|
|
7
|
+
const annotationUIDsInSegmentMap = new Map();
|
|
8
|
+
segmentIndices.forEach((index) => {
|
|
9
|
+
const annotationUIDsInSegment = annotationMap.get(index);
|
|
10
|
+
let uids = Array.from(annotationUIDsInSegment);
|
|
11
|
+
uids = uids.filter((uid) => !getAnnotation(uid).parentAnnotationUID);
|
|
12
|
+
const annotations = uids.map((uid) => {
|
|
13
|
+
const annotation = getAnnotation(uid);
|
|
14
|
+
const hasChildAnnotations = annotation.childAnnotationUIDs?.length;
|
|
15
|
+
const childPolylinesInformation = hasChildAnnotations &&
|
|
16
|
+
annotation.childAnnotationUIDs.map((childUID) => {
|
|
17
|
+
const childAnnotation = getAnnotation(childUID);
|
|
18
|
+
return {
|
|
19
|
+
polyline: childAnnotation.data.contour.polyline,
|
|
20
|
+
isClosed: childAnnotation.data.contour.closed,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const holesClosed = hasChildAnnotations &&
|
|
24
|
+
childPolylinesInformation.map((childInfo) => childInfo.isClosed);
|
|
25
|
+
const childPolylines = hasChildAnnotations &&
|
|
26
|
+
childPolylinesInformation.map((childInfo) => childInfo.polyline);
|
|
27
|
+
return {
|
|
28
|
+
polyline: annotation.data.contour.polyline,
|
|
29
|
+
isClosed: annotation.data.contour.closed,
|
|
30
|
+
annotationUID: annotation.annotationUID,
|
|
31
|
+
referencedImageId: annotation.metadata.referencedImageId,
|
|
32
|
+
holesPolyline: childPolylines,
|
|
33
|
+
holesUIDs: annotation.childAnnotationUIDs,
|
|
34
|
+
holesClosed,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
annotationUIDsInSegmentMap.set(index, annotations);
|
|
38
|
+
});
|
|
39
|
+
return { segmentIndices, annotationUIDsInSegmentMap };
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getAnnotationMapFromSegmentation, } from './getAnnotationMapFromSegmentation';
|
|
2
|
+
function closePolyline(polyline, closed) {
|
|
3
|
+
if (!polyline || polyline.length === 0) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
if (!closed) {
|
|
7
|
+
return [...polyline];
|
|
8
|
+
}
|
|
9
|
+
const firstPoint = polyline[0];
|
|
10
|
+
const lastPoint = polyline[polyline.length - 1];
|
|
11
|
+
const isAlreadyClosed = firstPoint[0] === lastPoint[0] &&
|
|
12
|
+
firstPoint[1] === lastPoint[1] &&
|
|
13
|
+
firstPoint[2] === lastPoint[2];
|
|
14
|
+
if (isAlreadyClosed) {
|
|
15
|
+
return [...polyline];
|
|
16
|
+
}
|
|
17
|
+
return [...polyline, firstPoint];
|
|
18
|
+
}
|
|
19
|
+
export function getPolylinesMap(contourRepresentationData, segmentIndex) {
|
|
20
|
+
const { annotationUIDsInSegmentMap } = getAnnotationMapFromSegmentation(contourRepresentationData);
|
|
21
|
+
if (!annotationUIDsInSegmentMap.has(segmentIndex)) {
|
|
22
|
+
console.warn(`No contour information found for segmentIndex ${segmentIndex}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const polylines = new Map();
|
|
26
|
+
const annotationsInfo = annotationUIDsInSegmentMap.get(segmentIndex);
|
|
27
|
+
for (const annotationInfo of annotationsInfo) {
|
|
28
|
+
polylines.set(annotationInfo.annotationUID, closePolyline(annotationInfo.polyline, annotationInfo.isClosed));
|
|
29
|
+
for (let i = 0; i < annotationInfo.holesUIDs?.length; i++) {
|
|
30
|
+
polylines.set(annotationInfo.holesUIDs[i], closePolyline(annotationInfo.holesPolyline[i], annotationInfo.holesClosed[i]));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return polylines;
|
|
34
|
+
}
|
package/dist/esm/stateManagement/segmentation/utilities/getViewportAssociatedToSegmentation.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
import type { ContourSegmentationAnnotation } from '../../../types';
|
|
3
|
+
export declare function getViewportsAssociatedToSegmentation(segmentationId: string): Types.IViewport[];
|
|
4
|
+
export declare function getViewportAssociatedToSegmentation(segmentationId: string): import("@cornerstonejs/core").Viewport;
|
|
5
|
+
export declare function getViewportWithMatchingViewPlaneNormal(viewports: Types.IViewport[], annotation: ContourSegmentationAnnotation, dotThreshold?: number): Types.IViewport | undefined;
|
package/dist/esm/stateManagement/segmentation/utilities/getViewportAssociatedToSegmentation.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import { getEnabledElementByViewportId } from '@cornerstonejs/core';
|
|
3
|
+
import { getViewportIdsWithSegmentation } from '../getViewportIdsWithSegmentation';
|
|
4
|
+
export function getViewportsAssociatedToSegmentation(segmentationId) {
|
|
5
|
+
const viewportIds = getViewportIdsWithSegmentation(segmentationId);
|
|
6
|
+
if (viewportIds?.length === 0) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const viewports = [];
|
|
10
|
+
for (const viewportId of viewportIds) {
|
|
11
|
+
const { viewport } = getEnabledElementByViewportId(viewportId) || {};
|
|
12
|
+
if (viewport) {
|
|
13
|
+
viewports.push(viewport);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return viewports;
|
|
17
|
+
}
|
|
18
|
+
export function getViewportAssociatedToSegmentation(segmentationId) {
|
|
19
|
+
const viewports = getViewportsAssociatedToSegmentation(segmentationId);
|
|
20
|
+
return viewports.length > 0 ? viewports[0] : undefined;
|
|
21
|
+
}
|
|
22
|
+
export function getViewportWithMatchingViewPlaneNormal(viewports, annotation, dotThreshold = 0.99) {
|
|
23
|
+
const annotationViewPlaneNormal = annotation.metadata?.viewPlaneNormal;
|
|
24
|
+
if (!annotationViewPlaneNormal || !Array.isArray(annotationViewPlaneNormal)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const normalizedAnnotationNormal = vec3.create();
|
|
28
|
+
vec3.normalize(normalizedAnnotationNormal, annotationViewPlaneNormal);
|
|
29
|
+
for (const viewport of viewports) {
|
|
30
|
+
const camera = viewport.getCamera();
|
|
31
|
+
if (!camera?.viewPlaneNormal) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const normalizedCameraNormal = vec3.create();
|
|
35
|
+
vec3.normalize(normalizedCameraNormal, camera.viewPlaneNormal);
|
|
36
|
+
const dotProduct = vec3.dot(normalizedAnnotationNormal, normalizedCameraNormal);
|
|
37
|
+
if (Math.abs(dotProduct) >= dotThreshold) {
|
|
38
|
+
return viewport;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { getViewportAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
|
|
2
|
+
export { getAnnotationMapFromSegmentation } from './getAnnotationMapFromSegmentation';
|
|
3
|
+
export { default as decimateContours } from './decimateContours';
|
|
4
|
+
export { extractSegmentPolylines } from './extractSegmentPolylines';
|
|
5
|
+
export { removeCompleteContourAnnotation } from './removeCompleteContourAnnotation';
|
|
6
|
+
export { default as removeContourHoles } from './removeContourHoles';
|
|
7
|
+
export { default as removeContourIslands } from './removeContourIslands';
|
|
8
|
+
export { default as smoothContours } from './smoothContours';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { getViewportAssociatedToSegmentation, getViewportWithMatchingViewPlaneNormal, } from './getViewportAssociatedToSegmentation';
|
|
2
|
+
export { getAnnotationMapFromSegmentation } from './getAnnotationMapFromSegmentation';
|
|
3
|
+
export { default as decimateContours } from './decimateContours';
|
|
4
|
+
export { extractSegmentPolylines } from './extractSegmentPolylines';
|
|
5
|
+
export { removeCompleteContourAnnotation } from './removeCompleteContourAnnotation';
|
|
6
|
+
export { default as removeContourHoles } from './removeContourHoles';
|
|
7
|
+
export { default as removeContourIslands } from './removeContourIslands';
|
|
8
|
+
export { default as smoothContours } from './smoothContours';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { removeContourSegmentationAnnotation } from '../../../utilities/contourSegmentation';
|
|
2
|
+
import { clearParentAnnotation, removeAnnotation, } from '../../annotation/annotationState';
|
|
3
|
+
export function removeCompleteContourAnnotation(annotation) {
|
|
4
|
+
if (!annotation) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (annotation.parentAnnotationUID) {
|
|
8
|
+
clearParentAnnotation(annotation);
|
|
9
|
+
}
|
|
10
|
+
removeAnnotation(annotation.annotationUID);
|
|
11
|
+
removeContourSegmentationAnnotation(annotation);
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function removeContourHoles(segmentationId: string, segmentIndex: number): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { findContourHoles } from '../../../utilities/contours';
|
|
2
|
+
import { getAnnotation } from '../../annotation/annotationState';
|
|
3
|
+
import { triggerAnnotationRemoved } from '../../annotation/helpers/state';
|
|
4
|
+
import { getSegmentation } from '../getSegmentation';
|
|
5
|
+
import { extractSegmentPolylines } from './extractSegmentPolylines';
|
|
6
|
+
import { removeCompleteContourAnnotation } from './removeCompleteContourAnnotation';
|
|
7
|
+
export default function removeContourHoles(segmentationId, segmentIndex) {
|
|
8
|
+
const segmentation = getSegmentation(segmentationId);
|
|
9
|
+
if (!segmentation) {
|
|
10
|
+
console.warn(`Invalid segmentation given ${segmentationId}`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (!segmentation.representationData.Contour) {
|
|
14
|
+
console.warn(`No contour representation found for segmentation ${segmentationId}`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const polylinesCanvasMap = extractSegmentPolylines(segmentationId, segmentIndex);
|
|
18
|
+
if (!polylinesCanvasMap) {
|
|
19
|
+
console.warn(`Error extracting contour data from segment ${segmentIndex} in segmentation ${segmentationId}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const keys = Array.from(polylinesCanvasMap?.keys());
|
|
23
|
+
const polylines = keys.map((key) => polylinesCanvasMap.get(key));
|
|
24
|
+
const holeDetectionResults = findContourHoles(polylines);
|
|
25
|
+
if (holeDetectionResults?.length > 0) {
|
|
26
|
+
holeDetectionResults.forEach((hole) => {
|
|
27
|
+
hole.holeIndexes.forEach((index) => {
|
|
28
|
+
const annotation = getAnnotation(keys[index]);
|
|
29
|
+
removeCompleteContourAnnotation(annotation);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getAnnotation } from '../../annotation/annotationState';
|
|
2
|
+
import { getSegmentation } from '../getSegmentation';
|
|
3
|
+
import { extractSegmentPolylines } from './extractSegmentPolylines';
|
|
4
|
+
import findIslands from '../../../utilities/contours/findIslands';
|
|
5
|
+
import { removeCompleteContourAnnotation } from './removeCompleteContourAnnotation';
|
|
6
|
+
export default function removeContourIslands(segmentationId, segmentIndex, options = { threshold: 3 }) {
|
|
7
|
+
const segmentation = getSegmentation(segmentationId);
|
|
8
|
+
if (!segmentation) {
|
|
9
|
+
console.warn(`Invalid segmentation given ${segmentationId}`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!segmentation.representationData.Contour) {
|
|
13
|
+
console.warn(`No contour representation found for segmentation ${segmentationId}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const polylinesCanvasMap = extractSegmentPolylines(segmentationId, segmentIndex);
|
|
17
|
+
if (!polylinesCanvasMap) {
|
|
18
|
+
console.warn(`Error extracting contour data from segment ${segmentIndex} in segmentation ${segmentationId}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const keys = Array.from(polylinesCanvasMap?.keys());
|
|
22
|
+
const polylines = keys.map((key) => polylinesCanvasMap.get(key));
|
|
23
|
+
const islands = findIslands(polylines, options.threshold);
|
|
24
|
+
if (islands?.length > 0) {
|
|
25
|
+
islands.forEach((index) => {
|
|
26
|
+
const annotation = getAnnotation(keys[index]);
|
|
27
|
+
removeCompleteContourAnnotation(annotation);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getAnnotation } from '../../annotation/annotationState';
|
|
2
|
+
import { getSegmentation } from '../getSegmentation';
|
|
3
|
+
import interpolateSegmentPoints from '../../../utilities/planarFreehandROITool/interpolation/interpolateSegmentPoints';
|
|
4
|
+
export default function smoothContours(segmentationId, segmentIndex, options = { knotsRatioPercentage: 30 }) {
|
|
5
|
+
const segmentation = getSegmentation(segmentationId);
|
|
6
|
+
if (!segmentation) {
|
|
7
|
+
console.warn(`Invalid segmentation given ${segmentationId}`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!segmentation.representationData.Contour) {
|
|
11
|
+
console.warn(`No contour representation found for segmentation ${segmentationId}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const contourRepresentationData = segmentation.representationData
|
|
15
|
+
.Contour;
|
|
16
|
+
const { annotationUIDsMap } = contourRepresentationData;
|
|
17
|
+
if (!annotationUIDsMap) {
|
|
18
|
+
console.warn(`No contours found for segmentation ${segmentationId}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!annotationUIDsMap.has(segmentIndex)) {
|
|
22
|
+
console.warn(`Error extracting contour data from segment ${segmentIndex} in segmentation ${segmentationId}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const annotationList = annotationUIDsMap.get(segmentIndex);
|
|
26
|
+
annotationList.forEach((annotationUID) => {
|
|
27
|
+
const annotation = getAnnotation(annotationUID);
|
|
28
|
+
if (!annotation) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const polyline = annotation.data.contour.polyline;
|
|
32
|
+
if (!polyline || polyline.length < 3) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const smoothedPolyline = interpolateSegmentPoints(polyline, 0, polyline.length - 1, options.knotsRatioPercentage);
|
|
36
|
+
annotation.data.contour.polyline = smoothedPolyline;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export interface ContourHoleDetectionResult {
|
|
3
|
+
contourIndex: number;
|
|
4
|
+
holeIndexes: number[];
|
|
5
|
+
}
|
|
6
|
+
export default function findContourHoles(polylines: Types.Point2[][]): ContourHoleDetectionResult[];
|
|
7
|
+
export { findContourHoles };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import getSignedArea from '../math/polyline/getSignedArea';
|
|
2
|
+
import containsPoints from '../math/polyline/containsPoints';
|
|
3
|
+
import isClosed from '../math/polyline/isClosed';
|
|
4
|
+
function isPolygonInsidePolygon(inner, outer) {
|
|
5
|
+
return containsPoints(outer, inner);
|
|
6
|
+
}
|
|
7
|
+
export default function findContourHoles(polylines) {
|
|
8
|
+
const results = [];
|
|
9
|
+
const closedPolylines = [];
|
|
10
|
+
polylines.forEach((polyline, index) => {
|
|
11
|
+
if (isClosed(polyline)) {
|
|
12
|
+
closedPolylines.push({ polyline, originalIndex: index });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
for (let i = 0; i < closedPolylines.length; i++) {
|
|
16
|
+
const outerContour = closedPolylines[i];
|
|
17
|
+
const outerArea = Math.abs(getSignedArea(outerContour.polyline));
|
|
18
|
+
const holeIndexes = [];
|
|
19
|
+
for (let j = 0; j < closedPolylines.length; j++) {
|
|
20
|
+
if (i === j) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const potentialHole = closedPolylines[j];
|
|
24
|
+
const holeArea = Math.abs(getSignedArea(potentialHole.polyline));
|
|
25
|
+
if (holeArea < outerArea &&
|
|
26
|
+
isPolygonInsidePolygon(potentialHole.polyline, outerContour.polyline)) {
|
|
27
|
+
holeIndexes.push(potentialHole.originalIndex);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (holeIndexes.length > 0) {
|
|
31
|
+
results.push({
|
|
32
|
+
contourIndex: outerContour.originalIndex,
|
|
33
|
+
holeIndexes: holeIndexes.sort((a, b) => a - b),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results.sort((a, b) => a.contourIndex - b.contourIndex);
|
|
38
|
+
}
|
|
39
|
+
export { findContourHoles };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import isClosed from '../math/polyline/isClosed';
|
|
2
|
+
import { getSignedArea } from '../math/polyline';
|
|
3
|
+
export default function findIslands(polylines, threshold) {
|
|
4
|
+
if (!polylines || polylines.length === 0) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
if (threshold <= 0) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const islandIndexes = [];
|
|
11
|
+
for (let i = 0; i < polylines.length; i++) {
|
|
12
|
+
const polyline = polylines[i];
|
|
13
|
+
if (!polyline || polyline.length < 3) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const isClosedPolyline = isClosed(polyline);
|
|
17
|
+
if (isClosedPolyline) {
|
|
18
|
+
const area = Math.abs(getSignedArea(polyline)) / 100;
|
|
19
|
+
if (area < threshold) {
|
|
20
|
+
islandIndexes.push(i);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return islandIndexes;
|
|
25
|
+
}
|
|
@@ -2,6 +2,7 @@ import areCoplanarContours from './areCoplanarContours';
|
|
|
2
2
|
import contourFinder from './contourFinder';
|
|
3
3
|
import { getDeduplicatedVTKPolyDataPoints } from './getDeduplicatedVTKPolyDataPoints';
|
|
4
4
|
import detectContourHoles from './detectContourHoles';
|
|
5
|
+
import findContourHoles from './findContourHoles';
|
|
5
6
|
import { generateContourSetsFromLabelmap } from './generateContourSetsFromLabelmap';
|
|
6
7
|
import AnnotationToPointData from './AnnotationToPointData';
|
|
7
8
|
import getContourHolesDataWorld from './getContourHolesDataWorld';
|
|
@@ -10,4 +11,5 @@ import updateContourPolyline from './updateContourPolyline';
|
|
|
10
11
|
import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
|
|
11
12
|
import findHandlePolylineIndex from './findHandlePolylineIndex';
|
|
12
13
|
import calculatePerimeter from './calculatePerimeter';
|
|
13
|
-
|
|
14
|
+
import findIslands from './findIslands';
|
|
15
|
+
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex, calculatePerimeter, findIslands, };
|
|
@@ -2,6 +2,7 @@ import areCoplanarContours from './areCoplanarContours';
|
|
|
2
2
|
import contourFinder from './contourFinder';
|
|
3
3
|
import { getDeduplicatedVTKPolyDataPoints } from './getDeduplicatedVTKPolyDataPoints';
|
|
4
4
|
import detectContourHoles from './detectContourHoles';
|
|
5
|
+
import findContourHoles from './findContourHoles';
|
|
5
6
|
import { generateContourSetsFromLabelmap } from './generateContourSetsFromLabelmap';
|
|
6
7
|
import AnnotationToPointData from './AnnotationToPointData';
|
|
7
8
|
import getContourHolesDataWorld from './getContourHolesDataWorld';
|
|
@@ -10,4 +11,5 @@ import updateContourPolyline from './updateContourPolyline';
|
|
|
10
11
|
import acceptAutogeneratedInterpolations from './interpolation/acceptAutogeneratedInterpolations';
|
|
11
12
|
import findHandlePolylineIndex from './findHandlePolylineIndex';
|
|
12
13
|
import calculatePerimeter from './calculatePerimeter';
|
|
13
|
-
|
|
14
|
+
import findIslands from './findIslands';
|
|
15
|
+
export { areCoplanarContours, contourFinder, getDeduplicatedVTKPolyDataPoints, detectContourHoles, findContourHoles, generateContourSetsFromLabelmap, AnnotationToPointData, getContourHolesDataWorld, getContourHolesDataCanvas, updateContourPolyline, acceptAutogeneratedInterpolations, findHandlePolylineIndex, calculatePerimeter, findIslands, };
|
|
@@ -40,14 +40,14 @@ export default function smoothAnnotation(annotation, options) {
|
|
|
40
40
|
return [planeP[0], planeP[1]];
|
|
41
41
|
});
|
|
42
42
|
let rotation = closed ? rotate(canvasPoints) : 0;
|
|
43
|
-
let interpolatedCanvasPoints = (interpolateSegmentPoints(canvasPoints, 0, canvasPoints.length, options?.knotsRatioPercentage || 30));
|
|
43
|
+
let interpolatedCanvasPoints = (interpolateSegmentPoints(canvasPoints, 0, canvasPoints.length - 1, options?.knotsRatioPercentage || 30));
|
|
44
44
|
if (interpolatedCanvasPoints === canvasPoints) {
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
47
|
rotate(interpolatedCanvasPoints, -rotation);
|
|
48
48
|
for (let i = 1; i < options?.loop; i++) {
|
|
49
49
|
rotation = closed ? rotate(interpolatedCanvasPoints) : 0;
|
|
50
|
-
interpolatedCanvasPoints = (interpolateSegmentPoints(interpolatedCanvasPoints, 0,
|
|
50
|
+
interpolatedCanvasPoints = (interpolateSegmentPoints(interpolatedCanvasPoints, 0, interpolatedCanvasPoints.length - 1, options?.knotsRatioPercentage || 30));
|
|
51
51
|
rotate(interpolatedCanvasPoints, -rotation);
|
|
52
52
|
}
|
|
53
53
|
const unRotate = mat4.invert(mat4.create(), rotateMat);
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.
|
|
1
|
+
export declare const version = "3.21.1";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.21.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.1",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"canvas": "^3.1.0"
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|
|
111
|
-
"@cornerstonejs/core": "^3.
|
|
111
|
+
"@cornerstonejs/core": "^3.21.1",
|
|
112
112
|
"@kitware/vtk.js": "32.12.1",
|
|
113
113
|
"@types/d3-array": "^3.0.4",
|
|
114
114
|
"@types/d3-interpolate": "^3.0.1",
|
|
@@ -127,5 +127,5 @@
|
|
|
127
127
|
"type": "individual",
|
|
128
128
|
"url": "https://ohif.org/donate"
|
|
129
129
|
},
|
|
130
|
-
"gitHead": "
|
|
130
|
+
"gitHead": "0f1548c39f1fd60af41bfa458f8402850a7fcb12"
|
|
131
131
|
}
|