@cornerstonejs/tools 3.1.1 → 3.2.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.
@@ -3,6 +3,7 @@ declare enum Events {
3
3
  TOOLGROUP_VIEWPORT_ADDED = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_ADDED",
4
4
  TOOLGROUP_VIEWPORT_REMOVED = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_REMOVED",
5
5
  TOOL_MODE_CHANGED = "CORNERSTONE_TOOLS_TOOL_MODE_CHANGED",
6
+ CROSSHAIR_TOOL_CENTER_CHANGED = "CORNERSTONE_TOOLS_CROSSHAIR_TOOL_CENTER_CHANGED",
6
7
  ANNOTATION_ADDED = "CORNERSTONE_TOOLS_ANNOTATION_ADDED",
7
8
  ANNOTATION_COMPLETED = "CORNERSTONE_TOOLS_ANNOTATION_COMPLETED",
8
9
  ANNOTATION_MODIFIED = "CORNERSTONE_TOOLS_ANNOTATION_MODIFIED",
@@ -4,6 +4,7 @@ var Events;
4
4
  Events["TOOLGROUP_VIEWPORT_ADDED"] = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_ADDED";
5
5
  Events["TOOLGROUP_VIEWPORT_REMOVED"] = "CORNERSTONE_TOOLS_TOOLGROUP_VIEWPORT_REMOVED";
6
6
  Events["TOOL_MODE_CHANGED"] = "CORNERSTONE_TOOLS_TOOL_MODE_CHANGED";
7
+ Events["CROSSHAIR_TOOL_CENTER_CHANGED"] = "CORNERSTONE_TOOLS_CROSSHAIR_TOOL_CENTER_CHANGED";
7
8
  Events["ANNOTATION_ADDED"] = "CORNERSTONE_TOOLS_ANNOTATION_ADDED";
8
9
  Events["ANNOTATION_COMPLETED"] = "CORNERSTONE_TOOLS_ANNOTATION_COMPLETED";
9
10
  Events["ANNOTATION_MODIFIED"] = "CORNERSTONE_TOOLS_ANNOTATION_MODIFIED";
@@ -33,6 +33,7 @@ declare class CrosshairsTool extends AnnotationTool {
33
33
  resetCrosshairs: () => void;
34
34
  computeToolCenter: () => void;
35
35
  _computeToolCenter: (viewportsInfo: any) => void;
36
+ setToolCenter(toolCenter: Types.Point3, suppressEvents?: boolean): void;
36
37
  addNewAnnotation: (evt: EventTypes.InteractionEventType) => CrosshairsAnnotation;
37
38
  cancel: () => void;
38
39
  getHandleNearImagePoint(element: HTMLDivElement, annotation: Annotation, canvasCoords: Types.Point2, proximity: number): ToolHandle | undefined;
@@ -2,7 +2,7 @@ import { vec2, vec3 } from 'gl-matrix';
2
2
  import vtkMath from '@kitware/vtk.js/Common/Core/Math';
3
3
  import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
4
4
  import { AnnotationTool } from './base';
5
- import { getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, } from '@cornerstonejs/core';
5
+ import { getEnabledElementByIds, getEnabledElement, utilities as csUtils, Enums, CONSTANTS, triggerEvent, eventTarget, } from '@cornerstonejs/core';
6
6
  import { getToolGroup, getToolGroupForViewport, } from '../store/ToolGroupManager';
7
7
  import { addAnnotation, getAnnotations, removeAnnotation, } from '../stateManagement/annotation/annotationState';
8
8
  import { drawCircle as drawCircleSvg, drawHandles as drawHandlesSvg, drawLine as drawLineSvg, } from '../drawingSvg';
@@ -162,8 +162,8 @@ class CrosshairsTool extends AnnotationTool {
162
162
  const firstPlane = csUtils.planar.planeEquation(normal1, point1);
163
163
  const secondPlane = csUtils.planar.planeEquation(normal2, point2);
164
164
  const thirdPlane = csUtils.planar.planeEquation(normal3, point3);
165
- this.toolCenter = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane);
166
- triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
165
+ const toolCenter = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane);
166
+ this.setToolCenter(toolCenter);
167
167
  };
168
168
  this.addNewAnnotation = (evt) => {
169
169
  const eventDetail = evt.detail;
@@ -258,6 +258,10 @@ class CrosshairsTool extends AnnotationTool {
258
258
  this.toolCenter[0] += deltaCameraPosition[0];
259
259
  this.toolCenter[1] += deltaCameraPosition[1];
260
260
  this.toolCenter[2] += deltaCameraPosition[2];
261
+ triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
262
+ toolGroupId: this.toolGroupId,
263
+ toolCenter: this.toolCenter,
264
+ });
261
265
  }
262
266
  }
263
267
  if (this.configuration.autoPan?.enabled) {
@@ -1171,6 +1175,17 @@ class CrosshairsTool extends AnnotationTool {
1171
1175
  }
1172
1176
  });
1173
1177
  }
1178
+ setToolCenter(toolCenter, suppressEvents = false) {
1179
+ this.toolCenter = toolCenter;
1180
+ const viewportsInfo = this._getViewportsInfo();
1181
+ triggerAnnotationRenderForViewportIds(viewportsInfo.map(({ viewportId }) => viewportId));
1182
+ if (!suppressEvents) {
1183
+ triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
1184
+ toolGroupId: this.toolGroupId,
1185
+ toolCenter: this.toolCenter,
1186
+ });
1187
+ }
1188
+ }
1174
1189
  getHandleNearImagePoint(element, annotation, canvasCoords, proximity) {
1175
1190
  const enabledElement = getEnabledElement(element);
1176
1191
  const { viewport } = enabledElement;
@@ -2,6 +2,7 @@ declare const getCalibratedLengthUnitsAndScale: (image: any, handles: any) => {
2
2
  unit: string;
3
3
  areaUnit: string;
4
4
  scale: number;
5
+ volumeUnit: string;
5
6
  };
6
7
  declare const getCalibratedProbeUnitsAndValue: (image: any, handles: any) => {
7
8
  units: string[];
@@ -1,6 +1,7 @@
1
1
  import { Enums, utilities } from '@cornerstonejs/core';
2
2
  const { CalibrationTypes } = Enums;
3
3
  const PIXEL_UNITS = 'px';
4
+ const VOXEL_UNITS = 'voxels';
4
5
  const SUPPORTED_REGION_DATA_TYPES = [
5
6
  1,
6
7
  2,
@@ -33,15 +34,21 @@ const SQUARE = '\xb2';
33
34
  const getCalibratedLengthUnitsAndScale = (image, handles) => {
34
35
  const { calibration, hasPixelSpacing } = image;
35
36
  let unit = hasPixelSpacing ? 'mm' : PIXEL_UNITS;
37
+ const volumeUnit = hasPixelSpacing ? 'mm\xb3' : VOXEL_UNITS;
36
38
  let areaUnit = unit + SQUARE;
37
39
  let scale = 1;
38
40
  let calibrationType = '';
39
41
  if (!calibration ||
40
42
  (!calibration.type && !calibration.sequenceOfUltrasoundRegions)) {
41
- return { unit, areaUnit, scale };
43
+ return { unit, areaUnit, scale, volumeUnit };
42
44
  }
43
45
  if (calibration.type === CalibrationTypes.UNCALIBRATED) {
44
- return { unit: PIXEL_UNITS, areaUnit: PIXEL_UNITS + SQUARE, scale };
46
+ return {
47
+ unit: PIXEL_UNITS,
48
+ areaUnit: PIXEL_UNITS + SQUARE,
49
+ scale,
50
+ volumeUnit: VOXEL_UNITS,
51
+ };
45
52
  }
46
53
  if (calibration.sequenceOfUltrasoundRegions) {
47
54
  let imageIndex1, imageIndex2;
@@ -62,7 +69,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
62
69
  imageIndex2[1] >= region.regionLocationMinY0 &&
63
70
  imageIndex2[1] <= region.regionLocationMaxY1);
64
71
  if (!regions?.length) {
65
- return { unit, areaUnit, scale };
72
+ return { unit, areaUnit, scale, volumeUnit };
66
73
  }
67
74
  regions = regions.filter((region) => SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
68
75
  SUPPORTED_LENGTH_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`));
@@ -71,6 +78,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
71
78
  unit: PIXEL_UNITS,
72
79
  areaUnit: PIXEL_UNITS + SQUARE,
73
80
  scale,
81
+ volumeUnit: VOXEL_UNITS,
74
82
  };
75
83
  }
76
84
  const region = regions[0];
@@ -88,6 +96,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
88
96
  unit: PIXEL_UNITS,
89
97
  areaUnit: PIXEL_UNITS + SQUARE,
90
98
  scale,
99
+ volumeUnit: VOXEL_UNITS,
91
100
  };
92
101
  }
93
102
  }
@@ -107,6 +116,7 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
107
116
  unit: unit + (calibrationType ? ` ${calibrationType}` : ''),
108
117
  areaUnit: areaUnit + (calibrationType ? ` ${calibrationType}` : ''),
109
118
  scale,
119
+ volumeUnit: volumeUnit + (calibrationType ? ` ${calibrationType}` : ''),
110
120
  };
111
121
  };
112
122
  const getCalibratedProbeUnitsAndValue = (image, handles) => {
@@ -1,26 +1,35 @@
1
1
  import type { NamedStatistics } from '../../../types';
2
- import Calculator from './Calculator';
3
- export default class BasicStatsCalculator extends Calculator {
4
- private static max;
5
- private static min;
6
- private static sum;
7
- private static count;
8
- private static maxIJK;
9
- private static maxLPS;
10
- private static minIJK;
11
- private static minLPS;
12
- private static runMean;
13
- private static m2;
14
- private static pointsInShape;
2
+ import { Calculator, InstanceCalculator } from './Calculator';
3
+ import type { Types } from '@cornerstonejs/core';
4
+ export declare class BasicStatsCalculator extends Calculator {
5
+ private static state;
15
6
  static statsInit(options: {
16
7
  storePointData: boolean;
17
8
  }): void;
18
9
  static statsCallback: ({ value: newValue, pointLPS, pointIJK, }: {
19
- value: any;
20
- pointLPS?: any;
21
- pointIJK?: any;
10
+ value: number | Types.RGB;
11
+ pointLPS?: Types.Point3 | null;
12
+ pointIJK?: Types.Point3 | null;
22
13
  }) => void;
23
14
  static getStatistics: (options?: {
24
15
  unit: string;
25
16
  }) => NamedStatistics;
26
17
  }
18
+ export declare class InstanceBasicStatsCalculator extends InstanceCalculator {
19
+ private state;
20
+ constructor(options: {
21
+ storePointData: boolean;
22
+ });
23
+ statsInit(options: {
24
+ storePointData: boolean;
25
+ }): void;
26
+ statsCallback(data: {
27
+ value: number | Types.RGB;
28
+ pointLPS?: Types.Point3 | null;
29
+ pointIJK?: Types.Point3 | null;
30
+ }): void;
31
+ getStatistics(options?: {
32
+ unit: string;
33
+ spacing?: number[] | number;
34
+ }): NamedStatistics;
35
+ }
@@ -1,119 +1,144 @@
1
1
  import { utilities } from '@cornerstonejs/core';
2
- import Calculator from './Calculator';
2
+ import { Calculator, InstanceCalculator } from './Calculator';
3
3
  const { PointsManager } = utilities;
4
- export default class BasicStatsCalculator extends Calculator {
5
- static { this.max = [-Infinity]; }
6
- static { this.min = [Infinity]; }
7
- static { this.sum = [0]; }
8
- static { this.count = 0; }
9
- static { this.maxIJK = null; }
10
- static { this.maxLPS = null; }
11
- static { this.minIJK = null; }
12
- static { this.minLPS = null; }
13
- static { this.runMean = [0]; }
14
- static { this.m2 = [0]; }
15
- static { this.pointsInShape = PointsManager.create3(1024); }
4
+ function createBasicStatsState(storePointData) {
5
+ return {
6
+ max: [-Infinity],
7
+ min: [Infinity],
8
+ sum: [0],
9
+ count: 0,
10
+ maxIJK: null,
11
+ maxLPS: null,
12
+ minIJK: null,
13
+ minLPS: null,
14
+ runMean: [0],
15
+ m2: [0],
16
+ pointsInShape: storePointData ? PointsManager.create3(1024) : null,
17
+ };
18
+ }
19
+ function basicStatsCallback(state, newValue, pointLPS = null, pointIJK = null) {
20
+ if (Array.isArray(newValue) &&
21
+ newValue.length > 1 &&
22
+ state.max.length === 1) {
23
+ state.max.push(state.max[0], state.max[0]);
24
+ state.min.push(state.min[0], state.min[0]);
25
+ state.sum.push(state.sum[0], state.sum[0]);
26
+ state.runMean.push(0, 0);
27
+ state.m2.push(state.m2[0], state.m2[0]);
28
+ }
29
+ if (state?.pointsInShape && pointLPS) {
30
+ state.pointsInShape.push(pointLPS);
31
+ }
32
+ const newArray = Array.isArray(newValue) ? newValue : [newValue];
33
+ state.count += 1;
34
+ state.max.forEach((it, idx) => {
35
+ const value = newArray[idx];
36
+ const delta = value - state.runMean[idx];
37
+ state.sum[idx] += value;
38
+ state.runMean[idx] += delta / state.count;
39
+ const delta2 = value - state.runMean[idx];
40
+ state.m2[idx] += delta * delta2;
41
+ state.min[idx] = Math.min(state.min[idx], value);
42
+ if (value < state.min[idx]) {
43
+ state.min[idx] = value;
44
+ if (idx === 0) {
45
+ state.minIJK = pointIJK;
46
+ state.minLPS = pointLPS;
47
+ }
48
+ }
49
+ if (value > state.max[idx]) {
50
+ state.max[idx] = value;
51
+ if (idx === 0) {
52
+ state.maxIJK = pointIJK;
53
+ state.maxLPS = pointLPS;
54
+ }
55
+ }
56
+ });
57
+ }
58
+ function basicGetStatistics(state, unit) {
59
+ const mean = state.sum.map((sum) => sum / state.count);
60
+ const stdDev = state.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / state.count));
61
+ const named = {
62
+ max: {
63
+ name: 'max',
64
+ label: 'Max Pixel',
65
+ value: state.max.length === 1 ? state.max[0] : state.max,
66
+ unit,
67
+ pointIJK: state.maxIJK,
68
+ pointLPS: state.maxLPS,
69
+ },
70
+ min: {
71
+ name: 'min',
72
+ label: 'Min Pixel',
73
+ value: state.min.length === 1 ? state.min[0] : state.min,
74
+ unit,
75
+ pointIJK: state.minIJK,
76
+ pointLPS: state.minLPS,
77
+ },
78
+ mean: {
79
+ name: 'mean',
80
+ label: 'Mean Pixel',
81
+ value: mean.length === 1 ? mean[0] : mean,
82
+ unit,
83
+ },
84
+ stdDev: {
85
+ name: 'stdDev',
86
+ label: 'Standard Deviation',
87
+ value: stdDev.length === 1 ? stdDev[0] : stdDev,
88
+ unit,
89
+ },
90
+ count: {
91
+ name: 'count',
92
+ label: 'Pixel Count',
93
+ value: state.count,
94
+ unit: null,
95
+ },
96
+ pointsInShape: state.pointsInShape,
97
+ array: [],
98
+ };
99
+ named.array.push(named.max, named.mean, named.stdDev, named.stdDev, named.count);
100
+ const store = state.pointsInShape !== null;
101
+ const freshState = createBasicStatsState(store);
102
+ state.max = freshState.max;
103
+ state.min = freshState.min;
104
+ state.sum = freshState.sum;
105
+ state.count = freshState.count;
106
+ state.maxIJK = freshState.maxIJK;
107
+ state.maxLPS = freshState.maxLPS;
108
+ state.minIJK = freshState.minIJK;
109
+ state.minLPS = freshState.minLPS;
110
+ state.runMean = freshState.runMean;
111
+ state.m2 = freshState.m2;
112
+ state.pointsInShape = freshState.pointsInShape;
113
+ return named;
114
+ }
115
+ export class BasicStatsCalculator extends Calculator {
116
+ static { this.state = createBasicStatsState(true); }
16
117
  static statsInit(options) {
17
118
  if (!options.storePointData) {
18
- BasicStatsCalculator.pointsInShape = null;
119
+ this.state.pointsInShape = null;
19
120
  }
121
+ this.state = createBasicStatsState(options.storePointData);
20
122
  }
21
123
  static { this.statsCallback = ({ value: newValue, pointLPS = null, pointIJK = null, }) => {
22
- if (Array.isArray(newValue) &&
23
- newValue.length > 1 &&
24
- this.max.length === 1) {
25
- this.max.push(this.max[0], this.max[0]);
26
- this.min.push(this.min[0], this.min[0]);
27
- this.sum.push(this.sum[0], this.sum[0]);
28
- this.runMean.push(0, 0);
29
- this.m2.push(this.m2[0], this.m2[0]);
30
- }
31
- if (this.pointsInShape && pointLPS) {
32
- this.pointsInShape?.push(pointLPS);
33
- }
34
- const newArray = Array.isArray(newValue) ? newValue : [newValue];
35
- this.count += 1;
36
- this.max.map((it, idx) => {
37
- const value = newArray[idx];
38
- const delta = value - this.runMean[idx];
39
- this.sum[idx] += value;
40
- this.runMean[idx] += delta / this.count;
41
- const delta2 = value - this.runMean[idx];
42
- this.m2[idx] += delta * delta2;
43
- this.min[idx] = Math.min(this.min[idx], value);
44
- if (value < this.min[idx]) {
45
- this.min[idx] = value;
46
- if (idx === 0) {
47
- this.minIJK = pointIJK;
48
- this.minLPS = pointLPS;
49
- }
50
- }
51
- if (value > this.max[idx]) {
52
- this.max[idx] = value;
53
- if (idx === 0) {
54
- this.maxIJK = pointIJK;
55
- this.maxLPS = pointLPS;
56
- }
57
- }
58
- });
124
+ basicStatsCallback(this.state, newValue, pointLPS, pointIJK);
59
125
  }; }
60
126
  static { this.getStatistics = (options) => {
61
- const mean = this.sum.map((sum) => sum / this.count);
62
- const stdDev = this.m2.map((squaredDiffSum) => Math.sqrt(squaredDiffSum / this.count));
63
- const unit = options?.unit || null;
64
- const named = {
65
- max: {
66
- name: 'max',
67
- label: 'Max Pixel',
68
- value: singleArrayAsNumber(this.max),
69
- unit,
70
- pointIJK: this.maxIJK,
71
- pointLPS: this.maxLPS,
72
- },
73
- min: {
74
- name: 'min',
75
- label: 'Min Pixel',
76
- value: singleArrayAsNumber(this.min),
77
- unit,
78
- pointIJK: this.minIJK,
79
- pointLPS: this.minLPS,
80
- },
81
- mean: {
82
- name: 'mean',
83
- label: 'Mean Pixel',
84
- value: singleArrayAsNumber(mean),
85
- unit,
86
- },
87
- stdDev: {
88
- name: 'stdDev',
89
- label: 'Standard Deviation',
90
- value: singleArrayAsNumber(stdDev),
91
- unit,
92
- },
93
- count: {
94
- name: 'count',
95
- label: 'Pixel Count',
96
- value: this.count,
97
- unit: null,
98
- },
99
- pointsInShape: this.pointsInShape,
100
- array: [],
101
- };
102
- named.array.push(named.max, named.mean, named.stdDev, named.stdDev, named.count);
103
- this.max = [-Infinity];
104
- this.min = [Infinity];
105
- this.sum = [0];
106
- this.m2 = [0];
107
- this.runMean = [0];
108
- this.count = 0;
109
- this.maxIJK = null;
110
- this.maxLPS = null;
111
- this.minIJK = null;
112
- this.minLPS = null;
113
- this.pointsInShape = PointsManager.create3(1024);
114
- return named;
127
+ return basicGetStatistics(this.state, options?.unit);
115
128
  }; }
116
129
  }
117
- function singleArrayAsNumber(val) {
118
- return val.length === 1 ? val[0] : val;
130
+ export class InstanceBasicStatsCalculator extends InstanceCalculator {
131
+ constructor(options) {
132
+ super(options);
133
+ this.state = createBasicStatsState(options.storePointData);
134
+ }
135
+ statsInit(options) {
136
+ this.state = createBasicStatsState(options.storePointData);
137
+ }
138
+ statsCallback(data) {
139
+ basicStatsCallback(this.state, data.value, data.pointLPS, data.pointIJK);
140
+ }
141
+ getStatistics(options) {
142
+ return basicGetStatistics(this.state, options?.unit);
143
+ }
119
144
  }
@@ -1,8 +1,11 @@
1
1
  import type { NamedStatistics } from '../../../types';
2
- declare abstract class Calculator {
3
- static run: ({ value }: {
4
- value: any;
5
- }) => void;
2
+ export declare abstract class Calculator {
6
3
  static getStatistics: () => NamedStatistics;
7
4
  }
8
- export default Calculator;
5
+ export declare class InstanceCalculator {
6
+ private storePointData;
7
+ constructor(options: {
8
+ storePointData: boolean;
9
+ });
10
+ getStatistics(): void;
11
+ }
@@ -1,3 +1,10 @@
1
- class Calculator {
1
+ export class Calculator {
2
+ }
3
+ export class InstanceCalculator {
4
+ constructor(options) {
5
+ this.storePointData = options.storePointData;
6
+ }
7
+ getStatistics() {
8
+ console.debug('InstanceCalculator getStatistics called');
9
+ }
2
10
  }
3
- export default Calculator;
@@ -1,3 +1,3 @@
1
- import BasicStatsCalculator from './BasicStatsCalculator';
2
- import Calculator from './Calculator';
3
- export { BasicStatsCalculator, Calculator };
1
+ import { BasicStatsCalculator, InstanceBasicStatsCalculator } from './BasicStatsCalculator';
2
+ import { Calculator, InstanceCalculator } from './Calculator';
3
+ export { BasicStatsCalculator, InstanceBasicStatsCalculator, Calculator, InstanceCalculator, };
@@ -1,3 +1,3 @@
1
- import BasicStatsCalculator from './BasicStatsCalculator';
2
- import Calculator from './Calculator';
3
- export { BasicStatsCalculator, Calculator };
1
+ import { BasicStatsCalculator, InstanceBasicStatsCalculator, } from './BasicStatsCalculator';
2
+ import { Calculator, InstanceCalculator } from './Calculator';
3
+ export { BasicStatsCalculator, InstanceBasicStatsCalculator, Calculator, InstanceCalculator, };
@@ -0,0 +1,26 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ import type { NamedStatistics } from '../../types';
3
+ export default class SegmentStatsCalculator {
4
+ private static calculators;
5
+ private static indices;
6
+ private static mode;
7
+ static statsInit(options: {
8
+ storePointData: boolean;
9
+ indices: number[];
10
+ mode: 'collective' | 'individual';
11
+ }): void;
12
+ static statsCallback(data: {
13
+ value: number | Types.RGB;
14
+ pointLPS?: Types.Point3;
15
+ pointIJK?: Types.Point3;
16
+ segmentIndex?: number;
17
+ }): void;
18
+ static getStatistics(options?: {
19
+ spacing?: number[] | number;
20
+ unit?: string;
21
+ calibration?: unknown;
22
+ hasPixelSpacing?: boolean;
23
+ }): NamedStatistics | {
24
+ [segmentIndex: number]: NamedStatistics;
25
+ };
26
+ }
@@ -0,0 +1,44 @@
1
+ import { InstanceVolumetricCalculator } from './VolumetricCalculator';
2
+ export default class SegmentStatsCalculator {
3
+ static { this.calculators = new Map(); }
4
+ static { this.indices = []; }
5
+ static { this.mode = 'collective'; }
6
+ static statsInit(options) {
7
+ const { storePointData, indices, mode } = options;
8
+ this.mode = mode;
9
+ this.indices = indices;
10
+ this.calculators.clear();
11
+ if (this.mode === 'individual') {
12
+ indices.forEach((index) => {
13
+ this.calculators.set(index, new InstanceVolumetricCalculator({ storePointData }));
14
+ });
15
+ }
16
+ else {
17
+ this.calculators.set(indices, new InstanceVolumetricCalculator({ storePointData }));
18
+ }
19
+ }
20
+ static statsCallback(data) {
21
+ const { segmentIndex, ...statsData } = data;
22
+ if (!segmentIndex) {
23
+ throw new Error('Segment index is required for stats calculation');
24
+ }
25
+ const calculator = this.mode === 'individual'
26
+ ? this.calculators.get(segmentIndex)
27
+ : this.calculators.get(this.indices);
28
+ if (!calculator) {
29
+ throw new Error(`No calculator found for segment ${segmentIndex}`);
30
+ }
31
+ calculator.statsCallback(statsData);
32
+ }
33
+ static getStatistics(options) {
34
+ if (this.mode === 'individual') {
35
+ const result = {};
36
+ this.calculators.forEach((calculator, segmentIndex) => {
37
+ result[segmentIndex] = calculator.getStatistics(options);
38
+ });
39
+ return result;
40
+ }
41
+ const calculator = this.calculators.get(this.indices);
42
+ return calculator.getStatistics(options);
43
+ }
44
+ }
@@ -1,15 +1,41 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import type { NamedStatistics } from '../../types';
3
- import { BasicStatsCalculator } from '../math/basic';
4
- export default class VolumetricCalculator extends BasicStatsCalculator {
5
- private static maxIJKs;
3
+ import { BasicStatsCalculator, InstanceBasicStatsCalculator } from '../math/basic/BasicStatsCalculator';
4
+ export declare class VolumetricCalculator extends BasicStatsCalculator {
5
+ private static volumetricState;
6
+ static statsInit(options: {
7
+ storePointData: boolean;
8
+ }): void;
9
+ static statsCallback(data: {
10
+ value: number | Types.RGB;
11
+ pointLPS?: Types.Point3;
12
+ pointIJK?: Types.Point3;
13
+ }): void;
6
14
  static getStatistics(options: {
7
- spacing?: number;
15
+ spacing?: number[] | number;
8
16
  unit?: string;
17
+ calibration?: unknown;
18
+ hasPixelSpacing?: boolean;
9
19
  }): NamedStatistics;
10
- static statsCallback(data: {
20
+ }
21
+ export declare class InstanceVolumetricCalculator extends InstanceBasicStatsCalculator {
22
+ private volumetricState;
23
+ constructor(options: {
24
+ storePointData: boolean;
25
+ });
26
+ statsInit(options: {
27
+ storePointData: boolean;
28
+ }): void;
29
+ statsCallback(data: {
11
30
  value: number | Types.RGB;
12
31
  pointLPS?: Types.Point3;
13
32
  pointIJK?: Types.Point3;
14
33
  }): void;
34
+ getStatistics(options?: {
35
+ spacing?: number[] | number;
36
+ unit?: string;
37
+ calibration?: unknown;
38
+ hasPixelSpacing?: boolean;
39
+ }): NamedStatistics;
15
40
  }
41
+ export default VolumetricCalculator;
@@ -1,48 +1,96 @@
1
- import { BasicStatsCalculator } from '../math/basic';
1
+ import { BasicStatsCalculator, InstanceBasicStatsCalculator, } from '../math/basic/BasicStatsCalculator';
2
+ import { getCalibratedLengthUnitsAndScale } from '../getCalibratedUnits';
2
3
  const TEST_MAX_LOCATIONS = 10;
3
- export default class VolumetricCalculator extends BasicStatsCalculator {
4
- static { this.maxIJKs = []; }
5
- static getStatistics(options) {
6
- const { spacing } = options;
7
- const stats = BasicStatsCalculator.getStatistics();
8
- const volumeUnit = spacing ? 'mm\xb3' : 'voxels\xb3';
9
- const volumeScale = spacing
10
- ? spacing[0] * spacing[1] * spacing[2] * 1000
11
- : 1;
12
- stats.volume = {
13
- value: Array.isArray(stats.count.value)
14
- ? stats.count.value.map((v) => v * volumeScale)
15
- : stats.count.value * volumeScale,
16
- unit: volumeUnit,
17
- name: 'volume',
18
- };
19
- stats.maxIJKs = this.maxIJKs;
20
- stats.array.push(stats.volume);
21
- this.maxIJKs = [];
22
- return stats;
4
+ function createVolumetricState() {
5
+ return {
6
+ maxIJKs: [],
7
+ };
8
+ }
9
+ function volumetricStatsCallback(state, data) {
10
+ const { value } = data;
11
+ const { maxIJKs } = state;
12
+ const length = maxIJKs.length;
13
+ if (typeof value !== 'number' ||
14
+ (length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
15
+ return;
23
16
  }
24
- static statsCallback(data) {
25
- BasicStatsCalculator.statsCallback(data);
26
- const { value } = data;
27
- const { maxIJKs } = this;
28
- const { length } = maxIJKs;
29
- if (typeof value !== 'number' ||
30
- (length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
31
- return;
32
- }
33
- if (!length || value >= maxIJKs[length - 1].value) {
34
- maxIJKs.push(data);
35
- }
36
- else {
37
- for (let i = 0; i < length; i++) {
38
- if (value <= maxIJKs[i].value) {
39
- maxIJKs.splice(i, 0, data);
40
- break;
41
- }
17
+ if (!length || value >= maxIJKs[length - 1].value) {
18
+ maxIJKs.push(data);
19
+ }
20
+ else {
21
+ for (let i = 0; i < length; i++) {
22
+ if (value <= maxIJKs[i].value) {
23
+ maxIJKs.splice(i, 0, data);
24
+ break;
42
25
  }
43
26
  }
44
- if (length >= TEST_MAX_LOCATIONS) {
45
- maxIJKs.splice(0, 1);
46
- }
27
+ }
28
+ if (length >= TEST_MAX_LOCATIONS) {
29
+ maxIJKs.splice(0, 1);
30
+ }
31
+ }
32
+ function volumetricGetStatistics(state, stats, options) {
33
+ const { spacing, calibration } = options;
34
+ const { volumeUnit } = getCalibratedLengthUnitsAndScale({
35
+ calibration,
36
+ hasPixelSpacing: true,
37
+ }, []);
38
+ const volumeScale = spacing ? spacing[0] * spacing[1] * spacing[2] * 1000 : 1;
39
+ stats.volume = {
40
+ value: Array.isArray(stats.count.value)
41
+ ? stats.count.value.map((v) => v * volumeScale)
42
+ : stats.count.value * volumeScale,
43
+ unit: volumeUnit,
44
+ name: 'volume',
45
+ };
46
+ stats.maxIJKs = state.maxIJKs.filter((entry) => entry.pointIJK !== undefined);
47
+ stats.array.push(stats.volume);
48
+ state.maxIJKs = [];
49
+ return stats;
50
+ }
51
+ export class VolumetricCalculator extends BasicStatsCalculator {
52
+ static { this.volumetricState = createVolumetricState(); }
53
+ static statsInit(options) {
54
+ super.statsInit(options);
55
+ this.volumetricState = createVolumetricState();
56
+ }
57
+ static statsCallback(data) {
58
+ super.statsCallback(data);
59
+ volumetricStatsCallback(this.volumetricState, data);
60
+ }
61
+ static getStatistics(options) {
62
+ const optionsWithUnit = {
63
+ ...options,
64
+ unit: options?.unit || 'none',
65
+ calibration: options?.calibration,
66
+ hasPixelSpacing: options?.hasPixelSpacing,
67
+ };
68
+ const stats = super.getStatistics(optionsWithUnit);
69
+ return volumetricGetStatistics(this.volumetricState, stats, optionsWithUnit);
70
+ }
71
+ }
72
+ export class InstanceVolumetricCalculator extends InstanceBasicStatsCalculator {
73
+ constructor(options) {
74
+ super(options);
75
+ this.volumetricState = createVolumetricState();
76
+ }
77
+ statsInit(options) {
78
+ super.statsInit(options);
79
+ this.volumetricState = createVolumetricState();
80
+ }
81
+ statsCallback(data) {
82
+ super.statsCallback(data);
83
+ volumetricStatsCallback(this.volumetricState, data);
84
+ }
85
+ getStatistics(options) {
86
+ const optionsWithUnit = {
87
+ ...options,
88
+ unit: options?.unit || 'none',
89
+ calibration: options?.calibration,
90
+ hasPixelSpacing: options?.hasPixelSpacing,
91
+ };
92
+ const stats = super.getStatistics(optionsWithUnit);
93
+ return volumetricGetStatistics(this.volumetricState, stats, optionsWithUnit);
47
94
  }
48
95
  }
96
+ export default VolumetricCalculator;
@@ -1,5 +1,9 @@
1
- declare function getStatistics({ segmentationId, segmentIndices, }: {
1
+ import type { NamedStatistics } from '../../types';
2
+ declare function getStatistics({ segmentationId, segmentIndices, mode, }: {
2
3
  segmentationId: string;
3
4
  segmentIndices: number[] | number;
4
- }): Promise<any>;
5
+ mode?: 'collective' | 'individual';
6
+ }): Promise<NamedStatistics | {
7
+ [segmentIndex: number]: NamedStatistics;
8
+ }>;
5
9
  export default getStatistics;
@@ -16,7 +16,7 @@ const triggerWorkerProgress = (eventTarget, progress) => {
16
16
  type: WorkerTypes.COMPUTE_STATISTICS,
17
17
  });
18
18
  };
19
- async function getStatistics({ segmentationId, segmentIndices, }) {
19
+ async function getStatistics({ segmentationId, segmentIndices, mode = 'collective', }) {
20
20
  registerComputeWorker();
21
21
  triggerWorkerProgress(eventTarget, 0);
22
22
  const segmentation = getSegmentation(segmentationId);
@@ -51,11 +51,21 @@ async function getStatistics({ segmentationId, segmentIndices, }) {
51
51
  const { refImageId, modalityUnitOptions } = getImageReferenceInfo(segVolumeId, segImageIds);
52
52
  const unit = getPixelValueUnitsImageId(refImageId, modalityUnitOptions);
53
53
  const stats = reconstructableVolume
54
- ? await calculateVolumeStatistics(operationData, indices, unit)
55
- : await calculateStackStatistics(segImageIds, indices, unit);
54
+ ? await calculateVolumeStatistics({
55
+ operationData,
56
+ indices,
57
+ unit,
58
+ mode,
59
+ })
60
+ : await calculateStackStatistics({
61
+ segImageIds,
62
+ indices,
63
+ unit,
64
+ mode,
65
+ });
56
66
  return stats;
57
67
  }
58
- async function calculateVolumeStatistics(operationData, indices, unit) {
68
+ async function calculateVolumeStatistics({ operationData, indices, unit, mode, }) {
59
69
  const strategyData = getStrategyData({
60
70
  operationData,
61
71
  strategy: {
@@ -87,8 +97,33 @@ async function calculateVolumeStatistics(operationData, indices, unit) {
87
97
  segmentationInfo,
88
98
  imageInfo,
89
99
  indices,
100
+ mode,
90
101
  });
91
102
  triggerWorkerProgress(eventTarget, 100);
103
+ if (mode === 'collective') {
104
+ return processSegmentationStatistics({
105
+ stats,
106
+ unit,
107
+ spacing,
108
+ segmentationImageData,
109
+ imageVoxelManager,
110
+ });
111
+ }
112
+ else {
113
+ const finalStats = {};
114
+ Object.entries(stats).forEach(([segmentIndex, stat]) => {
115
+ finalStats[segmentIndex] = processSegmentationStatistics({
116
+ stats: stat,
117
+ unit,
118
+ spacing,
119
+ segmentationImageData,
120
+ imageVoxelManager,
121
+ });
122
+ });
123
+ return finalStats;
124
+ }
125
+ }
126
+ const processSegmentationStatistics = ({ stats, unit, spacing, segmentationImageData, imageVoxelManager, }) => {
92
127
  stats.mean.unit = unit;
93
128
  stats.max.unit = unit;
94
129
  stats.min.unit = unit;
@@ -112,8 +147,8 @@ async function calculateVolumeStatistics(operationData, indices, unit) {
112
147
  }
113
148
  }
114
149
  return stats;
115
- }
116
- async function calculateStackStatistics(segImageIds, indices, unit) {
150
+ };
151
+ async function calculateStackStatistics({ segImageIds, indices, unit, mode }) {
117
152
  triggerWorkerProgress(eventTarget, 0);
118
153
  const segmentationInfo = [];
119
154
  const imageInfo = [];
@@ -142,12 +177,34 @@ async function calculateStackStatistics(segImageIds, indices, unit) {
142
177
  segmentationInfo,
143
178
  imageInfo,
144
179
  indices,
180
+ mode,
145
181
  });
146
182
  triggerWorkerProgress(eventTarget, 100);
147
- stats.mean.unit = unit;
148
- stats.max.unit = unit;
149
- stats.min.unit = unit;
150
- return stats;
183
+ const spacing = segmentationInfo[0].spacing;
184
+ const segmentationImageData = segmentationInfo[0];
185
+ const imageVoxelManager = imageInfo[0].voxelManager;
186
+ if (mode === 'collective') {
187
+ return processSegmentationStatistics({
188
+ stats,
189
+ unit,
190
+ spacing,
191
+ segmentationImageData,
192
+ imageVoxelManager,
193
+ });
194
+ }
195
+ else {
196
+ const finalStats = {};
197
+ Object.entries(stats).forEach(([segmentIndex, stat]) => {
198
+ finalStats[segmentIndex] = processSegmentationStatistics({
199
+ stats: stat,
200
+ unit,
201
+ spacing,
202
+ segmentationImageData,
203
+ imageVoxelManager,
204
+ });
205
+ });
206
+ return finalStats;
207
+ }
151
208
  }
152
209
  function getSphereStats(testMax, radiusIJK, segData, imageVoxels, spacing) {
153
210
  const { pointIJK: centerIJK } = testMax;
@@ -7,6 +7,7 @@ import floodFill from './floodFill';
7
7
  import { getBrushSizeForToolGroup, setBrushSizeForToolGroup } from './brushSizeForToolGroup';
8
8
  import { getBrushThresholdForToolGroup, setBrushThresholdForToolGroup } from './brushThresholdForToolGroup';
9
9
  import VolumetricCalculator from './VolumetricCalculator';
10
+ import SegmentStatsCalculator from './SegmentStatsCalculator';
10
11
  import thresholdSegmentationByRange from './thresholdSegmentationByRange';
11
12
  import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
12
13
  import createBidirectionalToolData from './createBidirectionalToolData';
@@ -25,4 +26,4 @@ import getStatistics from './getStatistics';
25
26
  import * as validateLabelmap from './validateLabelmap';
26
27
  import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
27
28
  import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
28
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, 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, };
@@ -7,6 +7,7 @@ import floodFill from './floodFill';
7
7
  import { getBrushSizeForToolGroup, setBrushSizeForToolGroup, } from './brushSizeForToolGroup';
8
8
  import { getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, } from './brushThresholdForToolGroup';
9
9
  import VolumetricCalculator from './VolumetricCalculator';
10
+ import SegmentStatsCalculator from './SegmentStatsCalculator';
10
11
  import thresholdSegmentationByRange from './thresholdSegmentationByRange';
11
12
  import contourAndFindLargestBidirectional from './contourAndFindLargestBidirectional';
12
13
  import createBidirectionalToolData from './createBidirectionalToolData';
@@ -25,4 +26,4 @@ import getStatistics from './getStatistics';
25
26
  import * as validateLabelmap from './validateLabelmap';
26
27
  import { computeStackLabelmapFromVolume } from '../../stateManagement/segmentation/helpers/computeStackLabelmapFromVolume';
27
28
  import { computeVolumeLabelmapFromStack } from '../../stateManagement/segmentation/helpers/computeVolumeLabelmapFromStack';
28
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, getOrCreateSegmentationVolume, getStatistics, validateLabelmap, computeStackLabelmapFromVolume, 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, };
@@ -1,10 +1,10 @@
1
1
  import { expose } from 'comlink';
2
2
  import { utilities } from '@cornerstonejs/core';
3
- import VolumetricCalculator from '../utilities/segmentation/VolumetricCalculator';
3
+ import SegmentStatsCalculator from '../utilities/segmentation/SegmentStatsCalculator';
4
4
  const { VoxelManager } = utilities;
5
5
  const computeWorker = {
6
6
  calculateSegmentsStatisticsVolume: (args) => {
7
- const { segmentationInfo, imageInfo, indices } = args;
7
+ const { segmentationInfo, imageInfo, indices, mode } = args;
8
8
  const { scalarData: segmentationScalarData, dimensions: segmentationDimensions, spacing: segmentationSpacing, } = segmentationInfo;
9
9
  const { scalarData: imageScalarData, dimensions: imageDimensions } = imageInfo;
10
10
  if (segmentationDimensions[0] !== imageDimensions[0] ||
@@ -20,26 +20,30 @@ const computeWorker = {
20
20
  dimensions: imageDimensions,
21
21
  scalarData: imageScalarData,
22
22
  });
23
+ SegmentStatsCalculator.statsInit({ storePointData: false, indices, mode });
23
24
  segVoxelManager.forEach(({ value, pointIJK, index }) => {
24
25
  if (indices.indexOf(value) === -1) {
25
26
  return;
26
27
  }
27
28
  const imageValue = imageVoxelManager.getAtIndex(index);
28
- VolumetricCalculator.statsCallback({
29
+ SegmentStatsCalculator.statsCallback({
30
+ segmentIndex: value,
29
31
  value: imageValue,
30
32
  pointIJK,
31
33
  });
32
34
  }, {
33
35
  boundsIJK: imageVoxelManager.getDefaultBounds(),
34
36
  });
35
- const stats = VolumetricCalculator.getStatistics({
37
+ const stats = SegmentStatsCalculator.getStatistics({
36
38
  spacing: segmentationSpacing,
37
39
  unit: 'mm',
40
+ mode,
38
41
  });
39
42
  return stats;
40
43
  },
41
44
  calculateSegmentsStatisticsStack: (args) => {
42
- const { segmentationInfo, imageInfo, indices } = args;
45
+ const { segmentationInfo, imageInfo, indices, mode } = args;
46
+ SegmentStatsCalculator.statsInit({ storePointData: true, indices, mode });
43
47
  for (let i = 0; i < segmentationInfo.length; i++) {
44
48
  const segInfo = segmentationInfo[i];
45
49
  const imgInfo = imageInfo[i];
@@ -61,16 +65,19 @@ const computeWorker = {
61
65
  return;
62
66
  }
63
67
  const imageValue = imageVoxelManager.getAtIndex(index);
64
- VolumetricCalculator.statsCallback({
68
+ SegmentStatsCalculator.statsCallback({
69
+ segmentIndex: value,
65
70
  value: imageValue,
71
+ pointIJK,
66
72
  });
67
73
  }, {
68
74
  boundsIJK: imageVoxelManager.getDefaultBounds(),
69
75
  });
70
76
  }
71
77
  const spacing = segmentationInfo[0].spacing;
72
- const stats = VolumetricCalculator.getStatistics({
78
+ const stats = SegmentStatsCalculator.getStatistics({
73
79
  spacing,
80
+ mode,
74
81
  });
75
82
  return stats;
76
83
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
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": "^2.11.2"
104
104
  },
105
105
  "peerDependencies": {
106
- "@cornerstonejs/core": "^3.1.1",
106
+ "@cornerstonejs/core": "^3.2.0",
107
107
  "@kitware/vtk.js": "32.9.0",
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": "91a215bf75b76f6cbd017a811b5dfcff60f7f060"
125
+ "gitHead": "ee69f78b449fce9b37702ad5e158d6764d00aff9"
126
126
  }