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