@cornerstonejs/tools 3.5.3 → 3.6.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.
Files changed (36) hide show
  1. package/dist/esm/config.d.ts +7 -0
  2. package/dist/esm/enums/WorkerTypes.d.ts +2 -1
  3. package/dist/esm/enums/WorkerTypes.js +1 -0
  4. package/dist/esm/index.d.ts +2 -2
  5. package/dist/esm/index.js +2 -2
  6. package/dist/esm/tools/annotation/BidirectionalTool.d.ts +3 -0
  7. package/dist/esm/tools/annotation/BidirectionalTool.js +39 -1
  8. package/dist/esm/tools/index.d.ts +2 -1
  9. package/dist/esm/tools/index.js +2 -1
  10. package/dist/esm/tools/segmentation/SegmentBidirectionalTool.d.ts +27 -0
  11. package/dist/esm/tools/segmentation/SegmentBidirectionalTool.js +253 -0
  12. package/dist/esm/tools/segmentation/strategies/utils/getStrategyData.js +5 -10
  13. package/dist/esm/types/CalculatorTypes.d.ts +15 -3
  14. package/dist/esm/types/ToolSpecificAnnotationTypes.d.ts +34 -0
  15. package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +1 -0
  16. package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +68 -5
  17. package/dist/esm/utilities/registerComputeWorker.js +4 -1
  18. package/dist/esm/utilities/segmentation/VolumetricCalculator.js +1 -0
  19. package/dist/esm/utilities/segmentation/contourAndFindLargestBidirectional.js +7 -5
  20. package/dist/esm/utilities/segmentation/findLargestBidirectional.d.ts +5 -0
  21. package/dist/esm/utilities/segmentation/findLargestBidirectional.js +1 -1
  22. package/dist/esm/utilities/segmentation/getOrCreateSegmentationVolume.js +5 -1
  23. package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.d.ts +1 -0
  24. package/dist/esm/utilities/segmentation/getReferenceVolumeForSegmentationVolume.js +20 -0
  25. package/dist/esm/utilities/segmentation/getSegmentLargestBidirectional.d.ts +5 -0
  26. package/dist/esm/utilities/segmentation/getSegmentLargestBidirectional.js +54 -0
  27. package/dist/esm/utilities/segmentation/getStatistics.js +42 -107
  28. package/dist/esm/utilities/segmentation/index.d.ts +3 -1
  29. package/dist/esm/utilities/segmentation/index.js +3 -1
  30. package/dist/esm/utilities/segmentation/isLineInSegment.d.ts +13 -1
  31. package/dist/esm/utilities/segmentation/isLineInSegment.js +20 -12
  32. package/dist/esm/utilities/segmentation/segmentContourAction.js +1 -0
  33. package/dist/esm/utilities/segmentation/utilsForWorker.d.ts +38 -0
  34. package/dist/esm/utilities/segmentation/utilsForWorker.js +125 -0
  35. package/dist/esm/workers/computeWorker.js +284 -40
  36. package/package.json +3 -3
@@ -13,6 +13,9 @@ function createBasicStatsState(storePointData) {
13
13
  minLPS: null,
14
14
  runMean: [0],
15
15
  m2: [0],
16
+ m3: [0],
17
+ m4: [0],
18
+ allValues: [[]],
16
19
  pointsInShape: storePointData ? PointsManager.create3(1024) : null,
17
20
  };
18
21
  }
@@ -25,6 +28,9 @@ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
25
28
  state.sum.push(state.sum[0], state.sum[0]);
26
29
  state.runMean.push(0, 0);
27
30
  state.m2.push(state.m2[0], state.m2[0]);
31
+ state.m3.push(state.m3[0], state.m3[0]);
32
+ state.m4.push(state.m4[0], state.m4[0]);
33
+ state.allValues.push([], []);
28
34
  }
29
35
  if (state?.pointsInShape && pointLPS) {
30
36
  state.pointsInShape.push(pointLPS);
@@ -33,11 +39,19 @@ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
33
39
  state.count += 1;
34
40
  state.max.forEach((it, idx) => {
35
41
  const value = newArray[idx];
42
+ state.allValues[idx].push(value);
43
+ const n = state.count;
36
44
  const delta = value - state.runMean[idx];
45
+ const delta_n = delta / n;
46
+ const term1 = delta * delta_n * (n - 1);
37
47
  state.sum[idx] += value;
38
- state.runMean[idx] += delta / state.count;
39
- const delta2 = value - state.runMean[idx];
40
- state.m2[idx] += delta * delta2;
48
+ state.runMean[idx] += delta_n;
49
+ state.m4[idx] +=
50
+ term1 * delta_n * delta_n * (n * n - 3 * n + 3) +
51
+ 6 * delta_n * delta_n * state.m2[idx] -
52
+ 4 * delta_n * state.m3[idx];
53
+ state.m3[idx] += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2[idx];
54
+ state.m2[idx] += term1;
41
55
  if (value < state.min[idx]) {
42
56
  state.min[idx] = value;
43
57
  if (idx === 0) {
@@ -54,9 +68,37 @@ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
54
68
  }
55
69
  });
56
70
  }
71
+ function calculateMedian(values) {
72
+ if (values.length === 0) {
73
+ return 0;
74
+ }
75
+ const sorted = [...values].sort((a, b) => a - b);
76
+ const mid = Math.floor(sorted.length / 2);
77
+ if (sorted.length % 2 === 0) {
78
+ return (sorted[mid - 1] + sorted[mid]) / 2;
79
+ }
80
+ else {
81
+ return sorted[mid];
82
+ }
83
+ }
57
84
  function basicGetStatistics(state, unit) {
58
85
  const mean = state.sum.map((sum) => sum / state.count);
59
86
  const stdDev = state.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / state.count));
87
+ const skewness = state.m3.map((m3, idx) => {
88
+ const variance = state.m2[idx] / state.count;
89
+ if (variance === 0) {
90
+ return 0;
91
+ }
92
+ return m3 / (state.count * Math.pow(variance, 1.5));
93
+ });
94
+ const kurtosis = state.m4.map((m4, idx) => {
95
+ const variance = state.m2[idx] / state.count;
96
+ if (variance === 0) {
97
+ return 0;
98
+ }
99
+ return m4 / (state.count * variance * variance) - 3;
100
+ });
101
+ const median = state.allValues.map((values) => calculateMedian(values));
60
102
  const named = {
61
103
  max: {
62
104
  name: 'max',
@@ -88,14 +130,32 @@ function basicGetStatistics(state, unit) {
88
130
  },
89
131
  count: {
90
132
  name: 'count',
91
- label: 'Pixel Count',
133
+ label: 'Voxel Count',
92
134
  value: state.count,
93
135
  unit: null,
94
136
  },
137
+ median: {
138
+ name: 'median',
139
+ label: 'Median',
140
+ value: median.length === 1 ? median[0] : median,
141
+ unit,
142
+ },
143
+ skewness: {
144
+ name: 'skewness',
145
+ label: 'Skewness',
146
+ value: skewness.length === 1 ? skewness[0] : skewness,
147
+ unit: null,
148
+ },
149
+ kurtosis: {
150
+ name: 'kurtosis',
151
+ label: 'Kurtosis',
152
+ value: kurtosis.length === 1 ? kurtosis[0] : kurtosis,
153
+ unit: null,
154
+ },
95
155
  pointsInShape: state.pointsInShape,
96
156
  array: [],
97
157
  };
98
- named.array.push(named.max, named.mean, named.stdDev, named.stdDev, named.count);
158
+ named.array.push(named.max, named.mean, named.stdDev, named.median, named.skewness, named.kurtosis, named.count);
99
159
  const store = state.pointsInShape !== null;
100
160
  const freshState = createBasicStatsState(store);
101
161
  state.max = freshState.max;
@@ -108,6 +168,9 @@ function basicGetStatistics(state, unit) {
108
168
  state.minLPS = freshState.minLPS;
109
169
  state.runMean = freshState.runMean;
110
170
  state.m2 = freshState.m2;
171
+ state.m3 = freshState.m3;
172
+ state.m4 = freshState.m4;
173
+ state.allValues = freshState.allValues;
111
174
  state.pointsInShape = freshState.pointsInShape;
112
175
  return named;
113
176
  }
@@ -1,4 +1,5 @@
1
1
  import { getWebWorkerManager } from '@cornerstonejs/core';
2
+ import { getConfig } from '../config';
2
3
  let registered = false;
3
4
  export function registerComputeWorker() {
4
5
  if (registered) {
@@ -12,9 +13,11 @@ export function registerComputeWorker() {
12
13
  });
13
14
  };
14
15
  const workerManager = getWebWorkerManager();
16
+ const config = getConfig();
17
+ const computeWorkerConfig = config.computeWorker;
15
18
  const options = {
16
19
  maxWorkerInstances: 1,
17
- autoTerminateOnIdle: {
20
+ autoTerminateOnIdle: computeWorkerConfig?.autoTerminateOnIdle ?? {
18
21
  enabled: true,
19
22
  idleTimeThreshold: 2000,
20
23
  },
@@ -42,6 +42,7 @@ function volumetricGetStatistics(state, stats, options) {
42
42
  : stats.count.value * volumeScale,
43
43
  unit: volumeUnit,
44
44
  name: 'volume',
45
+ label: 'Volume',
45
46
  };
46
47
  stats.maxIJKs = state.maxIJKs.filter((entry) => entry.pointIJK !== undefined);
47
48
  stats.array.push(stats.volume);
@@ -1,7 +1,6 @@
1
1
  import { generateContourSetsFromLabelmap } from '../contours';
2
- import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
3
2
  import findLargestBidirectional from './findLargestBidirectional';
4
- const { Labelmap } = SegmentationRepresentations;
3
+ import getOrCreateSegmentationVolume from './getOrCreateSegmentationVolume';
5
4
  export default function contourAndFindLargestBidirectional(segmentation) {
6
5
  const contours = generateContourSetsFromLabelmap({
7
6
  segmentations: segmentation,
@@ -9,15 +8,18 @@ export default function contourAndFindLargestBidirectional(segmentation) {
9
8
  if (!contours?.length || !contours[0].sliceContours.length) {
10
9
  return;
11
10
  }
12
- const { representationData, segments = [
11
+ const { segments = [
13
12
  null,
14
13
  { label: 'Unspecified', color: null, containedSegmentIndices: null },
15
14
  ], } = segmentation;
16
- const { volumeId: segVolumeId } = representationData[Labelmap];
15
+ const vol = getOrCreateSegmentationVolume(segmentation.segmentationId);
16
+ if (!vol) {
17
+ return;
18
+ }
17
19
  const segmentIndex = segments.findIndex((it) => !!it);
18
20
  if (segmentIndex === -1) {
19
21
  return;
20
22
  }
21
23
  segments[segmentIndex].segmentIndex = segmentIndex;
22
- return findLargestBidirectional(contours[0], segVolumeId, segments[segmentIndex]);
24
+ return findLargestBidirectional(contours[0], vol.volumeId, segments[segmentIndex]);
23
25
  }
@@ -1 +1,6 @@
1
+ import type { BidirectionalData } from './createBidirectionalToolData';
1
2
  export default function findLargestBidirectional(contours: any, segVolumeId: string, segment: any): any;
3
+ export declare function createBidirectionalForSlice(sliceContour: any, isInSegment: any, currentMax?: {
4
+ maxMajor: number;
5
+ maxMinor: number;
6
+ }): BidirectionalData;
@@ -18,7 +18,7 @@ export default function findLargestBidirectional(contours, segVolumeId, segment)
18
18
  }
19
19
  return maxBidirectional;
20
20
  }
21
- function createBidirectionalForSlice(sliceContour, isInSegment, currentMax = { maxMajor: 0, maxMinor: 0 }) {
21
+ export function createBidirectionalForSlice(sliceContour, isInSegment, currentMax = { maxMajor: 0, maxMinor: 0 }) {
22
22
  const { points } = sliceContour.polyData;
23
23
  const { maxMinor: currentMaxMinor, maxMajor: currentMaxMajor } = currentMax;
24
24
  let maxMajor = currentMaxMajor * currentMaxMajor;
@@ -1,4 +1,4 @@
1
- import { cache, volumeLoader } from '@cornerstonejs/core';
1
+ import { cache, volumeLoader, utilities } from '@cornerstonejs/core';
2
2
  import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
3
3
  function getOrCreateSegmentationVolume(segmentationId) {
4
4
  const { representationData } = getSegmentation(segmentationId);
@@ -15,6 +15,10 @@ function getOrCreateSegmentationVolume(segmentationId) {
15
15
  if (!labelmapImageIds || labelmapImageIds.length === 1) {
16
16
  return;
17
17
  }
18
+ const isValidVolume = utilities.isValidVolume(labelmapImageIds);
19
+ if (!isValidVolume) {
20
+ return;
21
+ }
18
22
  segVolume = volumeLoader.createAndCacheVolumeFromImagesSync(volumeId, labelmapImageIds);
19
23
  return segVolume;
20
24
  }
@@ -0,0 +1 @@
1
+ export declare function getReferenceVolumeForSegmentationVolume(segmentationVolumeId: string): any;
@@ -0,0 +1,20 @@
1
+ import { cache } from '@cornerstonejs/core';
2
+ export function getReferenceVolumeForSegmentationVolume(segmentationVolumeId) {
3
+ const segmentationVolume = cache.getVolume(segmentationVolumeId);
4
+ if (!segmentationVolume) {
5
+ return null;
6
+ }
7
+ const referencedVolumeId = segmentationVolume.referencedVolumeId;
8
+ let imageVolume;
9
+ if (referencedVolumeId) {
10
+ imageVolume = cache.getVolume(referencedVolumeId);
11
+ }
12
+ else {
13
+ const imageIds = segmentationVolume.imageIds;
14
+ const image = cache.getImage(imageIds[0]);
15
+ const referencedImageId = image.referencedImageId;
16
+ const volumeInfo = cache.getVolumeContainingImageId(referencedImageId);
17
+ imageVolume = volumeInfo?.volume;
18
+ }
19
+ return imageVolume;
20
+ }
@@ -0,0 +1,5 @@
1
+ export declare function getSegmentLargestBidirectional({ segmentationId, segmentIndices, mode, }: {
2
+ segmentationId: any;
3
+ segmentIndices: any;
4
+ mode?: string;
5
+ }): Promise<any>;
@@ -0,0 +1,54 @@
1
+ import { getWebWorkerManager } from '@cornerstonejs/core';
2
+ import { WorkerTypes } from '../../enums';
3
+ import { registerComputeWorker } from '../registerComputeWorker';
4
+ import { triggerWorkerProgress, getSegmentationDataForWorker, prepareVolumeStrategyDataForWorker, prepareStackDataForWorker, } from './utilsForWorker';
5
+ export async function getSegmentLargestBidirectional({ segmentationId, segmentIndices, mode = 'individual', }) {
6
+ registerComputeWorker();
7
+ triggerWorkerProgress(WorkerTypes.COMPUTE_LARGEST_BIDIRECTIONAL, 0);
8
+ const segData = getSegmentationDataForWorker(segmentationId, segmentIndices);
9
+ if (!segData) {
10
+ return;
11
+ }
12
+ const { operationData, segImageIds, reconstructableVolume, indices } = segData;
13
+ const bidirectionalData = reconstructableVolume
14
+ ? await calculateVolumeBidirectional({
15
+ operationData,
16
+ indices,
17
+ mode,
18
+ })
19
+ : await calculateStackBidirectional({
20
+ segImageIds,
21
+ indices,
22
+ mode,
23
+ });
24
+ triggerWorkerProgress(WorkerTypes.COMPUTE_LARGEST_BIDIRECTIONAL, 100);
25
+ return bidirectionalData;
26
+ }
27
+ async function calculateVolumeBidirectional({ operationData, indices, mode }) {
28
+ const strategyData = prepareVolumeStrategyDataForWorker(operationData);
29
+ const { segmentationVoxelManager, segmentationImageData } = strategyData;
30
+ const segmentationScalarData = segmentationVoxelManager.getCompleteScalarDataArray();
31
+ const segmentationInfo = {
32
+ scalarData: segmentationScalarData,
33
+ dimensions: segmentationImageData.getDimensions(),
34
+ spacing: segmentationImageData.getSpacing(),
35
+ origin: segmentationImageData.getOrigin(),
36
+ direction: segmentationImageData.getDirection(),
37
+ };
38
+ const bidirectionalData = await getWebWorkerManager().executeTask('compute', 'getSegmentLargestBidirectionalInternal', {
39
+ segmentationInfo,
40
+ indices,
41
+ mode,
42
+ });
43
+ return bidirectionalData;
44
+ }
45
+ async function calculateStackBidirectional({ segImageIds, indices, mode }) {
46
+ const { segmentationInfo } = prepareStackDataForWorker(segImageIds);
47
+ const bidirectionalData = await getWebWorkerManager().executeTask('compute', 'getSegmentLargestBidirectionalInternal', {
48
+ segmentationInfo,
49
+ indices,
50
+ mode,
51
+ isStack: true,
52
+ });
53
+ return bidirectionalData;
54
+ }
@@ -1,53 +1,18 @@
1
- import { cache, utilities, getWebWorkerManager, eventTarget, Enums, triggerEvent, metaData, } from '@cornerstonejs/core';
2
- import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
3
- import VolumetricCalculator from './VolumetricCalculator';
4
- import { getStrategyData } from '../../tools/segmentation/strategies/utils/getStrategyData';
1
+ import { utilities, getWebWorkerManager } from '@cornerstonejs/core';
2
+ import { triggerWorkerProgress, getSegmentationDataForWorker, prepareVolumeStrategyDataForWorker, prepareStackDataForWorker, getImageReferenceInfo, } from './utilsForWorker';
5
3
  import { getPixelValueUnitsImageId } from '../getPixelValueUnits';
6
- import ensureSegmentationVolume from '../../tools/segmentation/strategies/compositions/ensureSegmentationVolume';
7
- import ensureImageVolume from '../../tools/segmentation/strategies/compositions/ensureImageVolume';
8
- import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
9
- import { registerComputeWorker } from '../registerComputeWorker';
4
+ import VolumetricCalculator from './VolumetricCalculator';
10
5
  import { WorkerTypes } from '../../enums';
6
+ import { registerComputeWorker } from '../registerComputeWorker';
11
7
  const radiusForVol1 = Math.pow((3 * 1000) / (4 * Math.PI), 1 / 3);
12
- const workerManager = getWebWorkerManager();
13
- const triggerWorkerProgress = (eventTarget, progress) => {
14
- triggerEvent(eventTarget, Enums.Events.WEB_WORKER_PROGRESS, {
15
- progress,
16
- type: WorkerTypes.COMPUTE_STATISTICS,
17
- });
18
- };
19
8
  async function getStatistics({ segmentationId, segmentIndices, mode = 'collective', }) {
20
9
  registerComputeWorker();
21
- triggerWorkerProgress(eventTarget, 0);
22
- const segmentation = getSegmentation(segmentationId);
23
- const { representationData } = segmentation;
24
- const { Labelmap } = representationData;
25
- if (!Labelmap) {
26
- console.debug('No labelmap found for segmentation', segmentationId);
10
+ triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 0);
11
+ const segData = getSegmentationDataForWorker(segmentationId, segmentIndices);
12
+ if (!segData) {
27
13
  return;
28
14
  }
29
- const segVolumeId = Labelmap.volumeId;
30
- const segImageIds = Labelmap.imageIds;
31
- const operationData = {
32
- segmentationId,
33
- volumeId: segVolumeId,
34
- imageIds: segImageIds,
35
- };
36
- let reconstructableVolume = false;
37
- if (segImageIds) {
38
- const refImageIds = segImageIds.map((imageId) => {
39
- const image = cache.getImage(imageId);
40
- return image.referencedImageId;
41
- });
42
- reconstructableVolume = utilities.isValidVolume(refImageIds);
43
- }
44
- let indices = segmentIndices;
45
- if (!indices) {
46
- indices = [getActiveSegmentIndex(segmentationId)];
47
- }
48
- else if (!Array.isArray(indices)) {
49
- indices = [indices, 255];
50
- }
15
+ const { operationData, segVolumeId, segImageIds, reconstructableVolume, indices, } = segData;
51
16
  const { refImageId, modalityUnitOptions } = getImageReferenceInfo(segVolumeId, segImageIds);
52
17
  const unit = getPixelValueUnitsImageId(refImageId, modalityUnitOptions);
53
18
  const stats = reconstructableVolume
@@ -66,13 +31,7 @@ async function getStatistics({ segmentationId, segmentIndices, mode = 'collectiv
66
31
  return stats;
67
32
  }
68
33
  async function calculateVolumeStatistics({ operationData, indices, unit, mode, }) {
69
- const strategyData = getStrategyData({
70
- operationData,
71
- strategy: {
72
- ensureSegmentationVolumeFor3DManipulation: ensureSegmentationVolume.ensureSegmentationVolumeFor3DManipulation,
73
- ensureImageVolumeFor3DManipulation: ensureImageVolume.ensureImageVolumeFor3DManipulation,
74
- },
75
- });
34
+ const strategyData = prepareVolumeStrategyDataForWorker(operationData);
76
35
  const { segmentationVoxelManager, imageVoxelManager, segmentationImageData, imageData, } = strategyData;
77
36
  const spacing = segmentationImageData.getSpacing();
78
37
  const { boundsIJK: boundsOrig } = segmentationVoxelManager;
@@ -80,26 +39,27 @@ async function calculateVolumeStatistics({ operationData, indices, unit, mode, }
80
39
  return VolumetricCalculator.getStatistics({ spacing });
81
40
  }
82
41
  const segmentationScalarData = segmentationVoxelManager.getCompleteScalarDataArray();
83
- const imageScalarData = imageVoxelManager.getCompleteScalarDataArray();
84
42
  const segmentationInfo = {
85
43
  scalarData: segmentationScalarData,
86
44
  dimensions: segmentationImageData.getDimensions(),
87
45
  spacing: segmentationImageData.getSpacing(),
88
46
  origin: segmentationImageData.getOrigin(),
47
+ direction: segmentationImageData.getDirection(),
89
48
  };
90
49
  const imageInfo = {
91
- scalarData: imageScalarData,
50
+ scalarData: imageVoxelManager.getCompleteScalarDataArray(),
92
51
  dimensions: imageData.getDimensions(),
93
52
  spacing: imageData.getSpacing(),
94
53
  origin: imageData.getOrigin(),
54
+ direction: imageData.getDirection(),
95
55
  };
96
- const stats = await workerManager.executeTask('compute', 'calculateSegmentsStatisticsVolume', {
56
+ const stats = await getWebWorkerManager().executeTask('compute', 'calculateSegmentsStatisticsVolume', {
97
57
  segmentationInfo,
98
58
  imageInfo,
99
59
  indices,
100
60
  mode,
101
61
  });
102
- triggerWorkerProgress(eventTarget, 100);
62
+ triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 100);
103
63
  if (mode === 'collective') {
104
64
  return processSegmentationStatistics({
105
65
  stats,
@@ -123,6 +83,18 @@ async function calculateVolumeStatistics({ operationData, indices, unit, mode, }
123
83
  return finalStats;
124
84
  }
125
85
  }
86
+ const updateStatsArray = (stats, newStat) => {
87
+ if (!stats.array) {
88
+ return;
89
+ }
90
+ const existingIndex = stats.array.findIndex((stat) => stat.name === newStat.name);
91
+ if (existingIndex !== -1) {
92
+ stats.array[existingIndex] = newStat;
93
+ }
94
+ else {
95
+ stats.array.push(newStat);
96
+ }
97
+ };
126
98
  const processSegmentationStatistics = ({ stats, unit, spacing, segmentationImageData, imageVoxelManager, }) => {
127
99
  stats.mean.unit = unit;
128
100
  stats.max.unit = unit;
@@ -144,42 +116,32 @@ const processSegmentationStatistics = ({ stats, unit, spacing, segmentationImage
144
116
  value: mean.value,
145
117
  unit,
146
118
  };
119
+ updateStatsArray(stats, stats.peakValue);
147
120
  }
148
121
  }
122
+ if (stats.volume && stats.mean) {
123
+ const mtv = stats.volume.value;
124
+ const suvMean = stats.mean.value;
125
+ stats.lesionGlycolysis = {
126
+ name: 'lesionGlycolysis',
127
+ label: 'Lesion Glycolysis',
128
+ value: mtv * suvMean,
129
+ unit: `${stats.volume.unit}·${unit}`,
130
+ };
131
+ updateStatsArray(stats, stats.lesionGlycolysis);
132
+ }
149
133
  return stats;
150
134
  };
151
135
  async function calculateStackStatistics({ segImageIds, indices, unit, mode }) {
152
- triggerWorkerProgress(eventTarget, 0);
153
- const segmentationInfo = [];
154
- const imageInfo = [];
155
- for (const segImageId of segImageIds) {
156
- const segImage = cache.getImage(segImageId);
157
- const segPixelData = segImage.getPixelData();
158
- const segVoxelManager = segImage.voxelManager;
159
- const segSpacing = [segImage.rowPixelSpacing, segImage.columnPixelSpacing];
160
- const refImageId = segImage.referencedImageId;
161
- const refImage = cache.getImage(refImageId);
162
- const refPixelData = refImage.getPixelData();
163
- const refVoxelManager = refImage.voxelManager;
164
- const refSpacing = [refImage.rowPixelSpacing, refImage.columnPixelSpacing];
165
- segmentationInfo.push({
166
- scalarData: segPixelData,
167
- dimensions: segVoxelManager.dimensions,
168
- spacing: segSpacing,
169
- });
170
- imageInfo.push({
171
- scalarData: refPixelData,
172
- dimensions: refVoxelManager.dimensions,
173
- spacing: refSpacing,
174
- });
175
- }
176
- const stats = await workerManager.executeTask('compute', 'calculateSegmentsStatisticsStack', {
136
+ triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 0);
137
+ const { segmentationInfo, imageInfo } = prepareStackDataForWorker(segImageIds);
138
+ const stats = await getWebWorkerManager().executeTask('compute', 'calculateSegmentsStatisticsStack', {
177
139
  segmentationInfo,
178
140
  imageInfo,
179
141
  indices,
180
142
  mode,
181
143
  });
182
- triggerWorkerProgress(eventTarget, 100);
144
+ triggerWorkerProgress(WorkerTypes.COMPUTE_STATISTICS, 100);
183
145
  const spacing = segmentationInfo[0].spacing;
184
146
  const segmentationImageData = segmentationInfo[0];
185
147
  const imageVoxelManager = imageInfo[0].voxelManager;
@@ -237,31 +199,4 @@ function getSphereStats(testMax, radiusIJK, segData, imageVoxels, spacing) {
237
199
  });
238
200
  return VolumetricCalculator.getStatistics({ spacing });
239
201
  }
240
- function getImageReferenceInfo(segVolumeId, segImageIds) {
241
- let refImageId;
242
- let modalityUnitOptions;
243
- if (segVolumeId) {
244
- const segmentationVolume = cache.getVolume(segVolumeId);
245
- const referencedVolumeId = segmentationVolume.referencedVolumeId;
246
- const volume = cache.getVolume(referencedVolumeId);
247
- if (volume?.imageIds?.length > 0) {
248
- refImageId = volume.imageIds[0];
249
- }
250
- modalityUnitOptions = {
251
- isPreScaled: Object.keys(volume.scaling || {}).length > 0,
252
- isSuvScaled: Boolean(volume.scaling?.PT),
253
- };
254
- }
255
- else if (segImageIds?.length) {
256
- const segImage = cache.getImage(segImageIds[0]);
257
- refImageId = segImage.referencedImageId;
258
- const refImage = cache.getImage(refImageId);
259
- const scalingModule = metaData.get('scalingModule', refImageId);
260
- modalityUnitOptions = {
261
- isPreScaled: Boolean(refImage.preScale?.scaled),
262
- isSuvScaled: typeof scalingModule?.preScale?.scaled === 'number',
263
- };
264
- }
265
- return { refImageId, modalityUnitOptions };
266
- }
267
202
  export default getStatistics;
@@ -26,4 +26,6 @@ import getStatistics from './getStatistics';
26
26
  import * as validateLabelmap from './validateLabelmap';
27
27
  import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
28
28
  import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
29
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
29
+ import { getReferenceVolumeForSegmentationVolume } from './getReferenceVolumeForSegmentationVolume';
30
+ import { getSegmentLargestBidirectional } from './getSegmentLargestBidirectional';
31
+ export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, getReferenceVolumeForSegmentationVolume, getSegmentLargestBidirectional, };
@@ -26,4 +26,6 @@ import getStatistics from './getStatistics';
26
26
  import * as validateLabelmap from './validateLabelmap';
27
27
  import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
28
28
  import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
29
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, };
29
+ import { getReferenceVolumeForSegmentationVolume } from './getReferenceVolumeForSegmentationVolume';
30
+ import { getSegmentLargestBidirectional } from './getSegmentLargestBidirectional';
31
+ export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, SegmentStatsCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, computeVolumeLabelmapFromStack, getReferenceVolumeForSegmentationVolume, getSegmentLargestBidirectional, };
@@ -1,9 +1,21 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import { vec3 } from 'gl-matrix';
3
+ import type vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
3
4
  export default function isLineInSegment(point1: Types.Point3, point2: Types.Point3, isInSegment: any): boolean;
5
+ declare function createIsInSegmentMetadata({ dimensions, imageData, voxelManager, segmentIndex, containedSegmentIndices, }: {
6
+ dimensions: number[];
7
+ imageData: vtkImageData;
8
+ voxelManager: Types.IVoxelManager<number>;
9
+ segmentIndex: number;
10
+ containedSegmentIndices?: Set<number>;
11
+ }): {
12
+ testCenter: (point1: any, point2: any) => boolean;
13
+ toIJK: (point: any) => vec3;
14
+ testIJK: (ijk: any) => boolean;
15
+ };
4
16
  declare function createIsInSegment(segVolumeId: string, segmentIndex: number, containedSegmentIndices?: Set<number>): {
5
17
  testCenter: (point1: any, point2: any) => boolean;
6
18
  toIJK: (point: any) => vec3;
7
19
  testIJK: (ijk: any) => boolean;
8
20
  };
9
- export { createIsInSegment, isLineInSegment };
21
+ export { createIsInSegment, createIsInSegmentMetadata, isLineInSegment };
@@ -19,25 +19,19 @@ export default function isLineInSegment(point1, point2, isInSegment) {
19
19
  }
20
20
  return true;
21
21
  }
22
- function createIsInSegment(segVolumeId, segmentIndex, containedSegmentIndices) {
23
- const vol = cache.getVolume(segVolumeId);
24
- if (!vol) {
25
- console.warn(`No volume found for ${segVolumeId}`);
26
- return;
27
- }
28
- const voxelManager = vol.voxelManager;
29
- const width = vol.dimensions[0];
30
- const pixelsPerSlice = width * vol.dimensions[1];
22
+ function createIsInSegmentMetadata({ dimensions, imageData, voxelManager, segmentIndex, containedSegmentIndices, }) {
23
+ const width = dimensions[0];
24
+ const pixelsPerSlice = width * dimensions[1];
31
25
  return {
32
26
  testCenter: (point1, point2) => {
33
27
  const point = vec3.add(vec3.create(), point1, point2).map((it) => it / 2);
34
- const ijk = vol.imageData.worldToIndex(point).map(Math.round);
28
+ const ijk = imageData.worldToIndex(point).map(Math.round);
35
29
  const [i, j, k] = ijk;
36
30
  const index = i + j * width + k * pixelsPerSlice;
37
31
  const value = voxelManager.getAtIndex(index);
38
32
  return value === segmentIndex || containedSegmentIndices?.has(value);
39
33
  },
40
- toIJK: (point) => vol.imageData.worldToIndex(point),
34
+ toIJK: (point) => imageData.worldToIndex(point),
41
35
  testIJK: (ijk) => {
42
36
  const [i, j, k] = ijk;
43
37
  const index = Math.round(i) + Math.round(j) * width + Math.round(k) * pixelsPerSlice;
@@ -46,4 +40,18 @@ function createIsInSegment(segVolumeId, segmentIndex, containedSegmentIndices) {
46
40
  },
47
41
  };
48
42
  }
49
- export { createIsInSegment, isLineInSegment };
43
+ function createIsInSegment(segVolumeId, segmentIndex, containedSegmentIndices) {
44
+ const vol = cache.getVolume(segVolumeId);
45
+ if (!vol) {
46
+ console.warn(`No volume found for ${segVolumeId}`);
47
+ return;
48
+ }
49
+ return createIsInSegmentMetadata({
50
+ dimensions: vol.dimensions,
51
+ imageData: vol.imageData,
52
+ voxelManager: vol.voxelManager,
53
+ segmentIndex,
54
+ containedSegmentIndices,
55
+ });
56
+ }
57
+ export { createIsInSegment, createIsInSegmentMetadata, isLineInSegment };
@@ -6,6 +6,7 @@ import BidirectionalTool from '../../tools/annotation/BidirectionalTool';
6
6
  import { getSegmentations } from '../../stateManagement/segmentation/getSegmentations';
7
7
  import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
8
8
  export default function segmentContourAction(element, configuration) {
9
+ console.warn('Deprecation Alert: There is a new getSegmentLargestBidirectional function that handles volume, stack and individual segment cases properly. This function is deprecated and will be removed in a future version.');
9
10
  const { data: configurationData } = configuration;
10
11
  const enabledElement = getEnabledElement(element);
11
12
  const segment = (configurationData.getSegment || defaultGetSegment)(enabledElement, configurationData);