@cornerstonejs/core 1.25.0 → 1.26.1
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/BaseVolumeViewport.d.ts +1 -1
- package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
- 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/VolumeViewport.js +6 -0
- package/dist/cjs/RenderingEngine/VolumeViewport.js.map +1 -1
- 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/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js +10 -2
- package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
- package/dist/esm/RenderingEngine/BaseVolumeViewport.d.ts +1 -1
- package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -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/VolumeViewport.js +6 -0
- package/dist/esm/RenderingEngine/VolumeViewport.js.map +1 -1
- 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/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +10 -2
- package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +2 -2
- package/src/RenderingEngine/BaseVolumeViewport.ts +2 -5
- package/src/RenderingEngine/RenderingEngine.ts +38 -21
- package/src/RenderingEngine/VideoViewport.ts +523 -0
- package/src/RenderingEngine/VolumeViewport.ts +8 -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/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +25 -5
|
@@ -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;
|
|
@@ -302,6 +302,7 @@ class VolumeViewport extends BaseVolumeViewport {
|
|
|
302
302
|
const currentCamera = this.getCamera();
|
|
303
303
|
this.updateClippingPlanesForActors(currentCamera);
|
|
304
304
|
this.triggerCameraModifiedEventIfNecessary(currentCamera, currentCamera);
|
|
305
|
+
this.viewportProperties.slabThickness = slabThickness;
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
/**
|
|
@@ -387,6 +388,13 @@ class VolumeViewport extends BaseVolumeViewport {
|
|
|
387
388
|
throw new Error(`No actor found for the given volumeId: ${volumeId}`);
|
|
388
389
|
}
|
|
389
390
|
|
|
391
|
+
// if a custom slabThickness was set, we need to reset it
|
|
392
|
+
if (volumeActor.slabThickness) {
|
|
393
|
+
volumeActor.slabThickness = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
|
|
394
|
+
this.viewportProperties.slabThickness = undefined;
|
|
395
|
+
this.updateClippingPlanesForActors(this.getCamera());
|
|
396
|
+
}
|
|
397
|
+
|
|
390
398
|
const imageVolume = cache.getVolume(volumeActor.uid);
|
|
391
399
|
if (!imageVolume) {
|
|
392
400
|
throw new Error(
|
|
@@ -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;
|
package/src/enums/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import VOILUTFunctionType from './VOILUTFunctionType';
|
|
|
11
11
|
import DynamicOperatorType from './DynamicOperatorType';
|
|
12
12
|
import CalibrationTypes from './CalibrationTypes';
|
|
13
13
|
import ViewportStatus from './ViewportStatus';
|
|
14
|
+
import * as VideoViewport from './VideoViewport';
|
|
14
15
|
|
|
15
16
|
export {
|
|
16
17
|
Events,
|
|
@@ -26,4 +27,5 @@ export {
|
|
|
26
27
|
VOILUTFunctionType,
|
|
27
28
|
DynamicOperatorType,
|
|
28
29
|
ViewportStatus,
|
|
30
|
+
VideoViewport,
|
|
29
31
|
};
|
package/src/getEnabledElement.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import getRenderingEngine, {
|
|
2
2
|
getRenderingEngines,
|
|
3
3
|
} from './RenderingEngine/getRenderingEngine';
|
|
4
|
-
import { IEnabledElement } from './types';
|
|
4
|
+
import { IEnabledElement, IStackViewport, IVolumeViewport } from './types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A convenience method to find an EnabledElement given a reference to its
|
|
@@ -67,7 +67,9 @@ export function getEnabledElementByIds(
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const viewport = renderingEngine.getViewport(viewportId)
|
|
70
|
+
const viewport = renderingEngine.getViewport(viewportId) as
|
|
71
|
+
| IStackViewport
|
|
72
|
+
| IVolumeViewport;
|
|
71
73
|
|
|
72
74
|
if (!viewport) {
|
|
73
75
|
return;
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import VolumeViewport from './RenderingEngine/VolumeViewport';
|
|
|
12
12
|
import VolumeViewport3D from './RenderingEngine/VolumeViewport3D';
|
|
13
13
|
import BaseVolumeViewport from './RenderingEngine/BaseVolumeViewport';
|
|
14
14
|
import StackViewport from './RenderingEngine/StackViewport';
|
|
15
|
+
import VideoViewport from './RenderingEngine/VideoViewport';
|
|
15
16
|
import Viewport from './RenderingEngine/Viewport';
|
|
16
17
|
import eventTarget from './eventTarget';
|
|
17
18
|
import {
|
|
@@ -80,6 +81,7 @@ export {
|
|
|
80
81
|
VolumeViewport3D,
|
|
81
82
|
Viewport,
|
|
82
83
|
StackViewport,
|
|
84
|
+
VideoViewport,
|
|
83
85
|
RenderingEngine,
|
|
84
86
|
ImageVolume,
|
|
85
87
|
// Helpers
|
|
@@ -6,7 +6,10 @@ import type IVolumeViewport from './IVolumeViewport';
|
|
|
6
6
|
* Cornerstone Enabled Element interface
|
|
7
7
|
*/
|
|
8
8
|
interface IEnabledElement {
|
|
9
|
-
/** Cornerstone Viewport instance - can be Stack or Volume Viewport as of now
|
|
9
|
+
/** Cornerstone Viewport instance - can be Stack or Volume, or Video Viewport as of now.
|
|
10
|
+
* For the moment, need to cast to unknown first before casting to IVideoViewport
|
|
11
|
+
* (TODO) - this will be done as part of adding annotation tools for video
|
|
12
|
+
*/
|
|
10
13
|
viewport: IStackViewport | IVolumeViewport;
|
|
11
14
|
/** Cornerstone Rendering Engine instance */
|
|
12
15
|
renderingEngine: IRenderingEngine;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import IStackViewport from './IStackViewport';
|
|
2
2
|
import { PublicViewportInput } from './IViewport';
|
|
3
3
|
import IVolumeViewport from './IVolumeViewport';
|
|
4
|
+
import { IViewport } from './IViewport';
|
|
5
|
+
import IVideoViewport from './IVideoViewport';
|
|
4
6
|
|
|
5
7
|
export default interface IRenderingEngine {
|
|
6
8
|
id: string;
|
|
@@ -9,8 +11,8 @@ export default interface IRenderingEngine {
|
|
|
9
11
|
offScreenCanvasContainer: any;
|
|
10
12
|
setViewports(viewports: Array<PublicViewportInput>): void;
|
|
11
13
|
resize(immediate?: boolean, keepCamera?: boolean): void;
|
|
12
|
-
getViewport(id: string):
|
|
13
|
-
getViewports(): Array<
|
|
14
|
+
getViewport(id: string): IViewport;
|
|
15
|
+
getViewports(): Array<IViewport>;
|
|
14
16
|
render(): void;
|
|
15
17
|
renderViewports(viewportIds: Array<string>): void;
|
|
16
18
|
renderViewport(viewportId: string): void;
|
|
@@ -23,6 +25,7 @@ export default interface IRenderingEngine {
|
|
|
23
25
|
disableElement(viewportId: string): void;
|
|
24
26
|
getStackViewports(): Array<IStackViewport>;
|
|
25
27
|
getVolumeViewports(): Array<IVolumeViewport>;
|
|
28
|
+
getVideoViewports(): Array<IVideoViewport>;
|
|
26
29
|
destroy(): void;
|
|
27
30
|
_debugRender(): void;
|
|
28
31
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IViewport } from './IViewport';
|
|
2
|
+
import VideoViewportProperties from './VideoViewportProperties';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for Stack Viewport
|
|
6
|
+
*/
|
|
7
|
+
export default interface IVideoViewport extends IViewport {
|
|
8
|
+
/**
|
|
9
|
+
* Resizes the viewport - only used in CPU fallback for StackViewport. The
|
|
10
|
+
* GPU resizing happens inside the RenderingEngine.
|
|
11
|
+
*/
|
|
12
|
+
resize: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Sets the properties for the viewport on the default actor.
|
|
15
|
+
*/
|
|
16
|
+
setProperties(props: VideoViewportProperties, suppressEvents?: boolean): void;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve the viewport properties
|
|
19
|
+
*/
|
|
20
|
+
getProperties: () => VideoViewportProperties;
|
|
21
|
+
|
|
22
|
+
setVideoURL: (url: string) => void;
|
|
23
|
+
|
|
24
|
+
play: () => void;
|
|
25
|
+
|
|
26
|
+
pause: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Reset the viewport properties to the default values
|
|
29
|
+
*/
|
|
30
|
+
resetProperties(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Centers Pan and resets the zoom for stack viewport.
|
|
33
|
+
*/
|
|
34
|
+
resetCamera(resetPan?: boolean, resetZoom?: boolean): boolean;
|
|
35
|
+
}
|