@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.55.0",
3
+ "version": "1.56.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": "78d3c2fd9e2cd07d1ec5b84201f9f909ff6ce7cf"
50
+ "gitHead": "73863e2aac88c70974cf966ca4e0eae11c1e67dc"
51
51
  }
@@ -0,0 +1,17 @@
1
+ import CanvasActor from '.';
2
+
3
+ /**
4
+ * Mimics the VTK mapper functionality, but for non-vtk canvas based rendering
5
+ * classes.
6
+ */
7
+ export default class CanvasMapper {
8
+ private actor: CanvasActor;
9
+
10
+ constructor(actor: CanvasActor) {
11
+ this.actor = actor;
12
+ }
13
+
14
+ getInputData() {
15
+ return this.actor.getImage();
16
+ }
17
+ }
@@ -0,0 +1,49 @@
1
+ import CanvasActor from '.';
2
+
3
+ /**
4
+ * Properties for rendering on a labelmap canvas actor.
5
+ * Mostly a no-op right now, but the transfer function settings are live.
6
+ */
7
+ export default class CanvasProperties {
8
+ private actor: CanvasActor;
9
+ private opacity = 0.4;
10
+ private outlineOpacity = 0.4;
11
+ private transferFunction = [];
12
+
13
+ constructor(actor: CanvasActor) {
14
+ this.actor = actor;
15
+ }
16
+
17
+ public setRGBTransferFunction(index, cfun) {
18
+ this.transferFunction[index] = cfun;
19
+ }
20
+
21
+ public setScalarOpacity(opacity: number) {
22
+ // No-op until this gets set correctly
23
+ // this.opacity = opacity;
24
+ }
25
+
26
+ public setInterpolationTypeToNearest() {
27
+ // No-op
28
+ }
29
+
30
+ public setUseLabelOutline() {
31
+ // No-op - not implemented
32
+ }
33
+
34
+ public setLabelOutlineOpacity(opacity) {
35
+ this.outlineOpacity = opacity;
36
+ }
37
+
38
+ public setLabelOutlineThickness() {
39
+ // No-op - requires outline to be implemented first
40
+ }
41
+
42
+ public getColor(index: number) {
43
+ const cfun = this.transferFunction[0];
44
+ const r = cfun.getRedValue(index);
45
+ const g = cfun.getGreenValue(index);
46
+ const b = cfun.getBlueValue(index);
47
+ return [r, g, b, this.opacity];
48
+ }
49
+ }
@@ -0,0 +1,231 @@
1
+ import type { IViewport } from '../../types/IViewport';
2
+ import type { ICanvasActor } from '../../types/IActor';
3
+ import CanvasProperties from './CanvasProperties';
4
+ import CanvasMapper from './CanvasMapper';
5
+
6
+ /**
7
+ * Handles canvas rendering of derived image data, typically label maps.
8
+ * This class will update the canvas from the given viewport with a
9
+ * derived image. The derived image can be a standard Cornerstone labelmap
10
+ * or be an RLE based one. The RLE based ones are significantly faster to
11
+ * render as they only render the area actually relevant.
12
+ */
13
+ export default class CanvasActor implements ICanvasActor {
14
+ private image;
15
+ private derivedImage;
16
+ private canvasProperties = new CanvasProperties(this);
17
+ private visibility = false;
18
+ private mapper = new CanvasMapper(this);
19
+ private viewport;
20
+ protected className = 'CanvasActor';
21
+ protected canvas;
22
+
23
+ constructor(viewport: IViewport, derivedImage) {
24
+ this.derivedImage = derivedImage;
25
+ this.viewport = viewport;
26
+ }
27
+
28
+ /**
29
+ * Renders an RLE representation of the viewport data. This is optimized to
30
+ * avoid iterating over any data not actually containing data.
31
+ */
32
+ protected renderRLE(viewport, context, voxelManager) {
33
+ const { width, height } = this.image;
34
+ let { canvas } = this;
35
+ if (!canvas || canvas.width !== width || canvas.height !== height) {
36
+ this.canvas = canvas = new window.OffscreenCanvas(width, height);
37
+ }
38
+ const localContext = canvas.getContext('2d');
39
+ const imageData = localContext.createImageData(width, height);
40
+ const { data: imageArray } = imageData;
41
+ imageArray.fill(0);
42
+ const { map } = voxelManager;
43
+ let dirtyX = Infinity;
44
+ let dirtyY = Infinity;
45
+ let dirtyX2 = -Infinity;
46
+ let dirtyY2 = -Infinity;
47
+ for (let y = 0; y < height; y++) {
48
+ const row = map.getRun(y, 0);
49
+ if (!row) {
50
+ continue;
51
+ }
52
+ dirtyY = Math.min(dirtyY, y);
53
+ dirtyY2 = Math.max(dirtyY2, y);
54
+ const baseOffset = (y * width) << 2;
55
+ let indicesToDelete;
56
+ for (const run of row) {
57
+ const { start: start, end, value: segmentIndex } = run;
58
+ if (segmentIndex === 0) {
59
+ indicesToDelete ||= [];
60
+ indicesToDelete.push(row.indexOf(run));
61
+ continue;
62
+ }
63
+ dirtyX = Math.min(dirtyX, start);
64
+ dirtyX2 = Math.max(dirtyX2, end);
65
+ const rgb = this.canvasProperties
66
+ .getColor(segmentIndex)
67
+ .map((v) => v * 255);
68
+ let startOffset = baseOffset + (start << 2);
69
+
70
+ for (let i = start; i < end; i++) {
71
+ imageArray[startOffset++] = rgb[0];
72
+ imageArray[startOffset++] = rgb[1];
73
+ imageArray[startOffset++] = rgb[2];
74
+ imageArray[startOffset++] = rgb[3];
75
+ }
76
+ }
77
+ }
78
+
79
+ if (dirtyX > width) {
80
+ return;
81
+ }
82
+ const dirtyWidth = dirtyX2 - dirtyX;
83
+ const dirtyHeight = dirtyY2 - dirtyY;
84
+ localContext.putImageData(
85
+ imageData,
86
+ 0,
87
+ 0,
88
+ dirtyX,
89
+ dirtyY,
90
+ dirtyWidth,
91
+ dirtyHeight
92
+ );
93
+ context.drawImage(
94
+ canvas,
95
+ dirtyX,
96
+ dirtyY,
97
+ dirtyWidth,
98
+ dirtyHeight,
99
+ dirtyX,
100
+ dirtyY,
101
+ dirtyWidth,
102
+ dirtyHeight
103
+ );
104
+ }
105
+
106
+ public render(viewport: IViewport, context: CanvasRenderingContext2D): void {
107
+ if (!this.visibility) {
108
+ return;
109
+ }
110
+ const image = this.image || this.getImage();
111
+
112
+ const { width, height } = image;
113
+
114
+ const data = image.getScalarData();
115
+ if (!data) {
116
+ return;
117
+ }
118
+ const { voxelManager } = image;
119
+ if (voxelManager) {
120
+ if (voxelManager.map.getRun) {
121
+ return this.renderRLE(viewport, context, voxelManager);
122
+ }
123
+ }
124
+ let { canvas } = this;
125
+ if (!canvas || canvas.width !== width || canvas.height !== height) {
126
+ this.canvas = canvas = new window.OffscreenCanvas(width, height);
127
+ }
128
+ const localContext = canvas.getContext('2d');
129
+ const imageData = localContext.createImageData(width, height);
130
+ const { data: imageArray } = imageData;
131
+ let offset = 0;
132
+ let destOffset = 0;
133
+ let dirtyX = Infinity;
134
+ let dirtyY = Infinity;
135
+ let dirtyX2 = -Infinity;
136
+ let dirtyY2 = -Infinity;
137
+ for (let y = 0; y < height; y++) {
138
+ for (let x = 0; x < width; x++) {
139
+ // const destOffset = (x + y * width) * 4;
140
+ const segmentIndex = data[offset++];
141
+ if (segmentIndex) {
142
+ dirtyX = Math.min(x, dirtyX);
143
+ dirtyY = Math.min(y, dirtyY);
144
+ dirtyX2 = Math.max(x, dirtyX2);
145
+ dirtyY2 = Math.max(y, dirtyY2);
146
+ const rgb = this.canvasProperties.getColor(segmentIndex);
147
+ imageArray[destOffset] = rgb[0] * 255;
148
+ imageArray[destOffset + 1] = rgb[1] * 255;
149
+ imageArray[destOffset + 2] = rgb[2] * 255;
150
+ imageArray[destOffset + 3] = 127;
151
+ // imageArray.fill(55, offset, offset + 4);
152
+ }
153
+ destOffset += 4;
154
+ }
155
+ }
156
+
157
+ if (dirtyX > width) {
158
+ return;
159
+ }
160
+ const dirtyWidth = dirtyX2 - dirtyX + 1;
161
+ const dirtyHeight = dirtyY2 - dirtyY + 1;
162
+ localContext.putImageData(
163
+ imageData,
164
+ 0,
165
+ 0,
166
+ dirtyX,
167
+ dirtyY,
168
+ dirtyWidth,
169
+ dirtyHeight
170
+ );
171
+ context.drawImage(
172
+ canvas,
173
+ dirtyX,
174
+ dirtyY,
175
+ dirtyWidth,
176
+ dirtyHeight,
177
+ dirtyX,
178
+ dirtyY,
179
+ dirtyWidth,
180
+ dirtyHeight
181
+ );
182
+ }
183
+
184
+ public getClassName() {
185
+ return this.className;
186
+ }
187
+
188
+ public getProperty() {
189
+ return this.canvasProperties;
190
+ }
191
+
192
+ public setVisibility(visibility: boolean) {
193
+ this.visibility = visibility;
194
+ }
195
+
196
+ public getMapper() {
197
+ return this.mapper;
198
+ }
199
+
200
+ public isA(actorType) {
201
+ return actorType === this.className;
202
+ }
203
+
204
+ public getImage() {
205
+ if (this.image) {
206
+ return this.image;
207
+ }
208
+ this.image = { ...this.derivedImage };
209
+ const imageData = this.viewport.getImageData();
210
+ Object.assign(this.image, {
211
+ worldToIndex: (worldPos) => imageData.imageData.worldToIndex(worldPos),
212
+ indexToWorld: (index) => imageData.imageData.indexToWorld(index),
213
+ getDimensions: () => imageData.dimensions,
214
+ getScalarData: () => this.derivedImage?.getPixelData(),
215
+ getDirection: () => imageData.direction,
216
+ getSpacing: () => imageData.spacing,
217
+ setOrigin: () => null,
218
+ /**
219
+ * Stores the new image cache data to update the image object, and sets
220
+ * the image instance (this object) to null so that the next getImage
221
+ * refreshes the display.
222
+ */
223
+ setDerivedImage: (image) => {
224
+ this.derivedImage = image;
225
+ this.image = null;
226
+ },
227
+ modified: () => null,
228
+ });
229
+ return this.image;
230
+ }
231
+ }
@@ -1,7 +1,7 @@
1
1
  import { vec3 } from 'gl-matrix';
2
2
  import {
3
3
  Events as EVENTS,
4
- VideoViewport as VideoViewportEnum,
4
+ VideoEnums as VideoViewportEnum,
5
5
  MetadataModules,
6
6
  } from '../enums';
7
7
  import type {
@@ -13,6 +13,8 @@ import type {
13
13
  InternalVideoCamera,
14
14
  VideoViewportInput,
15
15
  VOIRange,
16
+ ICanvasActor,
17
+ IImage,
16
18
  TargetSpecifier,
17
19
  } from '../types';
18
20
  import * as metaData from '../metaData';
@@ -20,6 +22,16 @@ import { Transform } from './helpers/cpuFallback/rendering/transform';
20
22
  import { triggerEvent } from '../utilities';
21
23
  import Viewport from './Viewport';
22
24
  import { getOrCreateCanvas } from './helpers';
25
+ import CanvasActor from './CanvasActor';
26
+ import cache from '../cache';
27
+
28
+ /**
29
+ * A data type for the scalar data for video data.
30
+ */
31
+ export type CanvasScalarData = Uint8ClampedArray & {
32
+ frameNumber?: number;
33
+ getRange?: () => [number, number];
34
+ };
23
35
 
24
36
  /**
25
37
  * An object representing a single stack viewport, which is a camera
@@ -43,6 +55,8 @@ class VideoViewport extends Viewport implements IVideoViewport {
43
55
  private isPlaying = false;
44
56
  private scrollSpeed = 1;
45
57
  private playbackRate = 1;
58
+ private scalarData: CanvasScalarData;
59
+
46
60
  /**
47
61
  * The range is the set of frames to play
48
62
  */
@@ -132,11 +146,9 @@ class VideoViewport extends Viewport implements IVideoViewport {
132
146
  this.videoElement.remove();
133
147
  }
134
148
 
135
- private _getImageDataMetadata() {
136
- const imagePlaneModule = metaData.get(
137
- MetadataModules.IMAGE_PLANE,
138
- this.imageId
139
- );
149
+ public getImageDataMetadata(image: IImage | string) {
150
+ const imageId = typeof image === 'string' ? image : image.imageId;
151
+ const imagePlaneModule = metaData.get(MetadataModules.IMAGE_PLANE, imageId);
140
152
 
141
153
  let rowCosines = <Point3>imagePlaneModule.rowCosines;
142
154
  let columnCosines = <Point3>imagePlaneModule.columnCosines;
@@ -159,8 +171,6 @@ class VideoViewport extends Viewport implements IVideoViewport {
159
171
  );
160
172
 
161
173
  const { rows, columns } = imagePlaneModule;
162
- this.videoWidth = columns;
163
- this.videoHeight = rows;
164
174
  const scanAxisNormal = vec3.create();
165
175
  vec3.cross(scanAxisNormal, rowCosineVec, colCosineVec);
166
176
 
@@ -183,6 +193,8 @@ class VideoViewport extends Viewport implements IVideoViewport {
183
193
  bitsAllocated: 8,
184
194
  numComps: 3,
185
195
  origin,
196
+ rows,
197
+ columns,
186
198
  direction: [...rowCosineVec, ...colCosineVec, ...scanAxisNormal],
187
199
  dimensions: [xVoxels, yVoxels, zVoxels],
188
200
  spacing: [xSpacing, ySpacing, zSpacing],
@@ -202,7 +214,7 @@ class VideoViewport extends Viewport implements IVideoViewport {
202
214
  const { rendered } = metaData.get(MetadataModules.IMAGE_URL, imageId);
203
215
  const generalSeries = metaData.get(MetadataModules.GENERAL_SERIES, imageId);
204
216
  this.modality = generalSeries?.Modality;
205
- this.metadata = this._getImageDataMetadata();
217
+ this.metadata = this.getImageDataMetadata(imageId);
206
218
 
207
219
  return this.setVideoURL(rendered).then(() => {
208
220
  let { cineRate, numberOfFrames } = metaData.get(
@@ -224,11 +236,14 @@ class VideoViewport extends Viewport implements IVideoViewport {
224
236
  this.play();
225
237
  // This is ugly, but without it, the video often fails to render initially
226
238
  // so having a play, followed by a pause fixes things.
227
- // 50 ms is a tested value that seems to work to prevent exceptions
228
- window.setTimeout(() => {
229
- this.pause();
230
- this.setFrameNumber(frameNumber || 1);
231
- }, 50);
239
+ // 100 ms is a tested value that seems to work to prevent exceptions
240
+ return new Promise((resolve) => {
241
+ window.setTimeout(() => {
242
+ this.pause();
243
+ this.setFrameNumber(frameNumber || 1);
244
+ resolve(this);
245
+ }, 100);
246
+ });
232
247
  });
233
248
  }
234
249
 
@@ -257,6 +272,19 @@ class VideoViewport extends Viewport implements IVideoViewport {
257
272
  });
258
273
  }
259
274
 
275
+ /**
276
+ * Gets all the image ids associated with this video element. This will
277
+ * have # of frames elements.
278
+ */
279
+ public getImageIds(): string[] {
280
+ const imageIds = new Array<string>(this.numberOfFrames);
281
+ const baseImageId = this.imageId.replace(/[0-9]+$/, '');
282
+ for (let i = 0; i < this.numberOfFrames; i++) {
283
+ imageIds[i] = `${baseImageId}${i + 1}`;
284
+ }
285
+ return imageIds;
286
+ }
287
+
260
288
  public togglePlayPause(): boolean {
261
289
  if (this.isPlaying) {
262
290
  this.pause();
@@ -454,7 +482,10 @@ class VideoViewport extends Viewport implements IVideoViewport {
454
482
  });
455
483
  }
456
484
 
457
- protected getScalarData() {
485
+ protected getScalarData(): CanvasScalarData {
486
+ if (this.scalarData?.frameNumber === this.getFrameNumber()) {
487
+ return this.scalarData;
488
+ }
458
489
  const canvas = document.createElement('canvas');
459
490
  canvas.width = this.videoWidth;
460
491
  canvas.height = this.videoHeight;
@@ -466,8 +497,10 @@ class VideoViewport extends Viewport implements IVideoViewport {
466
497
  this.videoWidth,
467
498
  this.videoHeight
468
499
  );
469
- const { data: scalarData } = canvasData;
470
- (scalarData as any).getRange = () => [0, 255];
500
+ const scalarData = canvasData.data as CanvasScalarData;
501
+ scalarData.getRange = () => [0, 255];
502
+ scalarData.frameNumber = this.getFrameNumber();
503
+ this.scalarData = scalarData;
471
504
  return scalarData;
472
505
  }
473
506
 
@@ -866,6 +899,34 @@ class VideoViewport extends Viewport implements IVideoViewport {
866
899
  );
867
900
  return transform;
868
901
  }
902
+
903
+ /**
904
+ * Nothing to do for the clipping planes for video as they don't exist.
905
+ */
906
+ public updateCameraClippingPlanesAndRange() {
907
+ // No-op
908
+ }
909
+
910
+ public addImages(stackInputs: Array<any>) {
911
+ const actors = this.getActors();
912
+ stackInputs.forEach((stackInput) => {
913
+ const image = cache.getImage(stackInput.imageId);
914
+
915
+ const imageActor = this.createActorMapper(image);
916
+ if (imageActor) {
917
+ actors.push({ uid: stackInput.actorUID, actor: imageActor });
918
+ if (stackInput.callback) {
919
+ stackInput.callback({ imageActor, imageId: stackInput.imageId });
920
+ }
921
+ }
922
+ });
923
+ this.setActors(actors);
924
+ }
925
+
926
+ protected createActorMapper(image) {
927
+ return new CanvasActor(this, image);
928
+ }
929
+
869
930
  private renderFrame = () => {
870
931
  const transform = this.getTransform();
871
932
  const transformationMatrix: number[] = transform.getMatrix();
@@ -887,9 +948,13 @@ class VideoViewport extends Viewport implements IVideoViewport {
887
948
  this.videoHeight
888
949
  );
889
950
 
951
+ for (const actor of this.getActors()) {
952
+ (actor.actor as ICanvasActor).render(this, this.canvasContext);
953
+ }
890
954
  this.canvasContext.resetTransform();
891
955
 
892
- triggerEvent(this.element, EVENTS.IMAGE_RENDERED, {
956
+ // This is stack new image to agree with stack/non-volume viewports
957
+ triggerEvent(this.element, EVENTS.STACK_NEW_IMAGE, {
893
958
  element: this.element,
894
959
  viewportId: this.id,
895
960
  viewport: this,
@@ -504,7 +504,7 @@ class Viewport implements IViewport {
504
504
  }
505
505
 
506
506
  const renderer = this.getRenderer();
507
- renderer.addActor(actor);
507
+ renderer?.addActor(actor);
508
508
  this._actors.set(actorUID, Object.assign({}, actorEntry));
509
509
 
510
510
  // when we add an actor we should update the camera clipping range and
@@ -516,7 +516,7 @@ class Viewport implements IViewport {
516
516
  * Remove all actors from the renderer
517
517
  */
518
518
  public removeAllActors(): void {
519
- this.getRenderer().removeAllViewProps();
519
+ this.getRenderer()?.removeAllViewProps();
520
520
  this._actors = new Map();
521
521
  return;
522
522
  }
@@ -33,9 +33,9 @@ async function addImageSlicesToViewports(
33
33
  }
34
34
 
35
35
  // if not instance of BaseVolumeViewport, throw
36
- if (!(viewport instanceof StackViewport)) {
36
+ if (!(viewport as IStackViewport).addImages) {
37
37
  console.warn(
38
- `Viewport with Id ${viewportId} is not a StackViewport. Cannot add image segmentation to this viewport.`
38
+ `Viewport with Id ${viewportId} does not have addImages. Cannot add image segmentation to this viewport.`
39
39
  );
40
40
 
41
41
  return;
@@ -4,7 +4,7 @@ import HalfFloat from '@kitware/vtk.js/Common/Core/HalfFloat';
4
4
  import { getConfiguration } from '../../init';
5
5
 
6
6
  /**
7
- * vtkStreamingOpenGLTexture - A dervied class of the core vtkOpenGLTexture.
7
+ * vtkStreamingOpenGLTexture - A derived class of the core vtkOpenGLTexture.
8
8
  * This class has methods to update the texture memory on the GPU slice by slice
9
9
  * in an efficient yet GPU-architecture friendly manner.
10
10
  *
@@ -12,7 +12,7 @@ import DynamicOperatorType from './DynamicOperatorType';
12
12
  import CalibrationTypes from './CalibrationTypes';
13
13
  import ViewportStatus from './ViewportStatus';
14
14
  import ImageQualityStatus from './ImageQualityStatus';
15
- import * as VideoViewport from './VideoViewport';
15
+ import * as VideoEnums from './VideoEnums';
16
16
  import MetadataModules from './MetadataModules';
17
17
 
18
18
  export {
@@ -29,7 +29,7 @@ export {
29
29
  VOILUTFunctionType,
30
30
  DynamicOperatorType,
31
31
  ViewportStatus,
32
- VideoViewport,
32
+ VideoEnums,
33
33
  MetadataModules,
34
34
  ImageQualityStatus,
35
35
  };
@@ -28,24 +28,37 @@ export interface ImageLoaderOptions {
28
28
  additionalDetails?: Record<string, unknown>;
29
29
  }
30
30
 
31
- interface DerivedImageOptions {
32
- imageId?: string;
33
- targetBufferType?: PixelDataTypedArrayString;
34
- }
35
-
36
31
  interface DerivedImages {
37
32
  imageIds: Array<string>;
38
33
  promises: Array<Promise<IImage>>;
39
34
  }
40
35
 
41
- interface LocalImageOptions {
36
+ type LocalImageOptions = {
42
37
  scalarData?: PixelDataTypedArray;
43
38
  targetBufferType?: PixelDataTypedArrayString;
44
39
  dimensions?: Point2;
45
40
  spacing?: Point3;
46
41
  origin?: Point3;
47
42
  direction?: Mat3;
48
- }
43
+ /**
44
+ * Skip creation of the actual buffer object.
45
+ * In fact, this creates a very short buffer, as there are lots of places
46
+ * assuming a buffer exists.
47
+ * This can be used when there are alternative representations of the image data.
48
+ */
49
+ skipCreateBuffer?: boolean;
50
+ /**
51
+ * A method to call to update the image object when it gets added to the cache.
52
+ * This can be used to create alternative representations of the image data,
53
+ * such as a VoxelManager.
54
+ */
55
+ onCacheAdd?: (image: IImage) => void;
56
+ };
57
+
58
+ type DerivedImageOptions = LocalImageOptions & {
59
+ imageId?: string;
60
+ targetBufferType?: PixelDataTypedArrayString;
61
+ };
49
62
 
50
63
  /**
51
64
  * This module deals with ImageLoaders, loading images and caching images
@@ -248,6 +261,8 @@ export function createAndCacheDerivedImage(
248
261
  options.imageId = `derived:${uuidv4()}`;
249
262
  }
250
263
 
264
+ const { imageId, skipCreateBuffer, onCacheAdd } = options;
265
+
251
266
  const imagePlaneModule = metaData.get('imagePlaneModule', referencedImageId);
252
267
 
253
268
  const length = imagePlaneModule.rows * imagePlaneModule.columns;
@@ -257,8 +272,11 @@ export function createAndCacheDerivedImage(
257
272
  length
258
273
  );
259
274
 
260
- const imageScalarData = new TypedArrayConstructor(length);
261
- const derivedImageId = options.imageId;
275
+ // Use a buffer of size 1 for no data
276
+ const imageScalarData = new TypedArrayConstructor(
277
+ skipCreateBuffer ? 1 : length
278
+ );
279
+ const derivedImageId = imageId;
262
280
 
263
281
  ['imagePixelModule', 'imagePlaneModule', 'generalSeriesModule'].forEach(
264
282
  (type) => {
@@ -270,8 +288,8 @@ export function createAndCacheDerivedImage(
270
288
  );
271
289
 
272
290
  const localImage = createAndCacheLocalImage(
273
- { scalarData: imageScalarData },
274
- options.imageId,
291
+ { scalarData: imageScalarData, onCacheAdd, skipCreateBuffer },
292
+ imageId,
275
293
  true
276
294
  );
277
295
 
@@ -292,10 +310,11 @@ export function createAndCacheDerivedImage(
292
310
  * @param options
293
311
  * @param options.getDerivedImageId - function to get the derived imageId
294
312
  * @param options.targetBufferType - target buffer type
313
+ * @param options.skipBufferCreate - avoid creating the buffer
295
314
  */
296
315
  export function createAndCacheDerivedImages(
297
316
  referencedImageIds: Array<string>,
298
- options: {
317
+ options: DerivedImageOptions & {
299
318
  getDerivedImageId?: (referencedImageId: string) => string;
300
319
  targetBufferType?: PixelDataTypedArrayString;
301
320
  } = {}
@@ -309,9 +328,8 @@ export function createAndCacheDerivedImages(
309
328
  const derivedImageIds = [];
310
329
  const allPromises = referencedImageIds.map((referencedImageId) => {
311
330
  const newOptions: DerivedImageOptions = {
312
- imageId: options.getDerivedImageId
313
- ? options.getDerivedImageId(referencedImageId)
314
- : `derived:${uuidv4()}`,
331
+ imageId:
332
+ options.getDerivedImageId?.(referencedImageId) || `derived:${uuidv4()}`,
315
333
  ...options,
316
334
  };
317
335
  derivedImageIds.push(newOptions.imageId);
@@ -370,7 +388,7 @@ export function createAndCacheLocalImage(
370
388
 
371
389
  image.sizeInBytes = imageScalarData.byteLength;
372
390
  image.getPixelData = () => imageScalarData;
373
- } else {
391
+ } else if (options.skipCreateBuffer !== true) {
374
392
  const { numBytes, TypedArrayConstructor } = getBufferConfiguration(
375
393
  options.targetBufferType,
376
394
  length
@@ -382,6 +400,11 @@ export function createAndCacheLocalImage(
382
400
  image.getPixelData = () => imageScalarData;
383
401
  }
384
402
 
403
+ // The onCacheAdd may modify the size in bytes for this image, which is ok,
404
+ // as this is used after resolution for cache storage. It may also do
405
+ // thinks like adding alternative representations such as VoxelManager
406
+ options.onCacheAdd?.(image);
407
+
385
408
  const imageLoadObject = {
386
409
  promise: Promise.resolve(image),
387
410
  };