@cornerstonejs/tools 3.10.31 → 3.11.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.
@@ -3,12 +3,23 @@ import type { Types } from '@cornerstonejs/core';
3
3
  import type { LabelmapSegmentationData } from './types/LabelmapTypes';
4
4
  import type { SurfaceSegmentationData } from './types/SurfaceTypes';
5
5
  import type SegmentationRepresentations from './enums/SegmentationRepresentations';
6
+ export type SurfacesInfo = {
7
+ id: string;
8
+ points: number[];
9
+ polys: number[];
10
+ segmentIndex: number;
11
+ };
6
12
  export type PolySegConversionOptions = {
7
13
  segmentIndices?: number[];
8
14
  segmentationId?: string;
9
15
  viewport?: Types.IStackViewport | Types.IVolumeViewport;
10
16
  };
11
17
  type ComputeRepresentationFn<T> = (segmentationId: string, options: PolySegConversionOptions) => Promise<T>;
18
+ export type SurfaceClipResult = {
19
+ points: number[];
20
+ lines: number[];
21
+ numberOfCells: number;
22
+ };
12
23
  type PolySegAddOn = {
13
24
  canComputeRequestedRepresentation: (segmentationId: string, representationType: SegmentationRepresentations) => boolean;
14
25
  init: () => void;
@@ -16,6 +27,9 @@ type PolySegAddOn = {
16
27
  computeLabelmapData: ComputeRepresentationFn<LabelmapSegmentationData>;
17
28
  computeSurfaceData: ComputeRepresentationFn<SurfaceSegmentationData>;
18
29
  updateSurfaceData: ComputeRepresentationFn<SurfaceSegmentationData>;
30
+ clipAndCacheSurfacesForViewport: (surfacesInfo: SurfacesInfo[], viewport: Types.IVolumeViewport) => Promise<Map<number, Map<string, SurfaceClipResult>>>;
31
+ extractContourData: (polyDataCache: Map<number, Map<string, SurfaceClipResult>>) => Map<number, SurfaceClipResult[]>;
32
+ createAndAddContourSegmentationsFromClippedSurfaces: (rawContourData: Map<number, SurfaceClipResult[]>, viewport: Types.IStackViewport | Types.IVolumeViewport, segmentationId: string) => Map<number, Set<string>>;
19
33
  };
20
34
  type AddOns = {
21
35
  polySeg: PolySegAddOn;
@@ -1,3 +1,5 @@
1
+ import { eventTarget, triggerEvent } from '@cornerstonejs/core';
2
+ import Events from './enums/Events';
1
3
  let config = {};
2
4
  export function getConfig() {
3
5
  return config;
@@ -1,11 +1,14 @@
1
- import { getEnabledElementByViewportId } from '@cornerstonejs/core';
1
+ import { cache, getEnabledElementByViewportId, Enums, utilities, } from '@cornerstonejs/core';
2
2
  import Representations from '../../../enums/SegmentationRepresentations';
3
3
  import { handleContourSegmentation } from './contourHandler/handleContourSegmentation';
4
4
  import { getSegmentation } from '../../../stateManagement/segmentation/getSegmentation';
5
5
  import removeContourFromElement from './removeContourFromElement';
6
6
  import { getPolySeg } from '../../../config';
7
7
  import { computeAndAddRepresentation } from '../../../utilities/segmentation/computeAndAddRepresentation';
8
- let polySegConversionInProgress = false;
8
+ import { getUniqueSegmentIndices } from '../../../utilities/segmentation/getUniqueSegmentIndices';
9
+ import { getAnnotation } from '../../../stateManagement/annotation/annotationState';
10
+ import { vec3 } from 'gl-matrix';
11
+ const polySegConversionInProgressForViewportId = new Map();
9
12
  const processedViewportSegmentations = new Map();
10
13
  function removeRepresentation(viewportId, segmentationId, renderImmediate = false) {
11
14
  const enabledElement = getEnabledElementByViewportId(viewportId);
@@ -26,13 +29,13 @@ async function render(viewport, contourRepresentation) {
26
29
  return;
27
30
  }
28
31
  let contourData = segmentation.representationData[Representations.Contour];
32
+ const polySeg = getPolySeg();
29
33
  if (!contourData &&
30
34
  getPolySeg()?.canComputeRequestedRepresentation(segmentationId, Representations.Contour) &&
31
- !polySegConversionInProgress) {
32
- polySegConversionInProgress = true;
33
- const polySeg = getPolySeg();
35
+ !polySegConversionInProgressForViewportId.get(viewport.id)) {
36
+ polySegConversionInProgressForViewportId.set(viewport.id, true);
34
37
  contourData = await computeAndAddRepresentation(segmentationId, Representations.Contour, () => polySeg.computeContourData(segmentationId, { viewport }), () => undefined);
35
- polySegConversionInProgress = false;
38
+ polySegConversionInProgressForViewportId.set(viewport.id, false);
36
39
  }
37
40
  else if (!contourData && !getPolySeg()) {
38
41
  console.debug(`No contour data found for segmentationId ${segmentationId} and PolySeg add-on is not configured. Unable to convert from other representations to contour. Please register PolySeg using cornerstoneTools.init({ addons: { polySeg } }) to enable automatic conversion.`);
@@ -43,8 +46,89 @@ async function render(viewport, contourRepresentation) {
43
46
  if (!contourData.geometryIds?.length) {
44
47
  return;
45
48
  }
49
+ let hasContourDataButNotMatchingViewport = false;
50
+ const viewportNormal = viewport.getCamera().viewPlaneNormal;
51
+ if (contourData.annotationUIDsMap) {
52
+ hasContourDataButNotMatchingViewport = !_checkContourNormalsMatchViewport(contourData.annotationUIDsMap, viewportNormal);
53
+ }
54
+ if (contourData.geometryIds.length > 0) {
55
+ hasContourDataButNotMatchingViewport = !_checkContourGeometryMatchViewport(contourData.geometryIds, viewportNormal);
56
+ }
57
+ const viewportProcessed = processedViewportSegmentations.get(viewport.id) || new Set();
58
+ if (hasContourDataButNotMatchingViewport &&
59
+ !polySegConversionInProgressForViewportId.get(viewport.id) &&
60
+ !viewportProcessed.has(segmentationId) &&
61
+ viewport.viewportStatus === Enums.ViewportStatus.RENDERED) {
62
+ polySegConversionInProgressForViewportId.set(viewport.id, true);
63
+ const segmentIndices = getUniqueSegmentIndices(segmentationId);
64
+ const surfacesInfo = await polySeg.computeSurfaceData(segmentationId, {
65
+ segmentIndices,
66
+ viewport,
67
+ });
68
+ const geometryIds = surfacesInfo.geometryIds;
69
+ const pointsAndPolys = [];
70
+ for (const geometryId of geometryIds.values()) {
71
+ const geometry = cache.getGeometry(geometryId);
72
+ const data = geometry.data;
73
+ pointsAndPolys.push({
74
+ points: data.points,
75
+ polys: data.polys,
76
+ segmentIndex: data.segmentIndex,
77
+ id: data.segmentIndex,
78
+ });
79
+ }
80
+ const polyDataCache = await polySeg.clipAndCacheSurfacesForViewport(pointsAndPolys, viewport);
81
+ const rawResults = polySeg.extractContourData(polyDataCache);
82
+ const annotationUIDsMap = polySeg.createAndAddContourSegmentationsFromClippedSurfaces(rawResults, viewport, segmentationId);
83
+ contourData.annotationUIDsMap = new Map([
84
+ ...contourData.annotationUIDsMap,
85
+ ...annotationUIDsMap,
86
+ ]);
87
+ viewportProcessed.add(segmentationId);
88
+ processedViewportSegmentations.set(viewport.id, viewportProcessed);
89
+ polySegConversionInProgressForViewportId.set(viewport.id, false);
90
+ }
46
91
  handleContourSegmentation(viewport, contourData.geometryIds, contourData.annotationUIDsMap, contourRepresentation);
47
92
  }
93
+ function _checkContourGeometryMatchViewport(geometryIds, viewportNormal) {
94
+ const geometry = cache.getGeometry(geometryIds[0]);
95
+ if (!geometry) {
96
+ return false;
97
+ }
98
+ const geometryData = geometry.data;
99
+ const contours = geometryData.contours;
100
+ const points = contours[0].points;
101
+ const point1 = points[0];
102
+ const point2 = points[1];
103
+ const point3 = points[2];
104
+ let normal = vec3.cross(vec3.create(), vec3.sub(vec3.create(), point2, point1), vec3.sub(vec3.create(), point3, point1));
105
+ normal = vec3.normalize(vec3.create(), normal);
106
+ const dotProduct = vec3.dot(normal, viewportNormal);
107
+ return Math.abs(dotProduct) > 0.9;
108
+ }
109
+ function _checkContourNormalsMatchViewport(annotationUIDsMap, viewportNormal) {
110
+ const annotationUIDs = Array.from(annotationUIDsMap.values())
111
+ .flat()
112
+ .map((uidSet) => Array.from(uidSet))
113
+ .flat();
114
+ const randomAnnotationUIDs = utilities.getRandomSampleFromArray(annotationUIDs, 3);
115
+ for (const annotationUID of randomAnnotationUIDs) {
116
+ const annotation = getAnnotation(annotationUID);
117
+ if (annotation?.metadata) {
118
+ if (!annotation.metadata.viewPlaneNormal) {
119
+ continue;
120
+ }
121
+ const annotationNormal = annotation.metadata.viewPlaneNormal;
122
+ const dotProduct = Math.abs(viewportNormal[0] * annotationNormal[0] +
123
+ viewportNormal[1] * annotationNormal[1] +
124
+ viewportNormal[2] * annotationNormal[2]);
125
+ if (Math.abs(dotProduct - 1) > 0.01) {
126
+ return false;
127
+ }
128
+ }
129
+ }
130
+ return true;
131
+ }
48
132
  export default {
49
133
  render,
50
134
  removeRepresentation,
@@ -56,6 +56,9 @@ async function calculateVolumeStatistics({ operationData, indices, unit, mode, }
56
56
  origin: imageData.getOrigin(),
57
57
  direction: imageData.getDirection(),
58
58
  };
59
+ if (!imageInfo.scalarData?.length) {
60
+ return;
61
+ }
59
62
  const stats = await getWebWorkerManager().executeTask('compute', 'calculateSegmentsStatisticsVolume', {
60
63
  segmentationInfo,
61
64
  imageInfo,
@@ -121,7 +121,7 @@ export const getImageReferenceInfo = (segVolumeId, segImageIds) => {
121
121
  const refImage = cache.getImage(refImageId);
122
122
  const scalingModule = metaData.get('scalingModule', refImageId);
123
123
  const modalityUnitOptions = {
124
- isPreScaled: Boolean(refImage.preScale?.scaled),
124
+ isPreScaled: Boolean(refImage?.preScale?.scaled),
125
125
  isSuvScaled: typeof scalingModule?.suvbw === 'number',
126
126
  };
127
127
  return { refImageId, modalityUnitOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "3.10.31",
3
+ "version": "3.11.1",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "types": "./dist/esm/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -103,7 +103,7 @@
103
103
  "canvas": "^3.1.0"
104
104
  },
105
105
  "peerDependencies": {
106
- "@cornerstonejs/core": "^3.10.31",
106
+ "@cornerstonejs/core": "^3.11.1",
107
107
  "@kitware/vtk.js": "32.12.1",
108
108
  "@types/d3-array": "^3.0.4",
109
109
  "@types/d3-interpolate": "^3.0.1",
@@ -122,5 +122,5 @@
122
122
  "type": "individual",
123
123
  "url": "https://ohif.org/donate"
124
124
  },
125
- "gitHead": "f7d656d8b175c3ff00b145cdd5ceedf910afeda1"
125
+ "gitHead": "9e71e94884bd1c8734911b98ac67a5676fcaaecb"
126
126
  }