@cornerstonejs/core 1.36.3 → 1.37.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.36.3",
3
+ "version": "1.37.0",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -47,5 +47,5 @@
47
47
  "type": "individual",
48
48
  "url": "https://ohif.org/donate"
49
49
  },
50
- "gitHead": "b6b6f9273ca4cff849336005d199c4b1b1570815"
50
+ "gitHead": "5816f19627715147dc453e33e1480bf66dd63cf6"
51
51
  }
@@ -0,0 +1,5 @@
1
+ import type Point2 from './Point2';
2
+
3
+ type BoundsIJK = [Point2, Point2, Point2];
4
+
5
+ export default BoundsIJK;
@@ -105,6 +105,7 @@ import type {
105
105
  InternalVideoCamera,
106
106
  VideoViewportInput,
107
107
  } from './VideoViewportTypes';
108
+ import type BoundsIJK from './BoundsIJK';
108
109
 
109
110
  export type {
110
111
  // config
@@ -212,6 +213,7 @@ export type {
212
213
  // video
213
214
  InternalVideoCamera,
214
215
  VideoViewportInput,
216
+ BoundsIJK,
215
217
  Color,
216
218
  ColorLUT,
217
219
  };
@@ -0,0 +1,296 @@
1
+ import type { BoundsIJK, Point3, VolumeScalarData } from '../types';
2
+
3
+ /**
4
+ * This is a simple, standard interface to values associated with a voxel.
5
+ */
6
+ export default class VoxelManager<T> {
7
+ public modifiedSlices = new Set<number>();
8
+ public boundsIJK = [
9
+ [Infinity, -Infinity],
10
+ [Infinity, -Infinity],
11
+ [Infinity, -Infinity],
12
+ ] as BoundsIJK;
13
+
14
+ // Provide direct access to the underlying data, if any
15
+ public scalarData: VolumeScalarData;
16
+ public map: Map<number, T>;
17
+ public sourceVoxelManager: VoxelManager<T>;
18
+ public isInObject: (pointIPS, pointIJK) => boolean;
19
+ public readonly dimensions: Point3;
20
+
21
+ points: Set<number>;
22
+ width: number;
23
+ frameSize: number;
24
+ _get: (index: number) => T;
25
+ _set: (index: number, v: T) => boolean | void;
26
+
27
+ /**
28
+ * Creates a generic voxel value accessor, with access to the values
29
+ * provided by the _get and optionally _set values.
30
+ * @param dimensions - for the voxel volume
31
+ * @param _get - called to get a value by index
32
+ * @param _set - called when setting a value
33
+ */
34
+ constructor(
35
+ dimensions,
36
+ _get: (index: number) => T,
37
+ _set?: (index: number, v: T) => boolean | void
38
+ ) {
39
+ this.dimensions = dimensions;
40
+ this.width = dimensions[0];
41
+ this.frameSize = this.width * dimensions[1];
42
+ this._get = _get;
43
+ this._set = _set;
44
+ }
45
+
46
+ /**
47
+ * Gets the voxel value at position i,j,k.
48
+ */
49
+ public getAtIJK = (i, j, k) => {
50
+ const index = i + j * this.width + k * this.frameSize;
51
+ return this._get(index);
52
+ };
53
+
54
+ /**
55
+ * Sets the voxel value at position i,j,k and records the slice
56
+ * that was modified.
57
+ */
58
+ public setAtIJK = (i: number, j: number, k: number, v) => {
59
+ const index = i + j * this.width + k * this.frameSize;
60
+ if (this._set(index, v) !== false) {
61
+ this.modifiedSlices.add(k);
62
+ VoxelManager.addBounds(this.boundsIJK, [i, j, k]);
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Adds a point as an array or an index value to the set of points
68
+ * associated with this voxel value.
69
+ * Can be used for tracking clicked points or other modified values.
70
+ */
71
+ public addPoint(point: Point3 | number) {
72
+ const index = Array.isArray(point)
73
+ ? point[0] + this.width * point[1] + this.frameSize * point[2]
74
+ : point;
75
+ if (!this.points) {
76
+ this.points = new Set<number>();
77
+ }
78
+ this.points.add(index);
79
+ }
80
+
81
+ /**
82
+ * Gets the list of added points as an array of Point3 values
83
+ */
84
+ public getPoints(): Point3[] {
85
+ return this.points
86
+ ? [...this.points].map((index) => this.toIJK(index))
87
+ : [];
88
+ }
89
+
90
+ /**
91
+ * Gets the points added using addPoint as an array of indices.
92
+ */
93
+ public getPointIndices(): number[] {
94
+ return this.points ? [...this.points] : [];
95
+ }
96
+
97
+ /**
98
+ * Gets the voxel value at the given Point3 location.
99
+ */
100
+ public getAtIJKPoint = ([i, j, k]) => this.getAtIJK(i, j, k);
101
+
102
+ /**
103
+ * Sets the voxel value at the given point3 location to the specified value.
104
+ * Records the z index modified.
105
+ * Will record the index value if the VoxelManager is backed by a map.
106
+ */
107
+ public setAtIJKPoint = ([i, j, k], v) => this.setAtIJK(i, j, k, v);
108
+
109
+ /**
110
+ * Gets the value at the given index.
111
+ */
112
+ public getAtIndex = (index) => this._get(index);
113
+
114
+ /**
115
+ * Sets the value at the given index
116
+ */
117
+ public setAtIndex = (index, v) => {
118
+ if (this._set(index, v) !== false) {
119
+ const pointIJK = this.toIJK(index);
120
+ this.modifiedSlices.add(pointIJK[2]);
121
+ VoxelManager.addBounds(this.boundsIJK, pointIJK);
122
+ }
123
+ };
124
+
125
+ /**
126
+ * Converts an index value to a Point3 IJK value
127
+ */
128
+ public toIJK(index: number): Point3 {
129
+ return [
130
+ index % this.width,
131
+ Math.floor((index % this.frameSize) / this.width),
132
+ Math.floor(index / this.frameSize),
133
+ ];
134
+ }
135
+
136
+ /**
137
+ * Converts an IJK Point3 value to an index value
138
+ */
139
+ public toIndex(ijk: Point3) {
140
+ return ijk[0] + ijk[1] * this.width + ijk[2] * this.frameSize;
141
+ }
142
+
143
+ /**
144
+ * Gets the bounds for the modified set of values.
145
+ */
146
+ public getBoundsIJK(): BoundsIJK {
147
+ if (this.boundsIJK[0][0] < this.dimensions[0]) {
148
+ return this.boundsIJK;
149
+ }
150
+ return this.dimensions.map((dimension) => [0, dimension - 1]) as BoundsIJK;
151
+ }
152
+
153
+ /**
154
+ * Iterate over the points within the bounds, or the modified points if recorded.
155
+ */
156
+ public forEach = (callback, options?) => {
157
+ const boundsIJK = options?.boundsIJK || this.getBoundsIJK();
158
+ const { isWithinObject } = options || {};
159
+ if (this.map) {
160
+ // Optimize this for only values in the map
161
+ for (const index of this.map.keys()) {
162
+ const pointIJK = this.toIJK(index);
163
+ const value = this._get(index);
164
+ const callbackArguments = { value, index, pointIJK };
165
+ if (isWithinObject?.(callbackArguments) === false) {
166
+ continue;
167
+ }
168
+ callback(callbackArguments);
169
+ }
170
+ } else {
171
+ for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
172
+ const kIndex = k * this.frameSize;
173
+ for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
174
+ const jIndex = kIndex + j * this.width;
175
+ for (
176
+ let i = boundsIJK[0][0], index = jIndex + i;
177
+ i <= boundsIJK[0][1];
178
+ i++, index++
179
+ ) {
180
+ const value = this.getAtIndex(index);
181
+ const callbackArguments = { value, index, pointIJK: [i, j, k] };
182
+ if (isWithinObject?.(callbackArguments) === false) {
183
+ continue;
184
+ }
185
+ callback(callbackArguments);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ };
191
+
192
+ /**
193
+ * Clears any map specific data, as wellas the modified slices, points and
194
+ * bounds.
195
+ */
196
+ public clear() {
197
+ if (this.map) {
198
+ this.map.clear();
199
+ }
200
+ this.boundsIJK.map((bound) => {
201
+ bound[0] = Infinity;
202
+ bound[1] = -Infinity;
203
+ });
204
+ this.modifiedSlices.clear();
205
+ this.points?.clear();
206
+ }
207
+
208
+ /**
209
+ * @returns The array of modified k indices
210
+ */
211
+ public getArrayOfSlices(): number[] {
212
+ return Array.from(this.modifiedSlices);
213
+ }
214
+
215
+ /**
216
+ * Extends the bounds of this object to include the specified point
217
+ */
218
+ public static addBounds(bounds: BoundsIJK, point: Point3) {
219
+ bounds.forEach((bound, index) => {
220
+ bound[0] = Math.min(point[index], bound[0]);
221
+ bound[1] = Math.max(point[index], bound[1]);
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Creates a volume value accessor, based on a volume scalar data instance.
227
+ * This also works for image value accessors for single plane (k=0) accessors.
228
+ */
229
+ public static createVolumeVoxelManager(
230
+ dimensions: Point3,
231
+ scalarData
232
+ ): VoxelManager<number> {
233
+ const voxels = new VoxelManager(
234
+ dimensions,
235
+ (index) => scalarData[index],
236
+ (index, v) => {
237
+ const isChanged = scalarData[index] !== v;
238
+ scalarData[index] = v;
239
+ return isChanged;
240
+ }
241
+ );
242
+ voxels.scalarData = scalarData;
243
+ return voxels;
244
+ }
245
+
246
+ /**
247
+ * Creates a volume map value accessor. This is initially empty and
248
+ * the map stores the index to value instances.
249
+ * This is useful for sparse matrices containing pixel data.
250
+ */
251
+ public static createMapVoxelManager<T>(dimension: Point3): VoxelManager<T> {
252
+ const map = new Map<number, T>();
253
+ const voxelManager = new VoxelManager(
254
+ dimension,
255
+ map.get.bind(map),
256
+ (index, v) => map.set(index, v) && true
257
+ );
258
+ voxelManager.map = map;
259
+ return voxelManager;
260
+ }
261
+
262
+ /**
263
+ * Creates a history remembering voxel manager.
264
+ * This will remember the original values in the voxels, and will apply the
265
+ * update to the underlying source voxel manager.
266
+ */
267
+ public static createHistoryVoxelManager<T>(
268
+ sourceVoxelManager: VoxelManager<T>
269
+ ): VoxelManager<T> {
270
+ const map = new Map<number, T>();
271
+ const { dimensions } = sourceVoxelManager;
272
+ const voxelManager = new VoxelManager(
273
+ dimensions,
274
+ (index) => map.get(index),
275
+ function (index, v) {
276
+ if (!map.has(index)) {
277
+ const oldV = this.sourceVoxelManager.getAtIndex(index);
278
+ if (oldV === v) {
279
+ // No-op
280
+ return false;
281
+ }
282
+ map.set(index, oldV);
283
+ } else if (v === map.get(index)) {
284
+ map.delete(index);
285
+ }
286
+ this.sourceVoxelManager.setAtIndex(index, v);
287
+ }
288
+ );
289
+ voxelManager.map = map;
290
+ voxelManager.scalarData = sourceVoxelManager.scalarData;
291
+ voxelManager.sourceVoxelManager = sourceVoxelManager;
292
+ return voxelManager;
293
+ }
294
+ }
295
+
296
+ export type { VoxelManager };
@@ -59,6 +59,7 @@ import decimate from './decimate';
59
59
  import imageRetrieveMetadataProvider from './imageRetrieveMetadataProvider';
60
60
  import isVideoTransferSyntax from './isVideoTransferSyntax';
61
61
  import { getBufferConfiguration } from './getBufferConfiguration';
62
+ import VoxelManager from './VoxelManager';
62
63
 
63
64
  // name spaces
64
65
  import * as planar from './planar';
@@ -133,4 +134,5 @@ export {
133
134
  genericMetadataProvider,
134
135
  isVideoTransferSyntax,
135
136
  getBufferConfiguration,
137
+ VoxelManager,
136
138
  };