@cornerstonejs/core 1.41.0 → 1.42.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.js.map +1 -1
- package/dist/cjs/RenderingEngine/StackViewport.js +2 -1
- package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/helpers/createVolumeActor.js +2 -1
- package/dist/cjs/RenderingEngine/helpers/createVolumeActor.js.map +1 -1
- package/dist/cjs/cache/cache.d.ts +6 -3
- package/dist/cjs/cache/cache.js +43 -6
- package/dist/cjs/cache/cache.js.map +1 -1
- package/dist/cjs/cache/classes/ImageVolume.d.ts +25 -5
- package/dist/cjs/cache/classes/ImageVolume.js +302 -13
- package/dist/cjs/cache/classes/ImageVolume.js.map +1 -1
- package/dist/cjs/eventTarget.d.ts +1 -0
- package/dist/cjs/eventTarget.js +13 -1
- package/dist/cjs/eventTarget.js.map +1 -1
- package/dist/cjs/init.js +2 -0
- package/dist/cjs/init.js.map +1 -1
- package/dist/cjs/loaders/imageLoader.js +6 -2
- package/dist/cjs/loaders/imageLoader.js.map +1 -1
- package/dist/cjs/loaders/volumeLoader.d.ts +12 -9
- package/dist/cjs/loaders/volumeLoader.js +50 -5
- package/dist/cjs/loaders/volumeLoader.js.map +1 -1
- package/dist/cjs/types/Cornerstone3DConfig.d.ts +1 -0
- package/dist/cjs/types/IDynamicImageVolume.d.ts +2 -2
- package/dist/cjs/types/IImage.d.ts +5 -0
- package/dist/cjs/types/IImageVolume.d.ts +7 -2
- package/dist/cjs/types/ILoadObject.d.ts +2 -2
- package/dist/cjs/types/IVolume.d.ts +3 -26
- package/dist/cjs/types/ImageVolumeProps.d.ts +6 -0
- package/dist/cjs/types/ImageVolumeProps.js +3 -0
- package/dist/cjs/types/ImageVolumeProps.js.map +1 -0
- package/dist/cjs/types/VolumeProps.d.ts +27 -0
- package/dist/cjs/types/VolumeProps.js +3 -0
- package/dist/cjs/types/VolumeProps.js.map +1 -0
- package/dist/cjs/types/index.d.ts +4 -2
- package/dist/cjs/utilities/VoxelManager.d.ts +2 -2
- package/dist/cjs/utilities/cacheUtils.d.ts +2 -0
- package/dist/cjs/utilities/cacheUtils.js +96 -0
- package/dist/cjs/utilities/cacheUtils.js.map +1 -0
- package/dist/cjs/utilities/convertStackToVolumeViewport.d.ts +12 -0
- package/dist/cjs/utilities/convertStackToVolumeViewport.js +61 -0
- package/dist/cjs/utilities/convertStackToVolumeViewport.js.map +1 -0
- package/dist/cjs/utilities/convertVolumeToStackViewport.d.ts +9 -0
- package/dist/cjs/utilities/convertVolumeToStackViewport.js +95 -0
- package/dist/cjs/utilities/convertVolumeToStackViewport.js.map +1 -0
- package/dist/cjs/utilities/createSigmoidRGBTransferFunction.js +25 -2
- package/dist/cjs/utilities/createSigmoidRGBTransferFunction.js.map +1 -1
- package/dist/cjs/utilities/generateVolumePropsFromImageIds.d.ts +3 -0
- package/dist/cjs/utilities/generateVolumePropsFromImageIds.js +124 -0
- package/dist/cjs/utilities/generateVolumePropsFromImageIds.js.map +1 -0
- package/dist/cjs/utilities/index.d.ts +6 -1
- package/dist/cjs/utilities/index.js +12 -1
- package/dist/cjs/utilities/index.js.map +1 -1
- package/dist/cjs/utilities/roundNumber.d.ts +4 -0
- package/dist/cjs/utilities/roundNumber.js +36 -0
- package/dist/cjs/utilities/roundNumber.js.map +1 -0
- package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/StackViewport.js +2 -1
- package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/helpers/createVolumeActor.js +1 -1
- package/dist/esm/RenderingEngine/helpers/createVolumeActor.js.map +1 -1
- package/dist/esm/cache/cache.js +43 -6
- package/dist/esm/cache/cache.js.map +1 -1
- package/dist/esm/cache/classes/ImageVolume.js +279 -14
- package/dist/esm/cache/classes/ImageVolume.js.map +1 -1
- package/dist/esm/eventTarget.js +13 -1
- package/dist/esm/eventTarget.js.map +1 -1
- package/dist/esm/init.js +2 -0
- package/dist/esm/init.js.map +1 -1
- package/dist/esm/loaders/imageLoader.js +5 -2
- package/dist/esm/loaders/imageLoader.js.map +1 -1
- package/dist/esm/loaders/volumeLoader.js +50 -5
- package/dist/esm/loaders/volumeLoader.js.map +1 -1
- package/dist/esm/types/ImageVolumeProps.js +2 -0
- package/dist/esm/types/ImageVolumeProps.js.map +1 -0
- package/dist/esm/types/VolumeProps.js +2 -0
- package/dist/esm/types/VolumeProps.js.map +1 -0
- package/dist/esm/utilities/cacheUtils.js +65 -0
- package/dist/esm/utilities/cacheUtils.js.map +1 -0
- package/dist/esm/utilities/convertStackToVolumeViewport.js +49 -0
- package/dist/esm/utilities/convertStackToVolumeViewport.js.map +1 -0
- package/dist/esm/utilities/convertVolumeToStackViewport.js +58 -0
- package/dist/esm/utilities/convertVolumeToStackViewport.js.map +1 -0
- package/dist/esm/utilities/createSigmoidRGBTransferFunction.js +1 -1
- package/dist/esm/utilities/createSigmoidRGBTransferFunction.js.map +1 -1
- package/dist/esm/utilities/generateVolumePropsFromImageIds.js +118 -0
- package/dist/esm/utilities/generateVolumePropsFromImageIds.js.map +1 -0
- package/dist/esm/utilities/index.js +6 -1
- package/dist/esm/utilities/index.js.map +1 -1
- package/dist/esm/utilities/roundNumber.js +33 -0
- package/dist/esm/utilities/roundNumber.js.map +1 -0
- package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
- package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
- package/dist/types/cache/cache.d.ts +6 -3
- package/dist/types/cache/cache.d.ts.map +1 -1
- package/dist/types/cache/classes/ImageVolume.d.ts +25 -5
- package/dist/types/cache/classes/ImageVolume.d.ts.map +1 -1
- package/dist/types/eventTarget.d.ts +1 -0
- package/dist/types/eventTarget.d.ts.map +1 -1
- package/dist/types/loaders/imageLoader.d.ts.map +1 -1
- package/dist/types/loaders/volumeLoader.d.ts +12 -9
- package/dist/types/loaders/volumeLoader.d.ts.map +1 -1
- package/dist/types/types/Cornerstone3DConfig.d.ts +1 -0
- package/dist/types/types/Cornerstone3DConfig.d.ts.map +1 -1
- package/dist/types/types/IDynamicImageVolume.d.ts +2 -2
- package/dist/types/types/IDynamicImageVolume.d.ts.map +1 -1
- package/dist/types/types/IImage.d.ts +5 -0
- package/dist/types/types/IImage.d.ts.map +1 -1
- package/dist/types/types/IImageVolume.d.ts +7 -2
- package/dist/types/types/IImageVolume.d.ts.map +1 -1
- package/dist/types/types/ILoadObject.d.ts +2 -2
- package/dist/types/types/ILoadObject.d.ts.map +1 -1
- package/dist/types/types/IVolume.d.ts +3 -26
- package/dist/types/types/IVolume.d.ts.map +1 -1
- package/dist/types/types/ImageVolumeProps.d.ts +7 -0
- package/dist/types/types/ImageVolumeProps.d.ts.map +1 -0
- package/dist/types/types/VolumeProps.d.ts +28 -0
- package/dist/types/types/VolumeProps.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +4 -2
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/utilities/VoxelManager.d.ts +2 -2
- package/dist/types/utilities/VoxelManager.d.ts.map +1 -1
- package/dist/types/utilities/cacheUtils.d.ts +3 -0
- package/dist/types/utilities/cacheUtils.d.ts.map +1 -0
- package/dist/types/utilities/convertStackToVolumeViewport.d.ts +13 -0
- package/dist/types/utilities/convertStackToVolumeViewport.d.ts.map +1 -0
- package/dist/types/utilities/convertVolumeToStackViewport.d.ts +10 -0
- package/dist/types/utilities/convertVolumeToStackViewport.d.ts.map +1 -0
- package/dist/types/utilities/generateVolumePropsFromImageIds.d.ts +4 -0
- package/dist/types/utilities/generateVolumePropsFromImageIds.d.ts.map +1 -0
- package/dist/types/utilities/index.d.ts +6 -1
- package/dist/types/utilities/index.d.ts.map +1 -1
- package/dist/types/utilities/roundNumber.d.ts +5 -0
- package/dist/types/utilities/roundNumber.d.ts.map +1 -0
- 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 +0 -1
- package/src/RenderingEngine/StackViewport.ts +3 -1
- package/src/RenderingEngine/helpers/createVolumeActor.ts +1 -1
- package/src/cache/cache.ts +91 -7
- package/src/cache/classes/ImageVolume.ts +535 -21
- package/src/eventTarget.ts +19 -1
- package/src/init.ts +2 -2
- package/src/loaders/imageLoader.ts +6 -2
- package/src/loaders/volumeLoader.ts +118 -23
- package/src/types/Cornerstone3DConfig.ts +12 -0
- package/src/types/IDynamicImageVolume.ts +2 -2
- package/src/types/IImage.ts +6 -0
- package/src/types/IImageVolume.ts +14 -2
- package/src/types/ILoadObject.ts +2 -2
- package/src/types/IVolume.ts +4 -41
- package/src/types/ImageVolumeProps.ts +15 -0
- package/src/types/VolumeProps.ts +57 -0
- package/src/types/index.ts +5 -2
- package/src/utilities/VoxelManager.ts +2 -2
- package/src/utilities/cacheUtils.ts +121 -0
- package/src/utilities/convertStackToVolumeViewport.ts +115 -0
- package/src/utilities/convertVolumeToStackViewport.ts +125 -0
- package/src/utilities/createSigmoidRGBTransferFunction.ts +1 -1
- package/src/utilities/generateVolumePropsFromImageIds.ts +183 -0
- package/src/utilities/index.ts +11 -0
- package/src/utilities/roundNumber.ts +56 -0
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
2
|
+
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
|
|
1
3
|
import isTypedArray from '../../utilities/isTypedArray';
|
|
2
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
genericMetadataProvider,
|
|
6
|
+
getMinMax,
|
|
7
|
+
imageIdToURI,
|
|
8
|
+
} from '../../utilities';
|
|
3
9
|
import { vtkStreamingOpenGLTexture } from '../../RenderingEngine/vtkClasses';
|
|
4
|
-
import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
5
10
|
import {
|
|
6
|
-
IVolume,
|
|
7
|
-
VolumeScalarData,
|
|
8
11
|
Metadata,
|
|
9
12
|
Point3,
|
|
10
13
|
IImageVolume,
|
|
11
14
|
Mat3,
|
|
15
|
+
PixelDataTypedArray,
|
|
16
|
+
ImageVolumeProps,
|
|
17
|
+
IImage,
|
|
18
|
+
IImageLoadObject,
|
|
12
19
|
} from '../../types';
|
|
20
|
+
import cache from '../cache';
|
|
21
|
+
import * as metaData from '../../metaData';
|
|
13
22
|
|
|
14
23
|
/** The base class for volume data. It includes the volume metadata
|
|
15
24
|
* and the volume data along with the loading status.
|
|
@@ -19,11 +28,16 @@ export class ImageVolume implements IImageVolume {
|
|
|
19
28
|
private _imageIdsIndexMap = new Map();
|
|
20
29
|
private _imageURIsIndexMap = new Map();
|
|
21
30
|
/** volume scalar data 3D or 4D */
|
|
22
|
-
protected scalarData:
|
|
31
|
+
protected scalarData: PixelDataTypedArray | Array<PixelDataTypedArray>;
|
|
32
|
+
protected numFrames: number;
|
|
33
|
+
protected totalNumFrames: number;
|
|
34
|
+
protected cornerstoneImageMetaData = null;
|
|
23
35
|
|
|
24
36
|
/** Read-only unique identifier for the volume */
|
|
25
37
|
readonly volumeId: string;
|
|
26
38
|
|
|
39
|
+
imageCacheOffsetMap = new Map();
|
|
40
|
+
|
|
27
41
|
isPreScaled = false;
|
|
28
42
|
|
|
29
43
|
/** Dimensions of the volume */
|
|
@@ -60,29 +74,82 @@ export class ImageVolume implements IImageVolume {
|
|
|
60
74
|
loadStatus?: Record<string, any>;
|
|
61
75
|
/** optional reference volume id if the volume is derived from another volume */
|
|
62
76
|
referencedVolumeId?: string;
|
|
77
|
+
/** optional reference image ids if the volume is derived from a set of images in the image cache */
|
|
78
|
+
referencedImageIds?: Array<string>;
|
|
63
79
|
/** whether the metadata for the pixel spacing is not undefined */
|
|
64
80
|
hasPixelSpacing: boolean;
|
|
81
|
+
/** Property to store additional information */
|
|
82
|
+
additionalDetails?: Record<string, any>;
|
|
65
83
|
|
|
66
|
-
constructor(props:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
constructor(props: ImageVolumeProps) {
|
|
85
|
+
const {
|
|
86
|
+
imageIds,
|
|
87
|
+
scalarData,
|
|
88
|
+
scaling,
|
|
89
|
+
dimensions,
|
|
90
|
+
spacing,
|
|
91
|
+
origin,
|
|
92
|
+
direction,
|
|
93
|
+
volumeId,
|
|
94
|
+
referencedVolumeId,
|
|
95
|
+
sizeInBytes,
|
|
96
|
+
imageData,
|
|
97
|
+
metadata,
|
|
98
|
+
referencedImageIds,
|
|
99
|
+
additionalDetails,
|
|
100
|
+
} = props;
|
|
101
|
+
|
|
102
|
+
this.imageIds = imageIds;
|
|
103
|
+
this.volumeId = volumeId;
|
|
104
|
+
this.metadata = metadata;
|
|
105
|
+
this.dimensions = dimensions;
|
|
106
|
+
this.spacing = spacing;
|
|
107
|
+
this.origin = origin;
|
|
108
|
+
this.direction = direction;
|
|
109
|
+
this.scalarData = scalarData;
|
|
110
|
+
this.sizeInBytes = sizeInBytes;
|
|
76
111
|
this.vtkOpenGLTexture = vtkStreamingOpenGLTexture.newInstance();
|
|
77
112
|
this.numVoxels =
|
|
78
113
|
this.dimensions[0] * this.dimensions[1] * this.dimensions[2];
|
|
79
114
|
|
|
80
|
-
if (
|
|
81
|
-
this.
|
|
115
|
+
if (imageData) {
|
|
116
|
+
this.imageData = imageData;
|
|
117
|
+
} else {
|
|
118
|
+
const imageData = vtkImageData.newInstance();
|
|
119
|
+
|
|
120
|
+
const scalarArray = vtkDataArray.newInstance({
|
|
121
|
+
name: 'Pixels',
|
|
122
|
+
numberOfComponents: 1,
|
|
123
|
+
values: scalarData,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
imageData.setDimensions(dimensions);
|
|
127
|
+
imageData.setSpacing(spacing);
|
|
128
|
+
imageData.setDirection(direction);
|
|
129
|
+
imageData.setOrigin(origin);
|
|
130
|
+
imageData.getPointData().setScalars(scalarArray);
|
|
131
|
+
|
|
132
|
+
this.imageData = imageData;
|
|
82
133
|
}
|
|
83
134
|
|
|
84
|
-
|
|
85
|
-
|
|
135
|
+
this.numFrames = this._getNumFrames();
|
|
136
|
+
this._reprocessImageIds();
|
|
137
|
+
this._createCornerstoneImageMetaData();
|
|
138
|
+
|
|
139
|
+
if (scaling) {
|
|
140
|
+
this.scaling = scaling;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (referencedVolumeId) {
|
|
144
|
+
this.referencedVolumeId = referencedVolumeId;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (referencedImageIds) {
|
|
148
|
+
this.referencedImageIds = referencedImageIds;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (additionalDetails) {
|
|
152
|
+
this.additionalDetails = additionalDetails;
|
|
86
153
|
}
|
|
87
154
|
}
|
|
88
155
|
|
|
@@ -120,9 +187,9 @@ export class ImageVolume implements IImageVolume {
|
|
|
120
187
|
* Return the scalar data for 3D volumes or the active scalar data
|
|
121
188
|
* (current time point) for 4D volumes
|
|
122
189
|
*/
|
|
123
|
-
public getScalarData():
|
|
190
|
+
public getScalarData(): PixelDataTypedArray {
|
|
124
191
|
if (isTypedArray(this.scalarData)) {
|
|
125
|
-
return <
|
|
192
|
+
return <PixelDataTypedArray>this.scalarData;
|
|
126
193
|
}
|
|
127
194
|
|
|
128
195
|
throw new Error('Unknown scalar data type');
|
|
@@ -158,6 +225,453 @@ export class ImageVolume implements IImageVolume {
|
|
|
158
225
|
this.vtkOpenGLTexture.releaseGraphicsResources();
|
|
159
226
|
this.vtkOpenGLTexture.delete();
|
|
160
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Return all scalar data objects (buffers) which will be only one for
|
|
231
|
+
* 3D volumes and one per time point for 4D volumes
|
|
232
|
+
* images of each 3D volume is stored
|
|
233
|
+
* @returns scalar data array
|
|
234
|
+
*/
|
|
235
|
+
public getScalarDataArrays(): PixelDataTypedArray[] {
|
|
236
|
+
return this.isDynamicVolume()
|
|
237
|
+
? <PixelDataTypedArray[]>this.scalarData
|
|
238
|
+
: [<PixelDataTypedArray>this.scalarData];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* If completelyRemove is true, remove the volume completely from the cache. Otherwise,
|
|
243
|
+
* convert the volume to cornerstone images (stack images) and store it in the cache
|
|
244
|
+
* @param completelyRemove - If true, the image will be removed from the
|
|
245
|
+
* cache completely.
|
|
246
|
+
*/
|
|
247
|
+
public decache(completelyRemove = false): void | Array<string> {
|
|
248
|
+
if (completelyRemove) {
|
|
249
|
+
this.removeFromCache();
|
|
250
|
+
} else {
|
|
251
|
+
this.convertToImageSlicesAndCache();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public removeFromCache() {
|
|
256
|
+
cache.removeVolumeLoadObject(this.volumeId);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public getScalarDataLength(): number {
|
|
260
|
+
const { scalarData } = this;
|
|
261
|
+
return this.isDynamicVolume()
|
|
262
|
+
? (<PixelDataTypedArray[]>scalarData)[0].length
|
|
263
|
+
: (<PixelDataTypedArray>scalarData).length;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Returns the number of frames stored in a scalarData object. The number of
|
|
268
|
+
* frames is equal to the number of images for 3D volumes or the number of
|
|
269
|
+
* frames per time poins for 4D volumes.
|
|
270
|
+
* @returns number of frames per volume
|
|
271
|
+
*/
|
|
272
|
+
private _getNumFrames(): number {
|
|
273
|
+
const { imageIds, scalarData } = this;
|
|
274
|
+
const scalarDataCount = this.isDynamicVolume() ? scalarData.length : 1;
|
|
275
|
+
|
|
276
|
+
return imageIds.length / scalarDataCount;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private _getScalarDataLength(): number {
|
|
280
|
+
const { scalarData } = this;
|
|
281
|
+
return this.isDynamicVolume()
|
|
282
|
+
? (<PixelDataTypedArray[]>scalarData)[0].length
|
|
283
|
+
: (<PixelDataTypedArray>scalarData).length;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Creates the metadata required for converting the volume to an cornerstoneImage
|
|
288
|
+
*/
|
|
289
|
+
private _createCornerstoneImageMetaData() {
|
|
290
|
+
const { numFrames } = this;
|
|
291
|
+
|
|
292
|
+
if (numFrames === 0) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const bytesPerImage = this.sizeInBytes / numFrames;
|
|
297
|
+
const scalarDataLength = this._getScalarDataLength();
|
|
298
|
+
const numComponents = scalarDataLength / this.numVoxels;
|
|
299
|
+
const pixelsPerImage =
|
|
300
|
+
this.dimensions[0] * this.dimensions[1] * numComponents;
|
|
301
|
+
|
|
302
|
+
const { PhotometricInterpretation, voiLut, VOILUTFunction } = this.metadata;
|
|
303
|
+
|
|
304
|
+
let windowCenter = [];
|
|
305
|
+
let windowWidth = [];
|
|
306
|
+
|
|
307
|
+
if (voiLut && voiLut.length) {
|
|
308
|
+
windowCenter = voiLut.map((voi) => {
|
|
309
|
+
return voi.windowCenter;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
windowWidth = voiLut.map((voi) => {
|
|
313
|
+
return voi.windowWidth;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const color = numComponents > 1 ? true : false; //todo: fix this
|
|
318
|
+
|
|
319
|
+
this.cornerstoneImageMetaData = {
|
|
320
|
+
bytesPerImage,
|
|
321
|
+
numComponents,
|
|
322
|
+
pixelsPerImage,
|
|
323
|
+
windowCenter,
|
|
324
|
+
windowWidth,
|
|
325
|
+
color,
|
|
326
|
+
// we use rgb (3 components) for the color volumes (and not rgba), and not rgba (which is used
|
|
327
|
+
// in some parts of the lib for stack viewing in CPU)
|
|
328
|
+
rgba: false,
|
|
329
|
+
spacing: this.spacing,
|
|
330
|
+
dimensions: this.dimensions,
|
|
331
|
+
photometricInterpretation: PhotometricInterpretation,
|
|
332
|
+
voiLUTFunction: VOILUTFunction,
|
|
333
|
+
invert: PhotometricInterpretation === 'MONOCHROME1',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
protected getScalarDataByImageIdIndex(
|
|
338
|
+
imageIdIndex: number
|
|
339
|
+
): PixelDataTypedArray {
|
|
340
|
+
if (imageIdIndex < 0 || imageIdIndex >= this.imageIds.length) {
|
|
341
|
+
throw new Error('imageIdIndex out of range');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const scalarDataArrays = this.getScalarDataArrays();
|
|
345
|
+
const scalarDataIndex = Math.floor(imageIdIndex / this.numFrames);
|
|
346
|
+
|
|
347
|
+
return scalarDataArrays[scalarDataIndex];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Converts the requested imageId inside the volume to a cornerstoneImage
|
|
352
|
+
* object. It uses the typedArray set method to copy the pixelData from the
|
|
353
|
+
* correct offset in the scalarData to a new array for the image
|
|
354
|
+
*
|
|
355
|
+
* @param imageId - the imageId of the image to be converted
|
|
356
|
+
* @param imageIdIndex - the index of the imageId in the imageIds array
|
|
357
|
+
* @returns image object containing the pixel data, metadata, and other information
|
|
358
|
+
*/
|
|
359
|
+
public getCornerstoneImage(imageId: string, imageIdIndex: number): IImage {
|
|
360
|
+
const { imageIds } = this;
|
|
361
|
+
const frameIndex = this.imageIdIndexToFrameIndex(imageIdIndex);
|
|
362
|
+
|
|
363
|
+
const {
|
|
364
|
+
bytesPerImage,
|
|
365
|
+
pixelsPerImage,
|
|
366
|
+
windowCenter,
|
|
367
|
+
windowWidth,
|
|
368
|
+
numComponents,
|
|
369
|
+
color,
|
|
370
|
+
dimensions,
|
|
371
|
+
spacing,
|
|
372
|
+
invert,
|
|
373
|
+
voiLUTFunction,
|
|
374
|
+
photometricInterpretation,
|
|
375
|
+
} = this.cornerstoneImageMetaData;
|
|
376
|
+
|
|
377
|
+
// 1. Grab the buffer and it's type
|
|
378
|
+
const scalarData = this.getScalarDataByImageIdIndex(imageIdIndex);
|
|
379
|
+
const volumeBuffer = scalarData.buffer;
|
|
380
|
+
// (not sure if this actually works, TypeScript keeps complaining)
|
|
381
|
+
const TypedArray = scalarData.constructor;
|
|
382
|
+
|
|
383
|
+
// 2. Given the index of the image and frame length in bytes,
|
|
384
|
+
// create a view on the volume arraybuffer
|
|
385
|
+
const bytePerPixel = bytesPerImage / pixelsPerImage;
|
|
386
|
+
|
|
387
|
+
let byteOffset = bytesPerImage * frameIndex;
|
|
388
|
+
|
|
389
|
+
// If there is a discrepancy between the volume typed array
|
|
390
|
+
// and the bitsAllocated for the image. The reason is that VTK uses Float32
|
|
391
|
+
// on the GPU and if the type is not Float32, it will convert it. So for not
|
|
392
|
+
// having a performance issue, we convert all types initially to Float32 even
|
|
393
|
+
// if they are not Float32.
|
|
394
|
+
if (scalarData.BYTES_PER_ELEMENT !== bytePerPixel) {
|
|
395
|
+
byteOffset *= scalarData.BYTES_PER_ELEMENT / bytePerPixel;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 3. Create a new TypedArray of the same type for the new
|
|
399
|
+
// Image that will be created
|
|
400
|
+
// @ts-ignore
|
|
401
|
+
const imageScalarData = new TypedArray(pixelsPerImage);
|
|
402
|
+
// @ts-ignore
|
|
403
|
+
const volumeBufferView = new TypedArray(
|
|
404
|
+
volumeBuffer,
|
|
405
|
+
byteOffset,
|
|
406
|
+
pixelsPerImage
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// 4. Use e.g. TypedArray.set() to copy the data from the larger
|
|
410
|
+
// buffer's view into the smaller one
|
|
411
|
+
imageScalarData.set(volumeBufferView);
|
|
412
|
+
|
|
413
|
+
// 5. Create an Image Object from imageScalarData and put it into the Image cache
|
|
414
|
+
const volumeImageId = imageIds[imageIdIndex];
|
|
415
|
+
const modalityLutModule =
|
|
416
|
+
metaData.get('modalityLutModule', volumeImageId) || {};
|
|
417
|
+
const minMax = getMinMax(imageScalarData);
|
|
418
|
+
const intercept = modalityLutModule.rescaleIntercept
|
|
419
|
+
? modalityLutModule.rescaleIntercept
|
|
420
|
+
: 0;
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
imageId,
|
|
424
|
+
intercept,
|
|
425
|
+
windowCenter,
|
|
426
|
+
windowWidth,
|
|
427
|
+
voiLUTFunction,
|
|
428
|
+
color,
|
|
429
|
+
rgba: false,
|
|
430
|
+
numComps: numComponents,
|
|
431
|
+
// Note the dimensions were defined as [Columns, Rows, Frames]
|
|
432
|
+
rows: dimensions[1],
|
|
433
|
+
columns: dimensions[0],
|
|
434
|
+
sizeInBytes: imageScalarData.byteLength,
|
|
435
|
+
getPixelData: () => imageScalarData,
|
|
436
|
+
minPixelValue: minMax.min,
|
|
437
|
+
maxPixelValue: minMax.max,
|
|
438
|
+
slope: modalityLutModule.rescaleSlope
|
|
439
|
+
? modalityLutModule.rescaleSlope
|
|
440
|
+
: 1,
|
|
441
|
+
getCanvas: undefined, // todo: which canvas?
|
|
442
|
+
height: dimensions[0],
|
|
443
|
+
width: dimensions[1],
|
|
444
|
+
columnPixelSpacing: spacing[0],
|
|
445
|
+
rowPixelSpacing: spacing[1],
|
|
446
|
+
invert,
|
|
447
|
+
photometricInterpretation,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Converts imageIdIndex into frameIndex which will be the same
|
|
453
|
+
* for 3D volumes but different for 4D volumes. The indices are 0 based.
|
|
454
|
+
*/
|
|
455
|
+
protected imageIdIndexToFrameIndex(imageIdIndex: number): number {
|
|
456
|
+
return imageIdIndex % this.numFrames;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Converts the requested imageId inside the volume to a cornerstoneImage
|
|
461
|
+
* object. It uses the typedArray set method to copy the pixelData from the
|
|
462
|
+
* correct offset in the scalarData to a new array for the image
|
|
463
|
+
* Duplicate of getCornerstoneImageLoadObject for legacy reasons
|
|
464
|
+
*
|
|
465
|
+
* @param imageId - the imageId of the image to be converted
|
|
466
|
+
* @param imageIdIndex - the index of the imageId in the imageIds array
|
|
467
|
+
* @returns imageLoadObject containing the promise that resolves
|
|
468
|
+
* to the cornerstone image
|
|
469
|
+
*/
|
|
470
|
+
public convertToCornerstoneImage(
|
|
471
|
+
imageId: string,
|
|
472
|
+
imageIdIndex: number
|
|
473
|
+
): IImageLoadObject {
|
|
474
|
+
return this.getCornerstoneImageLoadObject(imageId, imageIdIndex);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Converts the requested imageId inside the volume to a cornerstoneImage
|
|
479
|
+
* object. It uses the typedArray set method to copy the pixelData from the
|
|
480
|
+
* correct offset in the scalarData to a new array for the image
|
|
481
|
+
*
|
|
482
|
+
* @param imageId - the imageId of the image to be converted
|
|
483
|
+
* @param imageIdIndex - the index of the imageId in the imageIds array
|
|
484
|
+
* @returns imageLoadObject containing the promise that resolves
|
|
485
|
+
* to the cornerstone image
|
|
486
|
+
*/
|
|
487
|
+
public getCornerstoneImageLoadObject(
|
|
488
|
+
imageId: string,
|
|
489
|
+
imageIdIndex: number
|
|
490
|
+
): IImageLoadObject {
|
|
491
|
+
const image = this.getCornerstoneImage(imageId, imageIdIndex);
|
|
492
|
+
|
|
493
|
+
const imageLoadObject = {
|
|
494
|
+
promise: Promise.resolve(image),
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return imageLoadObject;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Returns an array of all the volume's images as Cornerstone images.
|
|
502
|
+
* It iterates over all the imageIds and converts them to Cornerstone images.
|
|
503
|
+
*
|
|
504
|
+
* @returns An array of Cornerstone images.
|
|
505
|
+
*/
|
|
506
|
+
public getCornerstoneImages(): IImage[] {
|
|
507
|
+
const { imageIds } = this;
|
|
508
|
+
|
|
509
|
+
return imageIds.map((imageId, imageIdIndex) => {
|
|
510
|
+
return this.getCornerstoneImage(imageId, imageIdIndex);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Converts all the volume images (imageIds) to cornerstoneImages and caches them.
|
|
516
|
+
* It iterates over all the imageIds and convert them until there is no
|
|
517
|
+
* enough space left inside the imageCache. Finally it will decache the Volume.
|
|
518
|
+
*
|
|
519
|
+
*/
|
|
520
|
+
public convertToImageSlicesAndCache() {
|
|
521
|
+
// 1. Try to decache images in the volatile Image Cache to provide
|
|
522
|
+
// enough space to store another entire copy of the volume (as Images).
|
|
523
|
+
// If we do not have enough, we will store as many images in the cache
|
|
524
|
+
// as possible, and the rest of the volume will be decached.
|
|
525
|
+
const byteLength = this.sizeInBytes;
|
|
526
|
+
|
|
527
|
+
if (!this.imageIds?.length) {
|
|
528
|
+
// generate random imageIds
|
|
529
|
+
// check if the referenced volume has imageIds to see how many
|
|
530
|
+
// images we need to generate
|
|
531
|
+
const referencedVolumeId = this.referencedVolumeId;
|
|
532
|
+
const referencedVolume = cache.getVolume(referencedVolumeId);
|
|
533
|
+
|
|
534
|
+
const numSlices =
|
|
535
|
+
referencedVolume?.imageIds?.length || this.dimensions[2];
|
|
536
|
+
|
|
537
|
+
this.imageIds = Array.from({ length: numSlices }, (_, i) => {
|
|
538
|
+
return `generated:${this.volumeId}:${i}`;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
this._reprocessImageIds();
|
|
542
|
+
this.numFrames = this._getNumFrames();
|
|
543
|
+
this._createCornerstoneImageMetaData();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const numImages = this.imageIds.length;
|
|
547
|
+
const { bytesPerImage } = this.cornerstoneImageMetaData;
|
|
548
|
+
let bytesRemaining = cache.decacheIfNecessaryUntilBytesAvailable(
|
|
549
|
+
byteLength,
|
|
550
|
+
this.imageIds
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
for (let imageIdIndex = 0; imageIdIndex < numImages; imageIdIndex++) {
|
|
554
|
+
const imageId = this.imageIds[imageIdIndex];
|
|
555
|
+
|
|
556
|
+
bytesRemaining = bytesRemaining - bytesPerImage;
|
|
557
|
+
|
|
558
|
+
// 2. Convert each imageId to a cornerstone Image object which is
|
|
559
|
+
// resolved inside the promise of imageLoadObject
|
|
560
|
+
const image = this.getCornerstoneImage(imageId, imageIdIndex);
|
|
561
|
+
|
|
562
|
+
const imageLoadObject = {
|
|
563
|
+
promise: Promise.resolve(image),
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// 3. Caching the image
|
|
567
|
+
if (!cache.getImageLoadObject(imageId)) {
|
|
568
|
+
cache.putImageLoadObject(imageId, imageLoadObject).catch((err) => {
|
|
569
|
+
console.error(err);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 4. If we know we won't be able to add another Image to the cache
|
|
574
|
+
// without breaching the limit, stop here.
|
|
575
|
+
if (bytesRemaining <= bytesPerImage) {
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const imageOrientationPatient = [
|
|
580
|
+
this.direction[0],
|
|
581
|
+
this.direction[1],
|
|
582
|
+
this.direction[2],
|
|
583
|
+
this.direction[3],
|
|
584
|
+
this.direction[4],
|
|
585
|
+
this.direction[5],
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
const precision = 6;
|
|
589
|
+
const imagePositionPatient = [
|
|
590
|
+
parseFloat(
|
|
591
|
+
(
|
|
592
|
+
this.origin[0] +
|
|
593
|
+
imageIdIndex * this.direction[6] * this.spacing[0]
|
|
594
|
+
).toFixed(precision)
|
|
595
|
+
),
|
|
596
|
+
parseFloat(
|
|
597
|
+
(
|
|
598
|
+
this.origin[1] +
|
|
599
|
+
imageIdIndex * this.direction[7] * this.spacing[1]
|
|
600
|
+
).toFixed(precision)
|
|
601
|
+
),
|
|
602
|
+
parseFloat(
|
|
603
|
+
(
|
|
604
|
+
this.origin[2] +
|
|
605
|
+
imageIdIndex * this.direction[8] * this.spacing[2]
|
|
606
|
+
).toFixed(precision)
|
|
607
|
+
),
|
|
608
|
+
];
|
|
609
|
+
|
|
610
|
+
const pixelData = image.getPixelData();
|
|
611
|
+
const bitsAllocated = pixelData.BYTES_PER_ELEMENT * 8;
|
|
612
|
+
|
|
613
|
+
const imagePixelModule = {
|
|
614
|
+
// bitsStored: number;
|
|
615
|
+
// samplesPerPixel: number;
|
|
616
|
+
// highBit: number;
|
|
617
|
+
// pixelRepresentation: string;
|
|
618
|
+
// modality: string;
|
|
619
|
+
bitsAllocated,
|
|
620
|
+
photometricInterpretation: image.photometricInterpretation,
|
|
621
|
+
windowWidth: image.windowWidth,
|
|
622
|
+
windowCenter: image.windowCenter,
|
|
623
|
+
voiLUTFunction: image.voiLUTFunction,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const imagePlaneModule = {
|
|
627
|
+
rowCosines: [this.direction[0], this.direction[1], this.direction[2]],
|
|
628
|
+
columnCosines: [
|
|
629
|
+
this.direction[3],
|
|
630
|
+
this.direction[4],
|
|
631
|
+
this.direction[5],
|
|
632
|
+
],
|
|
633
|
+
pixelSpacing: [this.spacing[0], this.spacing[1]],
|
|
634
|
+
// sliceLocation?: number;
|
|
635
|
+
// sliceThickness?: number;
|
|
636
|
+
// frameOfReferenceUID: string;
|
|
637
|
+
imageOrientationPatient: imageOrientationPatient,
|
|
638
|
+
imagePositionPatient: imagePositionPatient,
|
|
639
|
+
columnPixelSpacing: image.columnPixelSpacing,
|
|
640
|
+
rowPixelSpacing: image.rowPixelSpacing,
|
|
641
|
+
columns: image.columns,
|
|
642
|
+
rows: image.rows,
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const generalSeriesModule = {
|
|
646
|
+
// modality: image.modality,
|
|
647
|
+
// seriesInstanceUID: string;
|
|
648
|
+
// seriesNumber: number;
|
|
649
|
+
// studyInstanceUID: string;
|
|
650
|
+
// seriesDate: DicomDateObject;
|
|
651
|
+
// seriesTime: DicomTimeObject;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const metadata = {
|
|
655
|
+
imagePixelModule,
|
|
656
|
+
imagePlaneModule,
|
|
657
|
+
generalSeriesModule,
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
['imagePixelModule', 'imagePlaneModule', 'generalSeriesModule'].forEach(
|
|
661
|
+
(type) => {
|
|
662
|
+
genericMetadataProvider.add(imageId, {
|
|
663
|
+
type,
|
|
664
|
+
metadata: metadata[type],
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
// 5. When as much of the Volume is processed into Images as possible
|
|
670
|
+
// without breaching the cache limit, remove the Volume
|
|
671
|
+
this.removeFromCache();
|
|
672
|
+
|
|
673
|
+
return this.imageIds;
|
|
674
|
+
}
|
|
161
675
|
}
|
|
162
676
|
|
|
163
677
|
export default ImageVolume;
|
package/src/eventTarget.ts
CHANGED
|
@@ -12,6 +12,20 @@ class CornerstoneEventTarget implements EventTarget {
|
|
|
12
12
|
this.listeners = {};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
public addEventListenerOnce(type, callback) {
|
|
16
|
+
// Create a wrapper function to encapsulate the original callback
|
|
17
|
+
const onceWrapper = (event) => {
|
|
18
|
+
// Remove the listener after its first invocation
|
|
19
|
+
this.removeEventListener(type, onceWrapper);
|
|
20
|
+
|
|
21
|
+
// Call the original callback
|
|
22
|
+
callback.call(this, event);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Add the wrapper as the listener
|
|
26
|
+
this.addEventListener(type, onceWrapper);
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
public addEventListener(type, callback) {
|
|
16
30
|
if (!this.listeners[type]) {
|
|
17
31
|
this.listeners[type] = [];
|
|
@@ -52,7 +66,11 @@ class CornerstoneEventTarget implements EventTarget {
|
|
|
52
66
|
const stackLength = stack.length;
|
|
53
67
|
|
|
54
68
|
for (let i = 0; i < stackLength; i++) {
|
|
55
|
-
|
|
69
|
+
try {
|
|
70
|
+
stack[i].call(this, event);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`error in event listener of type: ${event.type}`, error);
|
|
73
|
+
}
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
return !event.defaultPrevented;
|
package/src/init.ts
CHANGED
|
@@ -21,7 +21,7 @@ const defaultConfig: Cornerstone3DConfig = {
|
|
|
21
21
|
strictZSpacingForVolumeViewport: true,
|
|
22
22
|
},
|
|
23
23
|
// cache
|
|
24
|
-
|
|
24
|
+
enableCacheOptimization: true,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
let config: Cornerstone3DConfig = {
|
|
@@ -35,7 +35,7 @@ let config: Cornerstone3DConfig = {
|
|
|
35
35
|
strictZSpacingForVolumeViewport: true,
|
|
36
36
|
},
|
|
37
37
|
// cache
|
|
38
|
-
|
|
38
|
+
enableCacheOptimization: true,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
let webWorkerManager = null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import cache from '../cache/cache';
|
|
2
|
+
import { ImageVolume } from '../cache';
|
|
2
3
|
import Events from '../enums/Events';
|
|
3
4
|
import eventTarget from '../eventTarget';
|
|
4
5
|
import {
|
|
@@ -120,11 +121,14 @@ function loadImageFromCacheOrVolume(
|
|
|
120
121
|
// 2. Check if there exists a volume in the cache containing the imageId,
|
|
121
122
|
// we copy the pixelData over.
|
|
122
123
|
const cachedVolumeInfo = cache.getVolumeContainingImageId(imageId);
|
|
123
|
-
if (cachedVolumeInfo
|
|
124
|
+
if (cachedVolumeInfo?.volume?.loadStatus?.loaded) {
|
|
124
125
|
// 2.1 Convert the volume at the specific slice to a cornerstoneImage object.
|
|
125
126
|
// this will copy the pixel data over.
|
|
126
127
|
const { volume, imageIdIndex } = cachedVolumeInfo;
|
|
127
|
-
|
|
128
|
+
|
|
129
|
+
if (volume instanceof ImageVolume) {
|
|
130
|
+
imageLoadObject = volume.convertToCornerstoneImage(imageId, imageIdIndex);
|
|
131
|
+
}
|
|
128
132
|
return imageLoadObject;
|
|
129
133
|
}
|
|
130
134
|
// 3. If no volume found, we search inside the imageCache for the imageId
|