@cornerstonejs/core 1.24.0 → 1.26.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 (83) hide show
  1. package/dist/cjs/RenderingEngine/RenderingEngine.d.ts +5 -2
  2. package/dist/cjs/RenderingEngine/RenderingEngine.js +13 -5
  3. package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
  4. package/dist/cjs/RenderingEngine/VideoViewport.d.ts +51 -0
  5. package/dist/cjs/RenderingEngine/VideoViewport.js +355 -0
  6. package/dist/cjs/RenderingEngine/VideoViewport.js.map +1 -0
  7. package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.d.ts +2 -0
  8. package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.js +2 -0
  9. package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.js.map +1 -1
  10. package/dist/cjs/enums/VideoViewport.d.ts +5 -0
  11. package/dist/cjs/enums/VideoViewport.js +10 -0
  12. package/dist/cjs/enums/VideoViewport.js.map +1 -0
  13. package/dist/cjs/enums/ViewportType.d.ts +2 -1
  14. package/dist/cjs/enums/ViewportType.js +1 -0
  15. package/dist/cjs/enums/ViewportType.js.map +1 -1
  16. package/dist/cjs/enums/index.d.ts +2 -1
  17. package/dist/cjs/enums/index.js +26 -1
  18. package/dist/cjs/enums/index.js.map +1 -1
  19. package/dist/cjs/getEnabledElement.js.map +1 -1
  20. package/dist/cjs/index.d.ts +2 -1
  21. package/dist/cjs/index.js +3 -1
  22. package/dist/cjs/index.js.map +1 -1
  23. package/dist/cjs/types/IRenderingEngine.d.ts +5 -2
  24. package/dist/cjs/types/IVideoViewport.d.ts +12 -0
  25. package/dist/cjs/types/IVideoViewport.js +3 -0
  26. package/dist/cjs/types/IVideoViewport.js.map +1 -0
  27. package/dist/cjs/types/VideoViewportProperties.d.ts +10 -0
  28. package/dist/cjs/types/VideoViewportProperties.js +3 -0
  29. package/dist/cjs/types/VideoViewportProperties.js.map +1 -0
  30. package/dist/cjs/types/VideoViewportTypes.d.ts +18 -0
  31. package/dist/cjs/types/VideoViewportTypes.js +3 -0
  32. package/dist/cjs/types/VideoViewportTypes.js.map +1 -0
  33. package/dist/cjs/types/index.d.ts +4 -1
  34. package/dist/esm/RenderingEngine/RenderingEngine.d.ts +5 -2
  35. package/dist/esm/RenderingEngine/RenderingEngine.js +13 -5
  36. package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
  37. package/dist/esm/RenderingEngine/VideoViewport.d.ts +51 -0
  38. package/dist/esm/RenderingEngine/VideoViewport.js +330 -0
  39. package/dist/esm/RenderingEngine/VideoViewport.js.map +1 -0
  40. package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.d.ts +2 -0
  41. package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.js +2 -0
  42. package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.js.map +1 -1
  43. package/dist/esm/enums/VideoViewport.d.ts +5 -0
  44. package/dist/esm/enums/VideoViewport.js +7 -0
  45. package/dist/esm/enums/VideoViewport.js.map +1 -0
  46. package/dist/esm/enums/ViewportType.d.ts +2 -1
  47. package/dist/esm/enums/ViewportType.js +1 -0
  48. package/dist/esm/enums/ViewportType.js.map +1 -1
  49. package/dist/esm/enums/index.d.ts +2 -1
  50. package/dist/esm/enums/index.js +2 -1
  51. package/dist/esm/enums/index.js.map +1 -1
  52. package/dist/esm/getEnabledElement.js.map +1 -1
  53. package/dist/esm/index.d.ts +2 -1
  54. package/dist/esm/index.js +2 -1
  55. package/dist/esm/index.js.map +1 -1
  56. package/dist/esm/types/IRenderingEngine.d.ts +5 -2
  57. package/dist/esm/types/IVideoViewport.d.ts +12 -0
  58. package/dist/esm/types/IVideoViewport.js +2 -0
  59. package/dist/esm/types/IVideoViewport.js.map +1 -0
  60. package/dist/esm/types/VideoViewportProperties.d.ts +10 -0
  61. package/dist/esm/types/VideoViewportProperties.js +2 -0
  62. package/dist/esm/types/VideoViewportProperties.js.map +1 -0
  63. package/dist/esm/types/VideoViewportTypes.d.ts +18 -0
  64. package/dist/esm/types/VideoViewportTypes.js +2 -0
  65. package/dist/esm/types/VideoViewportTypes.js.map +1 -0
  66. package/dist/esm/types/index.d.ts +4 -1
  67. package/dist/umd/index.js +1 -1
  68. package/dist/umd/index.js.map +1 -1
  69. package/package.json +2 -2
  70. package/src/RenderingEngine/RenderingEngine.ts +38 -21
  71. package/src/RenderingEngine/VideoViewport.ts +523 -0
  72. package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +2 -0
  73. package/src/enums/VideoViewport.ts +9 -0
  74. package/src/enums/ViewportType.ts +1 -0
  75. package/src/enums/index.ts +2 -0
  76. package/src/getEnabledElement.ts +4 -2
  77. package/src/index.ts +2 -0
  78. package/src/types/IEnabledElement.ts +4 -1
  79. package/src/types/IRenderingEngine.ts +5 -2
  80. package/src/types/IVideoViewport.ts +35 -0
  81. package/src/types/VideoViewportProperties.ts +17 -0
  82. package/src/types/VideoViewportTypes.ts +20 -0
  83. package/src/types/index.ts +11 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/esm/index.d.ts",
@@ -46,5 +46,5 @@
46
46
  "type": "individual",
47
47
  "url": "https://ohif.org/donate"
48
48
  },
49
- "gitHead": "7db76a5a136c78e1532f0abe90f4c2f9613ac68f"
49
+ "gitHead": "df9b69e21ab5dd9ccb33eaca7508abcb9f031e00"
50
50
  }
@@ -11,8 +11,13 @@ import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesC
11
11
  import getOrCreateCanvas from './helpers/getOrCreateCanvas';
12
12
  import { getShouldUseCPURendering, isCornerstoneInitialized } from '../init';
13
13
  import type IStackViewport from '../types/IStackViewport';
14
+ import type IVideoViewport from '../types/IVideoViewport';
14
15
  import type IRenderingEngine from '../types/IRenderingEngine';
15
16
  import type IVolumeViewport from '../types/IVolumeViewport';
17
+ import type { IViewport } from '../types/IViewport';
18
+ import VideoViewport from './VideoViewport';
19
+ import viewportTypeToViewportClass from './helpers/viewportTypeToViewportClass';
20
+
16
21
  import type * as EventTypes from '../types/EventTypes';
17
22
  import type {
18
23
  ViewportInput,
@@ -20,7 +25,7 @@ import type {
20
25
  InternalViewportInput,
21
26
  NormalizedViewportInput,
22
27
  } from '../types/IViewport';
23
- import { OrientationAxis, ViewportStatus } from '../enums';
28
+ import { OrientationAxis } from '../enums';
24
29
  import VolumeViewport3D from './VolumeViewport3D';
25
30
 
26
31
  type ViewportDisplayCoords = {
@@ -73,7 +78,7 @@ class RenderingEngine implements IRenderingEngine {
73
78
  public hasBeenDestroyed: boolean;
74
79
  public offscreenMultiRenderWindow: any;
75
80
  readonly offScreenCanvasContainer: any; // WebGL
76
- private _viewports: Map<string, IStackViewport | IVolumeViewport>;
81
+ private _viewports: Map<string, IViewport>;
77
82
  private _needsRender: Set<string> = new Set();
78
83
  private _animationFrameSet = false;
79
84
  private _animationFrameHandle: number | null = null;
@@ -346,7 +351,7 @@ class RenderingEngine implements IRenderingEngine {
346
351
  *
347
352
  * @returns viewport
348
353
  */
349
- public getViewport(viewportId: string): IStackViewport | IVolumeViewport {
354
+ public getViewport(viewportId: string): IViewport {
350
355
  return this._viewports.get(viewportId);
351
356
  }
352
357
 
@@ -355,7 +360,7 @@ class RenderingEngine implements IRenderingEngine {
355
360
  *
356
361
  * @returns Array of viewports
357
362
  */
358
- public getViewports(): Array<IStackViewport | IVolumeViewport> {
363
+ public getViewports(): Array<IViewport> {
359
364
  this._throwIfDestroyed();
360
365
 
361
366
  return this._getViewportsAsArray();
@@ -371,12 +376,30 @@ class RenderingEngine implements IRenderingEngine {
371
376
  const viewports = this.getViewports();
372
377
 
373
378
  const isStackViewport = (
374
- viewport: IStackViewport | IVolumeViewport
379
+ viewport: IViewport
375
380
  ): viewport is StackViewport => {
376
381
  return viewport instanceof StackViewport;
377
382
  };
378
383
 
379
- return viewports.filter(isStackViewport);
384
+ return viewports.filter(isStackViewport) as Array<IStackViewport>;
385
+ }
386
+
387
+ /**
388
+ * Filters all the available viewports and return the stack viewports
389
+ * @returns stack viewports registered on the rendering Engine
390
+ */
391
+ public getVideoViewports(): Array<IVideoViewport> {
392
+ this._throwIfDestroyed();
393
+
394
+ const viewports = this.getViewports();
395
+
396
+ const isVideoViewport = (
397
+ viewport: IViewport
398
+ ): viewport is VideoViewport => {
399
+ return viewport instanceof VideoViewport;
400
+ };
401
+
402
+ return viewports.filter(isVideoViewport) as Array<IVideoViewport>;
380
403
  }
381
404
 
382
405
  /**
@@ -389,7 +412,7 @@ class RenderingEngine implements IRenderingEngine {
389
412
  const viewports = this.getViewports();
390
413
 
391
414
  const isVolumeViewport = (
392
- viewport: IStackViewport | IVolumeViewport
415
+ viewport: IViewport
393
416
  ): viewport is BaseVolumeViewport => {
394
417
  return viewport instanceof BaseVolumeViewport;
395
418
  };
@@ -822,15 +845,9 @@ class RenderingEngine implements IRenderingEngine {
822
845
  };
823
846
 
824
847
  // 4. Create a proper viewport based on the type of the viewport
848
+ const ViewportType = viewportTypeToViewportClass[type];
825
849
 
826
- if (type !== ViewportType.STACK) {
827
- // In the future these will need to be pluggable, but we aren't there yet
828
- // and these are just Stacks for now.
829
- throw new Error('Support for fully custom viewports not yet implemented');
830
- }
831
-
832
- // 4.a Create stack viewport
833
- const viewport = new StackViewport(viewportInput);
850
+ const viewport = new ViewportType(viewportInput);
834
851
 
835
852
  // 5. Storing the viewports
836
853
  this._viewports.set(viewportId, viewport);
@@ -961,7 +978,7 @@ class RenderingEngine implements IRenderingEngine {
961
978
  * @returns _xOffset the final offset which will be used for the next viewport
962
979
  */
963
980
  private _resize(
964
- viewportsDrivenByVtkJs: Array<IStackViewport | IVolumeViewport>,
981
+ viewportsDrivenByVtkJs: Array<IViewport>,
965
982
  offScreenCanvasWidth: number,
966
983
  offScreenCanvasHeight: number
967
984
  ): number {
@@ -1018,7 +1035,7 @@ class RenderingEngine implements IRenderingEngine {
1018
1035
  * @param _xOffset - xOffSet to draw
1019
1036
  */
1020
1037
  private _getViewportCoordsOnOffScreenCanvas(
1021
- viewport: InternalViewportInput | IStackViewport | IVolumeViewport,
1038
+ viewport: InternalViewportInput | IViewport,
1022
1039
  offScreenCanvasWidth: number,
1023
1040
  offScreenCanvasHeight: number,
1024
1041
  _xOffset: number
@@ -1177,7 +1194,7 @@ class RenderingEngine implements IRenderingEngine {
1177
1194
  * @param viewport - The viewport to render
1178
1195
  */
1179
1196
  private renderViewportUsingCustomOrVtkPipeline(
1180
- viewport: IStackViewport | IVolumeViewport
1197
+ viewport: IViewport
1181
1198
  ): EventTypes.ImageRenderedEventDetail[] {
1182
1199
  let eventDetail;
1183
1200
 
@@ -1187,7 +1204,7 @@ class RenderingEngine implements IRenderingEngine {
1187
1204
  viewport.sWidth < VIEWPORT_MIN_SIZE ||
1188
1205
  viewport.sHeight < VIEWPORT_MIN_SIZE
1189
1206
  ) {
1190
- console.log('Viewport is too small', viewport.sWidth, viewport.sHeight);
1207
+ console.warn('Viewport is too small', viewport.sWidth, viewport.sHeight);
1191
1208
  return;
1192
1209
  }
1193
1210
  if (viewportTypeUsesCustomRenderingPipeline(viewport.type) === true) {
@@ -1221,7 +1238,7 @@ class RenderingEngine implements IRenderingEngine {
1221
1238
  * @param offScreenCanvas - The offscreen canvas to render from.
1222
1239
  */
1223
1240
  private _renderViewportFromVtkCanvasToOnscreenCanvas(
1224
- viewport: IStackViewport | IVolumeViewport,
1241
+ viewport: IViewport,
1225
1242
  offScreenCanvas
1226
1243
  ): EventTypes.ImageRenderedEventDetail {
1227
1244
  const {
@@ -1267,7 +1284,7 @@ class RenderingEngine implements IRenderingEngine {
1267
1284
  *
1268
1285
  * @param viewport - The `Viewport` to render.
1269
1286
  */
1270
- private _resetViewport(viewport: IStackViewport | IVolumeViewport) {
1287
+ private _resetViewport(viewport: IViewport) {
1271
1288
  const renderingEngineId = this.id;
1272
1289
 
1273
1290
  const { element, canvas, id: viewportId } = viewport;
@@ -0,0 +1,523 @@
1
+ import { Events as EVENTS, VideoViewport as VideoViewportEnum } from '../enums';
2
+ import {
3
+ IVideoViewport,
4
+ VideoViewportProperties,
5
+ Point3,
6
+ Point2,
7
+ ICamera,
8
+ InternalVideoCamera,
9
+ VideoViewportInput,
10
+ } from '../types';
11
+ import { Transform } from './helpers/cpuFallback/rendering/transform';
12
+ import { triggerEvent } from '../utilities';
13
+ import Viewport from './Viewport';
14
+ import { getOrCreateCanvas } from './helpers';
15
+
16
+ /**
17
+ * An object representing a single stack viewport, which is a camera
18
+ * looking into an internal scene, and an associated target output `canvas`.
19
+ */
20
+ class VideoViewport extends Viewport implements IVideoViewport {
21
+ public static readonly useCustomRenderingPipeline = true;
22
+
23
+ // Viewport Data
24
+ readonly uid;
25
+ readonly renderingEngineId: string;
26
+ readonly canvasContext: CanvasRenderingContext2D;
27
+ private videoElement?: HTMLVideoElement;
28
+ private videoWidth = 0;
29
+ private videoHeight = 0;
30
+
31
+ private loop = false;
32
+ private mute = true;
33
+ private isPlaying = false;
34
+ private scrollSpeed = 1;
35
+ private fps = 30; // TODO We need to find a good solution for this.
36
+ private videoCamera: InternalVideoCamera = {
37
+ panWorld: [0, 0],
38
+ parallelScale: 1,
39
+ };
40
+
41
+ constructor(props: VideoViewportInput) {
42
+ super({
43
+ ...props,
44
+ canvas: props.canvas || getOrCreateCanvas(props.element),
45
+ });
46
+ this.canvasContext = this.canvas.getContext('2d');
47
+ this.renderingEngineId = props.renderingEngineId;
48
+
49
+ this.element.setAttribute('data-viewport-uid', this.id);
50
+ this.element.setAttribute(
51
+ 'data-rendering-engine-uid',
52
+ this.renderingEngineId
53
+ );
54
+
55
+ this.videoElement = document.createElement('video');
56
+ this.videoElement.muted = this.mute;
57
+ this.videoElement.loop = this.loop;
58
+ this.videoElement.crossOrigin = 'anonymous';
59
+
60
+ this.addEventListeners();
61
+ this.resize();
62
+ }
63
+
64
+ private addEventListeners() {
65
+ this.canvas.addEventListener(
66
+ EVENTS.ELEMENT_DISABLED,
67
+ this.elementDisabledHandler
68
+ );
69
+ }
70
+
71
+ private removeEventListeners() {
72
+ this.canvas.removeEventListener(
73
+ EVENTS.ELEMENT_DISABLED,
74
+ this.elementDisabledHandler
75
+ );
76
+ }
77
+
78
+ private elementDisabledHandler() {
79
+ this.removeEventListeners();
80
+ this.videoElement.remove();
81
+ }
82
+
83
+ public async setVideoURL(videoURL: string) {
84
+ return new Promise((resolve) => {
85
+ this.videoElement.src = videoURL;
86
+ this.videoElement.preload = 'auto';
87
+
88
+ const loadedMetadataEventHandler = () => {
89
+ this.videoWidth = this.videoElement.videoWidth;
90
+ this.videoHeight = this.videoElement.videoHeight;
91
+ this.videoElement.removeEventListener(
92
+ 'loadedmetadata',
93
+ loadedMetadataEventHandler
94
+ );
95
+
96
+ this.refreshRenderValues();
97
+
98
+ resolve(true);
99
+ };
100
+
101
+ this.videoElement.addEventListener(
102
+ 'loadedmetadata',
103
+ loadedMetadataEventHandler
104
+ );
105
+ });
106
+ }
107
+
108
+ public togglePlayPause(): boolean {
109
+ if (this.isPlaying) {
110
+ this.pause();
111
+ return false;
112
+ } else {
113
+ this.play();
114
+ return true;
115
+ }
116
+ }
117
+
118
+ public play() {
119
+ if (!this.isPlaying) {
120
+ this.videoElement.play();
121
+ this.isPlaying = true;
122
+ this.renderWhilstPlaying();
123
+ }
124
+ }
125
+
126
+ public async pause() {
127
+ if (this.isPlaying) {
128
+ await this.videoElement.pause();
129
+ this.isPlaying = false;
130
+ }
131
+ }
132
+
133
+ public async scroll(delta = 1) {
134
+ await this.pause();
135
+
136
+ const videoElement = this.videoElement;
137
+ const renderFrame = this.renderFrame;
138
+
139
+ const currentTime = videoElement.currentTime;
140
+ const newTime = currentTime + (delta * this.scrollSpeed) / this.fps;
141
+
142
+ videoElement.currentTime = newTime;
143
+
144
+ // Need to wait for seek update
145
+ const seekEventListener = (evt) => {
146
+ renderFrame();
147
+
148
+ videoElement.removeEventListener('seeked', seekEventListener);
149
+ };
150
+
151
+ videoElement.addEventListener('seeked', seekEventListener);
152
+ }
153
+
154
+ public async start() {
155
+ const videoElement = this.videoElement;
156
+ const renderFrame = this.renderFrame;
157
+
158
+ videoElement.currentTime = 0;
159
+
160
+ if (videoElement.paused) {
161
+ // Need to wait for seek update
162
+ const seekEventListener = (evt) => {
163
+ console.log('seeked');
164
+
165
+ renderFrame();
166
+
167
+ videoElement.removeEventListener('seeked', seekEventListener);
168
+ };
169
+
170
+ videoElement.addEventListener('seeked', seekEventListener);
171
+ }
172
+ }
173
+
174
+ public async end() {
175
+ const videoElement = this.videoElement;
176
+ const renderFrame = this.renderFrame;
177
+
178
+ videoElement.currentTime = videoElement.duration;
179
+
180
+ if (videoElement.paused) {
181
+ // Need to wait for seek update
182
+ const seekEventListener = (evt) => {
183
+ renderFrame();
184
+
185
+ videoElement.removeEventListener('seeked', seekEventListener);
186
+ };
187
+
188
+ videoElement.addEventListener('seeked', seekEventListener);
189
+ }
190
+ }
191
+
192
+ public async setTime(timeInSeconds: number) {
193
+ const videoElement = this.videoElement;
194
+ const renderFrame = this.renderFrame;
195
+
196
+ videoElement.currentTime = timeInSeconds;
197
+
198
+ if (videoElement.paused) {
199
+ // Need to wait for seek update
200
+ const seekEventListener = (evt) => {
201
+ renderFrame();
202
+
203
+ videoElement.removeEventListener('seeked', seekEventListener);
204
+ };
205
+
206
+ videoElement.addEventListener('seeked', seekEventListener);
207
+ }
208
+ }
209
+
210
+ // Sets the frame number - note according to DICOM, this is 1 based
211
+ public async setFrame(frame: number) {
212
+ this.setTime((frame - 1) / this.fps);
213
+ }
214
+
215
+ public setProperties(videoInterface: VideoViewportProperties) {
216
+ if (videoInterface.loop !== undefined) {
217
+ this.videoElement.loop = videoInterface.loop;
218
+ }
219
+
220
+ if (videoInterface.muted !== undefined) {
221
+ this.videoElement.muted = videoInterface.muted;
222
+ }
223
+
224
+ if (videoInterface.playbackRate !== undefined) {
225
+ this.setPlaybackRate(videoInterface.playbackRate);
226
+ }
227
+ }
228
+
229
+ public setPlaybackRate(rate = 1) {
230
+ // Minimum playback speed in chrome is 0.0625 compared to normal
231
+ if (rate < 0.0625) {
232
+ this.pause();
233
+ return;
234
+ }
235
+ if (!this.videoElement) {
236
+ return;
237
+ }
238
+ this.videoElement.playbackRate = rate;
239
+ this.play();
240
+ }
241
+
242
+ public setScrollSpeed(
243
+ scrollSpeed = 1,
244
+ unit = VideoViewportEnum.SpeedUnit.FRAME
245
+ ) {
246
+ this.scrollSpeed =
247
+ unit === VideoViewportEnum.SpeedUnit.SECOND
248
+ ? scrollSpeed * this.fps
249
+ : scrollSpeed;
250
+ }
251
+
252
+ public getProperties = (): VideoViewportProperties => {
253
+ return {
254
+ loop: this.videoElement.loop,
255
+ muted: this.videoElement.muted,
256
+ };
257
+ };
258
+
259
+ public resetProperties() {
260
+ this.setProperties({
261
+ loop: false,
262
+ muted: true,
263
+ });
264
+ }
265
+
266
+ public getImageData() {
267
+ return null;
268
+ }
269
+
270
+ public setCamera(camera: ICamera): void {
271
+ const { parallelScale, focalPoint } = camera;
272
+
273
+ // NOTE: the parallel scale should be done first
274
+ // because it affects the focal point later
275
+ if (camera.parallelScale !== undefined) {
276
+ this.videoCamera.parallelScale = 1 / parallelScale;
277
+ }
278
+
279
+ if (focalPoint !== undefined) {
280
+ const focalPointCanvas = this.worldToCanvas(focalPoint);
281
+ const canvasCenter: Point2 = [
282
+ this.element.clientWidth / 2,
283
+ this.element.clientHeight / 2,
284
+ ];
285
+
286
+ const panWorldDelta: Point2 = [
287
+ (focalPointCanvas[0] - canvasCenter[0]) /
288
+ this.videoCamera.parallelScale,
289
+ (focalPointCanvas[1] - canvasCenter[1]) /
290
+ this.videoCamera.parallelScale,
291
+ ];
292
+
293
+ this.videoCamera.panWorld = [
294
+ this.videoCamera.panWorld[0] - panWorldDelta[0],
295
+ this.videoCamera.panWorld[1] - panWorldDelta[1],
296
+ ];
297
+ }
298
+
299
+ this.canvasContext.fillRect(0, 0, this.canvas.width, this.canvas.height);
300
+
301
+ if (this.isPlaying === false) {
302
+ this.renderFrame();
303
+ }
304
+ }
305
+
306
+ public getCamera(): ICamera {
307
+ const { parallelScale } = this.videoCamera;
308
+
309
+ const canvasCenter: Point2 = [
310
+ this.element.clientWidth / 2,
311
+ this.element.clientHeight / 2,
312
+ ];
313
+
314
+ // All other viewports have the focal point in canvas coordinates in the center
315
+ // of the canvas, so to make tools work the same, we need to do the same here
316
+ // and convert to the world coordinate system since focal point is in world coordinates.
317
+ const canvasCenterWorld = this.canvasToWorld(canvasCenter);
318
+
319
+ return {
320
+ parallelProjection: true,
321
+ focalPoint: canvasCenterWorld,
322
+ position: [0, 0, 0],
323
+ parallelScale: 1 / parallelScale, // Reverse zoom direction back
324
+ viewPlaneNormal: [0, 0, 1],
325
+ };
326
+ }
327
+
328
+ public resetCamera = (): boolean => {
329
+ this.refreshRenderValues();
330
+
331
+ this.canvasContext.fillRect(0, 0, this.canvas.width, this.canvas.height);
332
+
333
+ if (this.isPlaying === false) {
334
+ // If its not replaying, just re-render the frame on move.
335
+ this.renderFrame();
336
+ }
337
+ return true;
338
+ };
339
+
340
+ public getFrameOfReferenceUID = (): string => {
341
+ // The video itself is the frame of reference.
342
+ return this.videoElement.src;
343
+ };
344
+
345
+ public resize = (): void => {
346
+ const canvas = this.canvas;
347
+ const { clientWidth, clientHeight } = canvas;
348
+
349
+ // Set the canvas to be same resolution as the client.
350
+ if (canvas.width !== clientWidth || canvas.height !== clientHeight) {
351
+ canvas.width = clientWidth;
352
+ canvas.height = clientHeight;
353
+ }
354
+
355
+ this.refreshRenderValues();
356
+
357
+ if (this.isPlaying === false) {
358
+ // If its not playing, just re-render on resize.
359
+ this.renderFrame();
360
+ }
361
+ };
362
+
363
+ /**
364
+ * Converts a VideoViewport canvas coordinate to a video coordinate.
365
+ *
366
+ * @param canvasPos - to convert to world
367
+ * @returns World position
368
+ */
369
+ public canvasToWorld = (canvasPos: Point2): Point3 => {
370
+ const pan: Point2 = this.videoCamera.panWorld; // In world coordinates
371
+ const worldToCanvasRatio: number = this.getWorldToCanvasRatio();
372
+
373
+ const panOffsetCanvas: Point2 = [
374
+ pan[0] * worldToCanvasRatio,
375
+ pan[1] * worldToCanvasRatio,
376
+ ];
377
+
378
+ const subCanvasPos: Point2 = [
379
+ canvasPos[0] - panOffsetCanvas[0],
380
+ canvasPos[1] - panOffsetCanvas[1],
381
+ ];
382
+
383
+ const worldPos: Point3 = [
384
+ subCanvasPos[0] / worldToCanvasRatio,
385
+ subCanvasPos[1] / worldToCanvasRatio,
386
+ 0,
387
+ ];
388
+
389
+ return worldPos;
390
+ };
391
+
392
+ /**
393
+ * Converts and [x,y] video coordinate to a Cornerstone3D VideoViewport.
394
+ *
395
+ * @param worldPos - world coord to convert to canvas
396
+ * @returns Canvas position
397
+ */
398
+ public worldToCanvas = (worldPos: Point3): Point2 => {
399
+ const pan: Point2 = this.videoCamera.panWorld;
400
+ const worldToCanvasRatio: number = this.getWorldToCanvasRatio();
401
+
402
+ const subCanvasPos: Point2 = [
403
+ (worldPos[0] + pan[0]) * worldToCanvasRatio,
404
+ (worldPos[1] + pan[1]) * worldToCanvasRatio,
405
+ ];
406
+
407
+ const canvasPos: Point2 = [subCanvasPos[0], subCanvasPos[1]];
408
+
409
+ return canvasPos;
410
+ };
411
+
412
+ private refreshRenderValues() {
413
+ // this means that each unit (pixel) in the world (video) would be
414
+ // represented by n pixels in the canvas.
415
+ let worldToCanvasRatio = this.canvas.width / this.videoWidth;
416
+
417
+ if (this.videoHeight * worldToCanvasRatio > this.canvas.height) {
418
+ // If by fitting the width, we exceed the height of the viewport, then we need to decrease the
419
+ // size of the viewport further by considering its verticality.
420
+ const secondWorldToCanvasRatio =
421
+ this.canvas.height / (this.videoHeight * worldToCanvasRatio);
422
+
423
+ worldToCanvasRatio *= secondWorldToCanvasRatio;
424
+ }
425
+
426
+ // Set the width as big as possible, this is the portion of the canvas
427
+ // that the video will occupy.
428
+ const drawWidth = Math.floor(this.videoWidth * worldToCanvasRatio);
429
+ const drawHeight = Math.floor(this.videoHeight * worldToCanvasRatio);
430
+
431
+ // calculate x and y offset in order to center the image
432
+ const xOffsetCanvas = this.canvas.width / 2 - drawWidth / 2;
433
+ const yOffsetCanvas = this.canvas.height / 2 - drawHeight / 2;
434
+
435
+ const xOffsetWorld = xOffsetCanvas / worldToCanvasRatio;
436
+ const yOffsetWorld = yOffsetCanvas / worldToCanvasRatio;
437
+
438
+ this.videoCamera.panWorld = [xOffsetWorld, yOffsetWorld];
439
+ this.videoCamera.parallelScale = worldToCanvasRatio;
440
+ }
441
+
442
+ private getWorldToCanvasRatio() {
443
+ return this.videoCamera.parallelScale;
444
+ }
445
+
446
+ private getCanvasToWorldRatio() {
447
+ return 1.0 / this.videoCamera.parallelScale;
448
+ }
449
+
450
+ public customRenderViewportToCanvas = () => {
451
+ this.renderFrame();
452
+ };
453
+
454
+ private renderFrame = () => {
455
+ const panWorld: Point2 = this.videoCamera.panWorld;
456
+ const worldToCanvasRatio: number = this.getWorldToCanvasRatio();
457
+ const canvasToWorldRatio: number = this.getCanvasToWorldRatio();
458
+
459
+ const halfCanvas = [this.canvas.width / 2, this.canvas.height / 2];
460
+ const halfCanvasWorldCoordinates = [
461
+ halfCanvas[0] * canvasToWorldRatio,
462
+ halfCanvas[1] * canvasToWorldRatio,
463
+ ];
464
+
465
+ const transform = new Transform();
466
+
467
+ // Translate to the center of the canvas (move origin of the transform
468
+ // to the center of the canvas)
469
+ transform.translate(halfCanvas[0], halfCanvas[1]);
470
+
471
+ // Scale
472
+ transform.scale(worldToCanvasRatio, worldToCanvasRatio);
473
+
474
+ // Apply the translation
475
+ transform.translate(panWorld[0], panWorld[1]);
476
+
477
+ // Translate back
478
+ transform.translate(
479
+ -halfCanvasWorldCoordinates[0],
480
+ -halfCanvasWorldCoordinates[1]
481
+ );
482
+ const transformationMatrix: number[] = transform.getMatrix();
483
+
484
+ this.canvasContext.transform(
485
+ transformationMatrix[0],
486
+ transformationMatrix[1],
487
+ transformationMatrix[2],
488
+ transformationMatrix[3],
489
+ transformationMatrix[4],
490
+ transformationMatrix[5]
491
+ );
492
+
493
+ this.canvasContext.drawImage(
494
+ this.videoElement,
495
+ 0,
496
+ 0,
497
+ this.videoWidth,
498
+ this.videoHeight
499
+ );
500
+
501
+ this.canvasContext.resetTransform();
502
+
503
+ triggerEvent(this.element, EVENTS.IMAGE_RENDERED, {
504
+ element: this.element,
505
+ viewportId: this.id,
506
+ viewport: this,
507
+ renderingEngineId: this.renderingEngineId,
508
+ time: this.videoElement.currentTime,
509
+ duration: this.videoElement.duration,
510
+ });
511
+ };
512
+
513
+ private renderWhilstPlaying = () => {
514
+ this.renderFrame();
515
+
516
+ //wait approximately 16ms and run again
517
+ if (this.isPlaying) {
518
+ requestAnimationFrame(this.renderWhilstPlaying);
519
+ }
520
+ };
521
+ }
522
+
523
+ export default VideoViewport;
@@ -3,12 +3,14 @@ import StackViewport from '../StackViewport';
3
3
  import VolumeViewport from '../VolumeViewport';
4
4
  import ViewportType from '../../enums/ViewportType';
5
5
  import VolumeViewport3D from '../VolumeViewport3D';
6
+ import VideoViewport from '../VideoViewport';
6
7
 
7
8
  const viewportTypeToViewportClass = {
8
9
  [ViewportType.ORTHOGRAPHIC]: VolumeViewport,
9
10
  [ViewportType.PERSPECTIVE]: VolumeViewport,
10
11
  [ViewportType.STACK]: StackViewport,
11
12
  [ViewportType.VOLUME_3D]: VolumeViewport3D,
13
+ [ViewportType.VIDEO]: VideoViewport,
12
14
  };
13
15
 
14
16
  export default viewportTypeToViewportClass;