@cornerstonejs/core 1.25.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.
- package/dist/cjs/RenderingEngine/RenderingEngine.d.ts +5 -2
- package/dist/cjs/RenderingEngine/RenderingEngine.js +13 -5
- package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/cjs/RenderingEngine/VideoViewport.d.ts +51 -0
- package/dist/cjs/RenderingEngine/VideoViewport.js +355 -0
- package/dist/cjs/RenderingEngine/VideoViewport.js.map +1 -0
- package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.d.ts +2 -0
- package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.js +2 -0
- package/dist/cjs/RenderingEngine/helpers/viewportTypeToViewportClass.js.map +1 -1
- package/dist/cjs/enums/VideoViewport.d.ts +5 -0
- package/dist/cjs/enums/VideoViewport.js +10 -0
- package/dist/cjs/enums/VideoViewport.js.map +1 -0
- package/dist/cjs/enums/ViewportType.d.ts +2 -1
- package/dist/cjs/enums/ViewportType.js +1 -0
- package/dist/cjs/enums/ViewportType.js.map +1 -1
- package/dist/cjs/enums/index.d.ts +2 -1
- package/dist/cjs/enums/index.js +26 -1
- package/dist/cjs/enums/index.js.map +1 -1
- package/dist/cjs/getEnabledElement.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/IRenderingEngine.d.ts +5 -2
- package/dist/cjs/types/IVideoViewport.d.ts +12 -0
- package/dist/cjs/types/IVideoViewport.js +3 -0
- package/dist/cjs/types/IVideoViewport.js.map +1 -0
- package/dist/cjs/types/VideoViewportProperties.d.ts +10 -0
- package/dist/cjs/types/VideoViewportProperties.js +3 -0
- package/dist/cjs/types/VideoViewportProperties.js.map +1 -0
- package/dist/cjs/types/VideoViewportTypes.d.ts +18 -0
- package/dist/cjs/types/VideoViewportTypes.js +3 -0
- package/dist/cjs/types/VideoViewportTypes.js.map +1 -0
- package/dist/cjs/types/index.d.ts +4 -1
- package/dist/esm/RenderingEngine/RenderingEngine.d.ts +5 -2
- package/dist/esm/RenderingEngine/RenderingEngine.js +13 -5
- package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/esm/RenderingEngine/VideoViewport.d.ts +51 -0
- package/dist/esm/RenderingEngine/VideoViewport.js +330 -0
- package/dist/esm/RenderingEngine/VideoViewport.js.map +1 -0
- package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.d.ts +2 -0
- package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.js +2 -0
- package/dist/esm/RenderingEngine/helpers/viewportTypeToViewportClass.js.map +1 -1
- package/dist/esm/enums/VideoViewport.d.ts +5 -0
- package/dist/esm/enums/VideoViewport.js +7 -0
- package/dist/esm/enums/VideoViewport.js.map +1 -0
- package/dist/esm/enums/ViewportType.d.ts +2 -1
- package/dist/esm/enums/ViewportType.js +1 -0
- package/dist/esm/enums/ViewportType.js.map +1 -1
- package/dist/esm/enums/index.d.ts +2 -1
- package/dist/esm/enums/index.js +2 -1
- package/dist/esm/enums/index.js.map +1 -1
- package/dist/esm/getEnabledElement.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/IRenderingEngine.d.ts +5 -2
- package/dist/esm/types/IVideoViewport.d.ts +12 -0
- package/dist/esm/types/IVideoViewport.js +2 -0
- package/dist/esm/types/IVideoViewport.js.map +1 -0
- package/dist/esm/types/VideoViewportProperties.d.ts +10 -0
- package/dist/esm/types/VideoViewportProperties.js +2 -0
- package/dist/esm/types/VideoViewportProperties.js.map +1 -0
- package/dist/esm/types/VideoViewportTypes.d.ts +18 -0
- package/dist/esm/types/VideoViewportTypes.js +2 -0
- package/dist/esm/types/VideoViewportTypes.js.map +1 -0
- package/dist/esm/types/index.d.ts +4 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +2 -2
- package/src/RenderingEngine/RenderingEngine.ts +38 -21
- package/src/RenderingEngine/VideoViewport.ts +523 -0
- package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +2 -0
- package/src/enums/VideoViewport.ts +9 -0
- package/src/enums/ViewportType.ts +1 -0
- package/src/enums/index.ts +2 -0
- package/src/getEnabledElement.ts +4 -2
- package/src/index.ts +2 -0
- package/src/types/IEnabledElement.ts +4 -1
- package/src/types/IRenderingEngine.ts +5 -2
- package/src/types/IVideoViewport.ts +35 -0
- package/src/types/VideoViewportProperties.ts +17 -0
- package/src/types/VideoViewportTypes.ts +20 -0
- 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.
|
|
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": "
|
|
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
|
|
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,
|
|
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):
|
|
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<
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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<
|
|
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 |
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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;
|