@cornerstonejs/core 1.55.0 → 1.56.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 (115) hide show
  1. package/dist/cjs/RenderingEngine/CanvasActor/CanvasMapper.d.ts +6 -0
  2. package/dist/cjs/RenderingEngine/CanvasActor/CanvasMapper.js +12 -0
  3. package/dist/cjs/RenderingEngine/CanvasActor/CanvasMapper.js.map +1 -0
  4. package/dist/cjs/RenderingEngine/CanvasActor/CanvasProperties.d.ts +15 -0
  5. package/dist/cjs/RenderingEngine/CanvasActor/CanvasProperties.js +33 -0
  6. package/dist/cjs/RenderingEngine/CanvasActor/CanvasProperties.js.map +1 -0
  7. package/dist/cjs/RenderingEngine/CanvasActor/index.d.ts +23 -0
  8. package/dist/cjs/RenderingEngine/CanvasActor/index.js +163 -0
  9. package/dist/cjs/RenderingEngine/CanvasActor/index.js.map +1 -0
  10. package/dist/cjs/RenderingEngine/VideoViewport.d.ts +28 -6
  11. package/dist/cjs/RenderingEngine/VideoViewport.js +55 -13
  12. package/dist/cjs/RenderingEngine/VideoViewport.js.map +1 -1
  13. package/dist/cjs/RenderingEngine/Viewport.js +3 -2
  14. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  15. package/dist/cjs/RenderingEngine/helpers/addImageSlicesToViewports.js +2 -3
  16. package/dist/cjs/RenderingEngine/helpers/addImageSlicesToViewports.js.map +1 -1
  17. package/dist/cjs/enums/{VideoViewport.js → VideoEnums.js} +1 -1
  18. package/dist/cjs/enums/VideoEnums.js.map +1 -0
  19. package/dist/cjs/enums/index.d.ts +2 -2
  20. package/dist/cjs/enums/index.js +3 -3
  21. package/dist/cjs/enums/index.js.map +1 -1
  22. package/dist/cjs/loaders/imageLoader.d.ts +9 -7
  23. package/dist/cjs/loaders/imageLoader.js +9 -7
  24. package/dist/cjs/loaders/imageLoader.js.map +1 -1
  25. package/dist/cjs/types/IActor.d.ts +8 -1
  26. package/dist/cjs/types/IImage.d.ts +3 -0
  27. package/dist/cjs/types/IImageVolume.d.ts +3 -1
  28. package/dist/cjs/types/IVideoViewport.d.ts +1 -0
  29. package/dist/cjs/types/index.d.ts +2 -2
  30. package/dist/cjs/utilities/RLEVoxelMap.d.ts +26 -0
  31. package/dist/cjs/utilities/RLEVoxelMap.js +178 -0
  32. package/dist/cjs/utilities/RLEVoxelMap.js.map +1 -0
  33. package/dist/cjs/utilities/VoxelManager.d.ts +11 -3
  34. package/dist/cjs/utilities/VoxelManager.js +76 -1
  35. package/dist/cjs/utilities/VoxelManager.js.map +1 -1
  36. package/dist/cjs/utilities/updateVTKImageDataWithCornerstoneImage.js +3 -0
  37. package/dist/cjs/utilities/updateVTKImageDataWithCornerstoneImage.js.map +1 -1
  38. package/dist/esm/RenderingEngine/CanvasActor/CanvasMapper.js +9 -0
  39. package/dist/esm/RenderingEngine/CanvasActor/CanvasMapper.js.map +1 -0
  40. package/dist/esm/RenderingEngine/CanvasActor/CanvasProperties.js +30 -0
  41. package/dist/esm/RenderingEngine/CanvasActor/CanvasProperties.js.map +1 -0
  42. package/dist/esm/RenderingEngine/CanvasActor/index.js +157 -0
  43. package/dist/esm/RenderingEngine/CanvasActor/index.js.map +1 -0
  44. package/dist/esm/RenderingEngine/VideoViewport.js +53 -12
  45. package/dist/esm/RenderingEngine/VideoViewport.js.map +1 -1
  46. package/dist/esm/RenderingEngine/Viewport.js +2 -2
  47. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  48. package/dist/esm/RenderingEngine/helpers/addImageSlicesToViewports.js +2 -3
  49. package/dist/esm/RenderingEngine/helpers/addImageSlicesToViewports.js.map +1 -1
  50. package/dist/esm/enums/{VideoViewport.js → VideoEnums.js} +1 -1
  51. package/dist/esm/enums/VideoEnums.js.map +1 -0
  52. package/dist/esm/enums/index.js +2 -2
  53. package/dist/esm/enums/index.js.map +1 -1
  54. package/dist/esm/loaders/imageLoader.js +7 -7
  55. package/dist/esm/loaders/imageLoader.js.map +1 -1
  56. package/dist/esm/utilities/RLEVoxelMap.js +175 -0
  57. package/dist/esm/utilities/RLEVoxelMap.js.map +1 -0
  58. package/dist/esm/utilities/VoxelManager.js +73 -1
  59. package/dist/esm/utilities/VoxelManager.js.map +1 -1
  60. package/dist/esm/utilities/updateVTKImageDataWithCornerstoneImage.js +3 -0
  61. package/dist/esm/utilities/updateVTKImageDataWithCornerstoneImage.js.map +1 -1
  62. package/dist/types/RenderingEngine/CanvasActor/CanvasMapper.d.ts +7 -0
  63. package/dist/types/RenderingEngine/CanvasActor/CanvasMapper.d.ts.map +1 -0
  64. package/dist/types/RenderingEngine/CanvasActor/CanvasProperties.d.ts +16 -0
  65. package/dist/types/RenderingEngine/CanvasActor/CanvasProperties.d.ts.map +1 -0
  66. package/dist/types/RenderingEngine/CanvasActor/index.d.ts +24 -0
  67. package/dist/types/RenderingEngine/CanvasActor/index.d.ts.map +1 -0
  68. package/dist/types/RenderingEngine/VideoViewport.d.ts +28 -6
  69. package/dist/types/RenderingEngine/VideoViewport.d.ts.map +1 -1
  70. package/dist/types/enums/{VideoViewport.d.ts → VideoEnums.d.ts} +1 -1
  71. package/dist/types/enums/VideoEnums.d.ts.map +1 -0
  72. package/dist/types/enums/index.d.ts +2 -2
  73. package/dist/types/enums/index.d.ts.map +1 -1
  74. package/dist/types/loaders/imageLoader.d.ts +9 -7
  75. package/dist/types/loaders/imageLoader.d.ts.map +1 -1
  76. package/dist/types/types/IActor.d.ts +8 -1
  77. package/dist/types/types/IActor.d.ts.map +1 -1
  78. package/dist/types/types/IImage.d.ts +3 -0
  79. package/dist/types/types/IImage.d.ts.map +1 -1
  80. package/dist/types/types/IImageVolume.d.ts +3 -1
  81. package/dist/types/types/IImageVolume.d.ts.map +1 -1
  82. package/dist/types/types/IVideoViewport.d.ts +1 -0
  83. package/dist/types/types/IVideoViewport.d.ts.map +1 -1
  84. package/dist/types/types/index.d.ts +2 -2
  85. package/dist/types/types/index.d.ts.map +1 -1
  86. package/dist/types/utilities/RLEVoxelMap.d.ts +27 -0
  87. package/dist/types/utilities/RLEVoxelMap.d.ts.map +1 -0
  88. package/dist/types/utilities/VoxelManager.d.ts +11 -3
  89. package/dist/types/utilities/VoxelManager.d.ts.map +1 -1
  90. package/dist/types/utilities/updateVTKImageDataWithCornerstoneImage.d.ts.map +1 -1
  91. package/dist/umd/index.js +1 -1
  92. package/dist/umd/index.js.map +1 -1
  93. package/package.json +2 -2
  94. package/src/RenderingEngine/CanvasActor/CanvasMapper.ts +17 -0
  95. package/src/RenderingEngine/CanvasActor/CanvasProperties.ts +49 -0
  96. package/src/RenderingEngine/CanvasActor/index.ts +231 -0
  97. package/src/RenderingEngine/VideoViewport.ts +83 -18
  98. package/src/RenderingEngine/Viewport.ts +2 -2
  99. package/src/RenderingEngine/helpers/addImageSlicesToViewports.ts +2 -2
  100. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLTexture.js +1 -1
  101. package/src/enums/index.ts +2 -2
  102. package/src/loaders/imageLoader.ts +39 -16
  103. package/src/types/IActor.ts +13 -1
  104. package/src/types/IImage.ts +4 -0
  105. package/src/types/IImageVolume.ts +5 -0
  106. package/src/types/IVideoViewport.ts +5 -0
  107. package/src/types/index.ts +8 -1
  108. package/src/utilities/RLEVoxelMap.ts +331 -0
  109. package/src/utilities/VoxelManager.ts +175 -5
  110. package/src/utilities/updateVTKImageDataWithCornerstoneImage.ts +4 -0
  111. package/dist/cjs/enums/VideoViewport.js.map +0 -1
  112. package/dist/esm/enums/VideoViewport.js.map +0 -1
  113. package/dist/types/enums/VideoViewport.d.ts.map +0 -1
  114. /package/dist/cjs/enums/{VideoViewport.d.ts → VideoEnums.d.ts} +0 -0
  115. /package/src/enums/{VideoViewport.ts → VideoEnums.ts} +0 -0
@@ -6,6 +6,18 @@ export type Actor = vtkActor;
6
6
  export type VolumeActor = vtkVolume;
7
7
  export type ImageActor = vtkImageSlice;
8
8
 
9
+ export interface ICanvasActor {
10
+ render(viewport, context): void;
11
+
12
+ getMapper();
13
+
14
+ getProperty();
15
+
16
+ isA(actorType): boolean;
17
+
18
+ getClassName(): string;
19
+ }
20
+
9
21
  /**
10
22
  * Cornerstone Actor Entry including actor uid, actual Actor, and
11
23
  * slabThickness for the actor. ActorEntry is the object that
@@ -15,7 +27,7 @@ export type ActorEntry = {
15
27
  /** actor UID */
16
28
  uid: string;
17
29
  /** actual actor object */
18
- actor: Actor | VolumeActor | ImageActor;
30
+ actor: Actor | VolumeActor | ImageActor | ICanvasActor;
19
31
  /** the id of the reference volume from which this actor is derived or created*/
20
32
  referenceId?: string;
21
33
  /** slab thickness for the actor */
@@ -2,8 +2,10 @@ import type CPUFallbackLUT from './CPUFallbackLUT';
2
2
  import type CPUFallbackColormap from './CPUFallbackColormap';
3
3
  import type CPUFallbackEnabledElement from './CPUFallbackEnabledElement';
4
4
  import type { PixelDataTypedArray } from './PixelDataTypedArray';
5
+ import type VoxelManager from '../utilities/VoxelManager';
5
6
  import { ImageQualityStatus } from '../enums';
6
7
  import IImageCalibration from './IImageCalibration';
8
+ import RGB from './RGB';
7
9
 
8
10
  /**
9
11
  * Cornerstone Image interface, it is used for both CPU and GPU rendering
@@ -122,6 +124,8 @@ interface IImage {
122
124
  calibration?: IImageCalibration;
123
125
  imageFrame?: any;
124
126
 
127
+ voxelManager?: VoxelManager<number> | VoxelManager<RGB>;
128
+
125
129
  bufferView?: {
126
130
  buffer: ArrayBuffer;
127
131
  offset: number;
@@ -1,10 +1,12 @@
1
1
  import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
2
+ import type { VoxelManager } from '../utilities';
2
3
  import {
3
4
  Metadata,
4
5
  PixelDataTypedArray,
5
6
  Point3,
6
7
  IImageLoadObject,
7
8
  Mat3,
9
+ RGB,
8
10
  } from '../types';
9
11
 
10
12
  /**
@@ -69,6 +71,9 @@ interface IImageVolume {
69
71
  /** return the volume scalar data */
70
72
  getScalarData(): PixelDataTypedArray;
71
73
 
74
+ /** A voxel manager to manage the scalar data */
75
+ voxelManager?: VoxelManager<number> | VoxelManager<RGB>;
76
+
72
77
  convertToImageSlicesAndCache(): string[];
73
78
 
74
79
  /** return the index of a given imageId */
@@ -65,6 +65,11 @@ export default interface IVideoViewport extends IViewport {
65
65
  */
66
66
  setFrameNumber(frameNo: number);
67
67
 
68
+ /**
69
+ * Sets the current video time, in seconds
70
+ */
71
+ setTime(time: number);
72
+
68
73
  /**
69
74
  * Sets the range of frames for displaying. This is the range of frames
70
75
  * that are shown/looped over when the video is playing.
@@ -18,7 +18,13 @@ import type {
18
18
  PublicViewportInput,
19
19
  TargetSpecifier,
20
20
  } from './IViewport';
21
- import type { VolumeActor, Actor, ActorEntry, ImageActor } from './IActor';
21
+ import type {
22
+ VolumeActor,
23
+ Actor,
24
+ ActorEntry,
25
+ ImageActor,
26
+ ICanvasActor,
27
+ } from './IActor';
22
28
  import type {
23
29
  IImageLoadObject,
24
30
  IVolumeLoadObject,
@@ -160,6 +166,7 @@ export type {
160
166
  Actor,
161
167
  ActorEntry,
162
168
  ImageActor,
169
+ ICanvasActor,
163
170
  IImageLoadObject,
164
171
  IVolumeLoadObject,
165
172
  IVolumeInput,
@@ -0,0 +1,331 @@
1
+ import { PixelDataTypedArray } from '../types';
2
+
3
+ /**
4
+ * The RLERun specifies a contigous run of values for a row,
5
+ * where all indices (i only) from `[start,end)` have the specified
6
+ * value.
7
+ */
8
+ export type RLERun<T> = {
9
+ value: T;
10
+ start: number;
11
+ end: number;
12
+ };
13
+
14
+ /**
15
+ * RLE based implementation of a voxel map.
16
+ * This can be used as single or multi-plane, as the underlying indexes are
17
+ * mapped to rows and hte rows are indexed started at 0 and continuing
18
+ * incrementing for all rows in the multi-plane voxel.
19
+ */
20
+ export default class RLEVoxelMap<T> {
21
+ /**
22
+ * The rows for the voxel map is a map from the j index location (or for
23
+ * volumes, `j + k*height`) to a list of RLE runs. That is, each entry in
24
+ * the rows specifies the voxel data for a given row in the image.
25
+ * Then, the RLE runs themselves specify the pixel values for given rows as
26
+ * a pair of start/end indices, plus the value to apply.
27
+ */
28
+ protected rows = new Map<number, RLERun<T>[]>();
29
+ /** The height of the images stored in the voxel map (eg the height of each plane) */
30
+ protected height = 1;
31
+ /** The width of the image planes */
32
+ protected width = 1;
33
+ /**
34
+ * The number of image planes stored (the depth of the indices), with the k
35
+ * index going from 0...depth.
36
+ */
37
+ protected depth = 1;
38
+ /**
39
+ * A multiplier value to go from j values to overall index values.
40
+ */
41
+ protected jMultiple = 1;
42
+ /**
43
+ * A multiplier value to go from k values to overall index values.
44
+ */
45
+ protected kMultiple = 1;
46
+ /** Number of components in the value */
47
+ protected numComps = 1;
48
+
49
+ /**
50
+ * The default value returned for get.
51
+ * This allows treting the voxel map more like scalar data, returning the right
52
+ * default value for unset values.
53
+ * Set to 0 by default, but any maps where 0 not in T should update this value.
54
+ */
55
+ public defaultValue: T = 0 as unknown as T;
56
+
57
+ /**
58
+ * The constructor for creating pixel data.
59
+ */
60
+ public pixelDataConstructor = Uint8Array;
61
+
62
+ constructor(width: number, height: number, depth = 1) {
63
+ this.width = width;
64
+ this.height = height;
65
+ this.depth = depth;
66
+ this.jMultiple = width;
67
+ this.kMultiple = this.jMultiple * height;
68
+ }
69
+
70
+ /**
71
+ * Gets the value encoded in the map at the given index, which is
72
+ * an integer `[i,j,k]` voxel index, equal to `index=i+(j+k*height)*width`
73
+ * value (eg a standard ScalarData index for stack/volume single component
74
+ * indices.)
75
+ *
76
+ * Returns defaultValue if the RLE value is not found.
77
+ */
78
+ public get = (index: number): T => {
79
+ const i = index % this.jMultiple;
80
+ const j = (index - i) / this.jMultiple;
81
+ const rle = this.getRLE(i, j);
82
+ return rle?.value || this.defaultValue;
83
+ };
84
+
85
+ /**
86
+ * Gets a list of RLERun values which specify the data on the row j
87
+ * This allows applying or modifying the run directly. See CanvasActor
88
+ * for an example in the RLE rendering.
89
+ */
90
+ protected getRLE(i: number, j: number, k = 0): RLERun<T> {
91
+ const row = this.rows.get(j + k * this.height);
92
+ if (!row) {
93
+ return;
94
+ }
95
+ const index = this.findIndex(row, i);
96
+ const rle = row[index];
97
+ return i >= rle?.start ? rle : undefined;
98
+ }
99
+
100
+ /**
101
+ * Finds the index in the row that i is contained in, OR that i would be
102
+ * before. That is, the rle value for the returned index in that row
103
+ * has `i ε [start,end)` if a direct RLE is found, or `i ε [end_-1,start)` if
104
+ * in the prefix. If no RLE is found with that index, then
105
+ * `i ε [end_final,length)`
106
+ */
107
+ protected findIndex(row: RLERun<T>[], i: number) {
108
+ for (let index = 0; index < row.length; index++) {
109
+ const { end: iEnd } = row[index];
110
+ if (i < iEnd) {
111
+ return index;
112
+ }
113
+ }
114
+ return row.length;
115
+ }
116
+
117
+ /**
118
+ * Gets the run for the given j,k indices. This is used to allow fast access
119
+ * to runs for data for things like rendering entire rows of data.
120
+ */
121
+ public getRun = (j: number, k: number) => {
122
+ const runIndex = j + k * this.height;
123
+ return this.rows.get(runIndex);
124
+ };
125
+
126
+ /**
127
+ * Adds to the RLE at the given position. This is unfortunately fairly
128
+ * complex since it is desirable to minimize the number of runs, but to still
129
+ * allow it to be efficient.
130
+ */
131
+ public set = (index: number, value: T) => {
132
+ if (value === undefined) {
133
+ throw new Error(`Can't set undefined at ${index % this.width}`);
134
+ }
135
+ const i = index % this.width;
136
+ const j = (index - i) / this.width;
137
+ const row = this.rows.get(j);
138
+ if (!row) {
139
+ this.rows.set(j, [{ start: i, end: i + 1, value }]);
140
+ return;
141
+ }
142
+ const rleIndex = this.findIndex(row, i);
143
+ const rle1 = row[rleIndex];
144
+ const rle0 = row[rleIndex - 1];
145
+
146
+ // Adding to the end of the row
147
+ if (!rle1) {
148
+ // We are at the end, check if the previous rle can be extended
149
+ if (!rle0 || rle0.value !== value || rle0.end !== i) {
150
+ row[rleIndex] = { start: i, end: i + 1, value };
151
+ // validateRow(row, i, rleIndex, value);
152
+ return;
153
+ }
154
+ // Just add it to the previous element.
155
+ rle0.end++;
156
+ return;
157
+ }
158
+
159
+ const { start, end, value: oldValue } = rle1;
160
+
161
+ // Handle the already in place case
162
+ if (value === oldValue && i >= start) {
163
+ // validateRow(row, i, rleIndex, value, start);
164
+ return;
165
+ }
166
+
167
+ const rleInsert = { start: i, end: i + 1, value };
168
+ const isAfter = i > start;
169
+ const insertIndex = isAfter ? rleIndex + 1 : rleIndex;
170
+ const rlePrev = isAfter ? rle1 : rle0;
171
+ let rleNext = isAfter ? row[rleIndex + 1] : rle1;
172
+
173
+ // Can merge with previous value, so no insert
174
+ if (rlePrev?.value === value && rlePrev?.end === i) {
175
+ rlePrev.end++;
176
+ if (rleNext?.value === value && rleNext.start === i + 1) {
177
+ rlePrev.end = rleNext.end;
178
+ row.splice(rleIndex, 1);
179
+ // validateRow(row, i, rleIndex, value);
180
+ } else if (rleNext?.start === i) {
181
+ rleNext.start++;
182
+ if (rleNext.start === rleNext.end) {
183
+ row.splice(rleIndex, 1);
184
+ rleNext = row[rleIndex];
185
+ // Check if we can merge twice
186
+ if (rleNext?.start === i + 1 && rleNext.value === value) {
187
+ rlePrev.end = rleNext.end;
188
+ row.splice(rleIndex, 1);
189
+ }
190
+ }
191
+ // validateRow(row, i, rleIndex, value);
192
+ }
193
+ return;
194
+ }
195
+
196
+ // Can merge with next, so no insert
197
+ if (rleNext?.value === value && rleNext.start === i + 1) {
198
+ rleNext.start--;
199
+ if (rlePrev?.end > i) {
200
+ rlePrev.end = i;
201
+ if (rlePrev.end === rlePrev.start) {
202
+ row.splice(rleIndex, 1);
203
+ }
204
+ }
205
+ // validateRow(row, i, rleIndex, value);
206
+ return;
207
+ }
208
+
209
+ // Can't merge, need to see if we can replace
210
+ if (rleNext?.start === i && rleNext.end === i + 1) {
211
+ rleNext.value = value;
212
+ const nextnext = row[rleIndex + 1];
213
+ if (nextnext?.start == i + 1 && nextnext.value === value) {
214
+ row.splice(rleIndex + 1, 1);
215
+ rleNext.end = nextnext.end;
216
+ }
217
+ // validateRow(row, i, rleIndex, value);
218
+ return;
219
+ }
220
+
221
+ // Need to fix the next start value
222
+ if (i === rleNext?.start) {
223
+ rleNext.start++;
224
+ }
225
+ if (isAfter && end > i + 1) {
226
+ // Insert two items, to split the existing into three
227
+ row.splice(insertIndex, 0, rleInsert, {
228
+ start: i + 1,
229
+ end: rlePrev.end,
230
+ value: rlePrev.value,
231
+ });
232
+ } else {
233
+ row.splice(insertIndex, 0, rleInsert);
234
+ }
235
+ if (rlePrev?.end > i) {
236
+ rlePrev.end = i;
237
+ }
238
+ // validateRow(row, i, rleIndex, value, insertIndex);
239
+ };
240
+
241
+ /**
242
+ * Clears all entries.
243
+ */
244
+ public clear() {
245
+ this.rows.clear();
246
+ }
247
+
248
+ /**
249
+ * Gets the set of key entries - that is j values. This may include
250
+ * `j>=height`, where `j = key % height`, and `k = Math.floor(j / height)`
251
+ */
252
+ public keys(): number[] {
253
+ return [...this.rows.keys()];
254
+ }
255
+
256
+ /**
257
+ * Gets the pixel data into the provided pixel data array, or creates one
258
+ * according to the assigned type.
259
+ */
260
+ public getPixelData(
261
+ k = 0,
262
+ pixelData?: PixelDataTypedArray
263
+ ): PixelDataTypedArray {
264
+ if (!pixelData) {
265
+ pixelData = new this.pixelDataConstructor(
266
+ this.width * this.height * this.numComps
267
+ );
268
+ } else {
269
+ pixelData.fill(0);
270
+ }
271
+ const { width, height, numComps } = this;
272
+
273
+ for (let j = 0; j < height; j++) {
274
+ const row = this.getRun(j, k);
275
+ if (!row) {
276
+ continue;
277
+ }
278
+ if (numComps === 1) {
279
+ for (const rle of row) {
280
+ const rowOffset = j * width;
281
+ const { start, end, value } = rle;
282
+ for (let i = start; i < end; i++) {
283
+ pixelData[rowOffset + i] = value as unknown as number;
284
+ }
285
+ }
286
+ } else {
287
+ for (const rle of row) {
288
+ const rowOffset = j * width * numComps;
289
+ const { start, end, value } = rle;
290
+ for (let i = start; i < end; i += numComps) {
291
+ for (let comp = 0; comp < numComps; comp++) {
292
+ pixelData[rowOffset + i + comp] = value[comp];
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ return pixelData;
299
+ }
300
+ }
301
+
302
+ // This is some code to allow debugging RLE maps
303
+ // To be deleted along with references once RLE is better tested.
304
+ // Might move to testing code at that point
305
+ // function validateRow(row, ...inputs) {
306
+ // if (!row) {
307
+ // return;
308
+ // }
309
+ // let lastRle;
310
+ // for (const rle of row) {
311
+ // const { start, end, value } = rle;
312
+ // if (start < 0 || end > 1920 || start >= end) {
313
+ // console.log('Wrong order', ...inputs);
314
+ // debugger;
315
+ // }
316
+ // if (!lastRle) {
317
+ // lastRle = rle;
318
+ // continue;
319
+ // }
320
+ // const { start: lastStart, end: lastEnd, value: lastValue } = lastRle;
321
+ // lastRle = rle;
322
+ // if (start < lastEnd) {
323
+ // console.log('inputs for wrong overlap', ...inputs);
324
+ // debugger;
325
+ // }
326
+ // if (start === lastEnd && value === lastValue) {
327
+ // console.log('inputs for two in a row same', ...inputs);
328
+ // debugger;
329
+ // }
330
+ // }
331
+ // }
@@ -1,4 +1,19 @@
1
- import type { BoundsIJK, Point3, PixelDataTypedArray } from '../types';
1
+ import type {
2
+ BoundsIJK,
3
+ Point3,
4
+ PixelDataTypedArray,
5
+ IImage,
6
+ RGB,
7
+ } from '../types';
8
+ import RLEVoxelMap from './RLEVoxelMap';
9
+ import isEqual from './isEqual';
10
+
11
+ /**
12
+ * Have a default size for cached RLE encoded images. This is hard to guess
13
+ * up front because the RLE is usually used to store new/updated data, but this
14
+ * is a first guess.
15
+ */
16
+ const DEFAULT_RLE_SIZE = 5 * 1024;
2
17
 
3
18
  /**
4
19
  * This is a simple, standard interface to values associated with a voxel.
@@ -13,10 +28,11 @@ export default class VoxelManager<T> {
13
28
 
14
29
  // Provide direct access to the underlying data, if any
15
30
  public scalarData: PixelDataTypedArray;
16
- public map: Map<number, T>;
31
+ public map: Map<number, T> | RLEVoxelMap<T>;
17
32
  public sourceVoxelManager: VoxelManager<T>;
18
33
  public isInObject: (pointIPS, pointIJK) => boolean;
19
34
  public readonly dimensions: Point3;
35
+ public numComps = 1;
20
36
 
21
37
  points: Set<number>;
22
38
  width: number;
@@ -233,21 +249,89 @@ export default class VoxelManager<T> {
233
249
  bounds[2][1] = Math.max(point[2], bounds[2][1]);
234
250
  }
235
251
 
252
+ /**
253
+ * Gets the pixel data for the given array.
254
+ */
255
+ public getPixelData: (
256
+ sliceIndex?: number,
257
+ pixelData?: PixelDataTypedArray
258
+ ) => PixelDataTypedArray;
259
+
260
+ /**
261
+ * Creates a voxel manager backed by an array of scalar data having the
262
+ * given number of components.
263
+ * Note that the number of components can be larger than three, in case data
264
+ * is stored in additional pixels. However, the return type is still RGB.
265
+ */
266
+ public static createRGBVolumeVoxelManager(
267
+ dimensions: Point3,
268
+ scalarData,
269
+ numComponents
270
+ ): VoxelManager<RGB> {
271
+ const voxels = new VoxelManager<RGB>(
272
+ dimensions,
273
+ (index) => {
274
+ index *= numComponents;
275
+ return [scalarData[index++], scalarData[index++], scalarData[index++]];
276
+ },
277
+ (index, v) => {
278
+ index *= 3;
279
+ const isChanged = !isEqual(scalarData[index], v);
280
+ scalarData[index++] = v[0];
281
+ scalarData[index++] = v[1];
282
+ scalarData[index++] = v[2];
283
+ return isChanged;
284
+ }
285
+ );
286
+ voxels.numComps = numComponents;
287
+ voxels.scalarData = scalarData;
288
+ return voxels;
289
+ }
290
+
236
291
  /**
237
292
  * Creates a volume value accessor, based on a volume scalar data instance.
238
293
  * This also works for image value accessors for single plane (k=0) accessors.
239
294
  */
240
295
  public static createVolumeVoxelManager(
241
296
  dimensions: Point3,
242
- scalarData
243
- ): VoxelManager<number> {
297
+ scalarData,
298
+ numComponents = 0
299
+ ): VoxelManager<number> | VoxelManager<RGB> {
244
300
  if (dimensions.length !== 3) {
245
301
  throw new Error(
246
302
  'Dimensions must be provided as [number, number, number] for [width, height, depth]'
247
303
  );
248
304
  }
305
+ if (!numComponents) {
306
+ numComponents =
307
+ scalarData.length / dimensions[0] / dimensions[1] / dimensions[2];
308
+ // We only support 1,3,4 component data, and sometimes the scalar data
309
+ // doesn't match for some reason, so throw an exception
310
+ if (numComponents > 4 || numComponents < 1 || numComponents === 2) {
311
+ throw new Error(
312
+ `Number of components ${numComponents} must be 1, 3 or 4`
313
+ );
314
+ }
315
+ }
316
+ if (numComponents > 1) {
317
+ return VoxelManager.createRGBVolumeVoxelManager(
318
+ dimensions,
319
+ scalarData,
320
+ numComponents
321
+ );
322
+ }
323
+ return VoxelManager.createNumberVolumeVoxelManager(dimensions, scalarData);
324
+ }
249
325
 
250
- const voxels = new VoxelManager(
326
+ /**
327
+ * Creates a volume voxel manager that works on single numeric values stored
328
+ * in an array like structure of numbers.
329
+ */
330
+ public static createNumberVolumeVoxelManager(
331
+ dimensions: Point3,
332
+ scalarData
333
+ ): VoxelManager<number> {
334
+ const voxels = new VoxelManager<number>(
251
335
  dimensions,
252
336
  (index) => scalarData[index],
253
337
  (index, v) => {
@@ -308,6 +392,92 @@ export default class VoxelManager<T> {
308
392
  voxelManager.sourceVoxelManager = sourceVoxelManager;
309
393
  return voxelManager;
310
394
  }
395
+
396
+ /**
397
+ * Creates a lazy voxel manager that will create an image plane as required
398
+ * for each slice of a volume as it gets changed. This can be used to
399
+ * store image data that gets created as required.
400
+ */
401
+ public static createLazyVoxelManager<T>(
402
+ dimensions: Point3,
403
+ planeFactory: (width: number, height: number) => T
404
+ ): VoxelManager<T> {
405
+ const map = new Map<number, T>();
406
+ const [width, height, depth] = dimensions;
407
+ const planeSize = width * height;
408
+
409
+ const voxelManager = new VoxelManager(
410
+ dimensions,
411
+ (index) => map.get(Math.floor(index / planeSize))?.[index % planeSize],
412
+ (index, v) => {
413
+ const k = Math.floor(index / planeSize);
414
+ let layer = map.get(k);
415
+ if (!layer) {
416
+ layer = planeFactory(width, height);
417
+ map.set(k, layer);
418
+ }
419
+ layer[index % planeSize] = v;
420
+ }
421
+ );
422
+ voxelManager.map = map;
423
+ return voxelManager;
424
+ }
425
+
426
+ /**
427
+ * Creates a RLE based voxel manager. This is effective for storing
428
+ * segmentation maps or already RLE encoded data such as ultrasounds.
429
+ */
430
+ public static createRLEVoxelManager<T>(dimensions: Point3): VoxelManager<T> {
431
+ const [width, height, depth] = dimensions;
432
+ const map = new RLEVoxelMap<T>(width, height, depth);
433
+
434
+ const voxelManager = new VoxelManager<T>(
435
+ dimensions,
436
+ (index) => map.get(index),
437
+ (index, v) => map.set(index, v)
438
+ );
439
+ voxelManager.map = map;
440
+ voxelManager.getPixelData = map.getPixelData.bind(map);
441
+ return voxelManager;
442
+ }
443
+
444
+ /**
445
+ * This method adds a voxelManager instance to the image object
446
+ * where the object added is of type:
447
+ * 1. RLE map if the scalar data is missing or too small (dummy data)
448
+ * 2. Volume VoxelManager scalar data representations
449
+ */
450
+ public static addInstanceToImage(image: IImage) {
451
+ const { width, height } = image;
452
+ const scalarData = image.getPixelData();
453
+ // This test works for single images, or single representations of images
454
+ // from a volume representation, for grayscale, indexed and RGB or RGBA images.
455
+ if (scalarData?.length >= width * height) {
456
+ // This case means there is enough scalar data for at least one image,
457
+ // with 1 or more components, and creates a volume voxel manager
458
+ // that can lookup the data
459
+ image.voxelManager = VoxelManager.createVolumeVoxelManager(
460
+ [width, height, 1],
461
+ scalarData
462
+ );
463
+ return;
464
+ }
465
+ // This case occurs when the image data is a dummy image data set
466
+ // created just to prevent exceptions in the caching logic. Then, the
467
+ // RLE voxel manager can be created to store the data instead.
468
+ image.voxelManager = VoxelManager.createRLEVoxelManager<number>([
469
+ width,
470
+ height,
471
+ 1,
472
+ ]);
473
+ // The RLE voxel manager knows how to get scalar data pixel data representations.
474
+ // That allows using the RLE representation as a normal pixel data representation
475
+ // for VIEWING purposes.
476
+ image.getPixelData = image.voxelManager.getPixelData;
477
+ // Assign a different size to the cached data because this is actually
478
+ // storing an RLE representation, which doesn't have an up front size.
479
+ image.sizeInBytes = DEFAULT_RLE_SIZE;
480
+ }
311
481
  }
312
482
 
313
483
  export type { VoxelManager };
@@ -6,6 +6,10 @@ function updateVTKImageDataWithCornerstoneImage(
6
6
  image: IImage
7
7
  ) {
8
8
  const pixelData = image.getPixelData();
9
+ if (!sourceImageData.getPointData) {
10
+ // This happens for a CanvasActor, that doesn't have the getPointData
11
+ return;
12
+ }
9
13
  const scalarData = sourceImageData
10
14
  .getPointData()
11
15
  .getScalars()
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoViewport.js","sourceRoot":"","sources":["../../../src/enums/VideoViewport.ts"],"names":[],"mappings":";;;AAGA,IAAK,SAGJ;AAHD,WAAK,SAAS;IACZ,wBAAW,CAAA;IACX,yBAAY,CAAA;AACd,CAAC,EAHI,SAAS,KAAT,SAAS,QAGb;AAEQ,8BAAS"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoViewport.js","sourceRoot":"","sources":["../../../src/enums/VideoViewport.ts"],"names":[],"mappings":"AAGA,IAAK,SAGJ;AAHD,WAAK,SAAS;IACZ,wBAAW,CAAA;IACX,yBAAY,CAAA;AACd,CAAC,EAHI,SAAS,KAAT,SAAS,QAGb;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoViewport.d.ts","sourceRoot":"","sources":["../../../src/enums/VideoViewport.ts"],"names":[],"mappings":"AAGA,aAAK,SAAS;IACZ,KAAK,MAAM;IACX,MAAM,MAAM;CACb;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
File without changes