@cornerstonejs/core 0.36.2 → 0.36.4
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/loaders/volumeLoader.d.ts +1 -0
- package/dist/cjs/loaders/volumeLoader.js +5 -1
- package/dist/cjs/loaders/volumeLoader.js.map +1 -1
- package/dist/cjs/utilities/getSliceRange.js +3 -1
- package/dist/cjs/utilities/getSliceRange.js.map +1 -1
- package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
- package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
- package/dist/esm/loaders/volumeLoader.d.ts +1 -0
- package/dist/esm/loaders/volumeLoader.js +3 -0
- package/dist/esm/loaders/volumeLoader.js.map +1 -1
- package/dist/esm/utilities/getSliceRange.js +3 -1
- package/dist/esm/utilities/getSliceRange.js.map +1 -1
- package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
- 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 +4 -3
- package/src/RenderingEngine/BaseVolumeViewport.ts +847 -0
- package/src/RenderingEngine/RenderingEngine.ts +1364 -0
- package/src/RenderingEngine/StackViewport.ts +2690 -0
- package/src/RenderingEngine/Viewport.ts +1244 -0
- package/src/RenderingEngine/VolumeViewport.ts +420 -0
- package/src/RenderingEngine/VolumeViewport3D.ts +42 -0
- package/src/RenderingEngine/getRenderingEngine.ts +34 -0
- package/src/RenderingEngine/helpers/addVolumesToViewports.ts +52 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/colormap.ts +343 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/index.ts +4 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/lookupTable.ts +469 -0
- package/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts +58 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/calculateTransform.ts +136 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/canvasToPixel.ts +25 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/computeAutoVoi.ts +47 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts +38 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/createViewport.ts +64 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/doesImageNeedToBeRendered.ts +36 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/fitToWindow.ts +22 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts +60 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/generateLut.ts +83 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getDefaultViewport.ts +88 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageFitScale.ts +52 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageSize.ts +55 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getLut.ts +53 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getModalityLut.ts +55 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getTransform.ts +17 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getVOILut.ts +74 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/initializeRenderCanvas.ts +37 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/lutMatches.ts +21 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/now.ts +13 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/pixelToCanvas.ts +22 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts +193 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderGrayscaleImage.ts +166 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderPseudoColorImage.ts +203 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/resetCamera.ts +32 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/resize.ts +109 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/saveLastRendered.ts +36 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/setDefaultViewport.ts +17 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/setToPixelCoordinateSystem.ts +32 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedColorPixelDataToCanvasImageData.ts +58 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageData.ts +76 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataColorLUT.ts +60 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPET.ts +50 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUT.ts +66 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUTPET.ts +68 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataRGBA.ts +81 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedRGBAPixelDataToCanvasImageData.ts +56 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/transform.ts +126 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/validator.ts +31 -0
- package/src/RenderingEngine/helpers/createVolumeActor.ts +103 -0
- package/src/RenderingEngine/helpers/createVolumeMapper.ts +37 -0
- package/src/RenderingEngine/helpers/getOrCreateCanvas.ts +58 -0
- package/src/RenderingEngine/helpers/index.ts +15 -0
- package/src/RenderingEngine/helpers/isRgbaSourceRgbDest.ts +1 -0
- package/src/RenderingEngine/helpers/setDefaultVolumeVOI.ts +227 -0
- package/src/RenderingEngine/helpers/setVolumesForViewports.ts +52 -0
- package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +14 -0
- package/src/RenderingEngine/helpers/viewportTypeUsesCustomRenderingPipeline.ts +7 -0
- package/src/RenderingEngine/helpers/volumeNewImageEventDispatcher.ts +75 -0
- package/src/RenderingEngine/index.ts +23 -0
- package/src/RenderingEngine/renderingEngineCache.ts +43 -0
- package/src/RenderingEngine/vtkClasses/index.js +11 -0
- package/src/RenderingEngine/vtkClasses/vtkOffscreenMultiRenderWindow.js +149 -0
- package/src/RenderingEngine/vtkClasses/vtkSharedVolumeMapper.js +52 -0
- package/src/RenderingEngine/vtkClasses/vtkSlabCamera.d.ts +781 -0
- package/src/RenderingEngine/vtkClasses/vtkSlabCamera.js +155 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLRenderWindow.js +47 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLTexture.js +272 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLViewNodeFactory.js +159 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +319 -0
- package/src/Settings.ts +294 -0
- package/src/cache/cache.ts +854 -0
- package/src/cache/classes/Contour.ts +70 -0
- package/src/cache/classes/ContourSet.ts +151 -0
- package/src/cache/classes/ImageVolume.ts +155 -0
- package/src/cache/index.ts +5 -0
- package/src/constants/cpuColormaps.ts +1537 -0
- package/src/constants/epsilon.ts +3 -0
- package/src/constants/index.ts +13 -0
- package/src/constants/mprCameraValues.ts +20 -0
- package/src/constants/rendering.ts +8 -0
- package/src/constants/viewportPresets.ts +357 -0
- package/src/enums/BlendModes.ts +23 -0
- package/src/enums/ContourType.ts +6 -0
- package/src/enums/Events.ts +196 -0
- package/src/enums/GeometryType.ts +5 -0
- package/src/enums/InterpolationType.ts +13 -0
- package/src/enums/OrientationAxis.ts +8 -0
- package/src/enums/RequestType.ts +13 -0
- package/src/enums/SharedArrayBufferModes.ts +11 -0
- package/src/enums/VOILUTFunctionType.ts +10 -0
- package/src/enums/ViewportType.ts +21 -0
- package/src/enums/index.ts +23 -0
- package/src/eventTarget.ts +67 -0
- package/src/getEnabledElement.ts +105 -0
- package/src/global.ts +8 -0
- package/src/index.ts +123 -0
- package/src/init.ts +247 -0
- package/src/loaders/geometryLoader.ts +108 -0
- package/src/loaders/imageLoader.ts +298 -0
- package/src/loaders/volumeLoader.ts +477 -0
- package/src/metaData.ts +84 -0
- package/src/requestPool/imageLoadPoolManager.ts +43 -0
- package/src/requestPool/imageRetrievalPoolManager.ts +25 -0
- package/src/requestPool/requestPoolManager.ts +329 -0
- package/src/types/ActorSliceRange.ts +17 -0
- package/src/types/CPUFallbackColormap.ts +23 -0
- package/src/types/CPUFallbackColormapData.ts +12 -0
- package/src/types/CPUFallbackColormapsData.ts +7 -0
- package/src/types/CPUFallbackEnabledElement.ts +71 -0
- package/src/types/CPUFallbackLUT.ts +5 -0
- package/src/types/CPUFallbackLookupTable.ts +17 -0
- package/src/types/CPUFallbackRenderingTools.ts +25 -0
- package/src/types/CPUFallbackTransform.ts +16 -0
- package/src/types/CPUFallbackViewport.ts +29 -0
- package/src/types/CPUFallbackViewportDisplayedArea.ts +15 -0
- package/src/types/CPUIImageData.ts +47 -0
- package/src/types/ContourData.ts +19 -0
- package/src/types/Cornerstone3DConfig.ts +31 -0
- package/src/types/CustomEventType.ts +14 -0
- package/src/types/EventTypes.ts +403 -0
- package/src/types/FlipDirection.ts +9 -0
- package/src/types/IActor.ts +23 -0
- package/src/types/ICache.ts +28 -0
- package/src/types/ICachedGeometry.ts +13 -0
- package/src/types/ICachedImage.ts +13 -0
- package/src/types/ICachedVolume.ts +12 -0
- package/src/types/ICamera.ts +36 -0
- package/src/types/IContour.ts +18 -0
- package/src/types/IContourSet.ts +56 -0
- package/src/types/IDynamicImageVolume.ts +18 -0
- package/src/types/IEnabledElement.ts +21 -0
- package/src/types/IGeometry.ts +12 -0
- package/src/types/IImage.ts +113 -0
- package/src/types/IImageData.ts +45 -0
- package/src/types/IImageVolume.ts +78 -0
- package/src/types/ILoadObject.ts +36 -0
- package/src/types/IRegisterImageLoader.ts +10 -0
- package/src/types/IRenderingEngine.ts +28 -0
- package/src/types/IStackViewport.ts +138 -0
- package/src/types/IStreamingImageVolume.ts +13 -0
- package/src/types/IStreamingVolumeProperties.ts +14 -0
- package/src/types/IViewport.ts +149 -0
- package/src/types/IViewportId.ts +9 -0
- package/src/types/IVolume.ts +45 -0
- package/src/types/IVolumeInput.ts +36 -0
- package/src/types/IVolumeViewport.ts +141 -0
- package/src/types/ImageLoaderFn.ts +16 -0
- package/src/types/ImageSliceData.ts +6 -0
- package/src/types/Mat3.ts +16 -0
- package/src/types/Metadata.ts +39 -0
- package/src/types/OrientationVectors.ts +36 -0
- package/src/types/Plane.ts +6 -0
- package/src/types/Point2.ts +6 -0
- package/src/types/Point3.ts +6 -0
- package/src/types/Point4.ts +6 -0
- package/src/types/ScalingParameters.ts +27 -0
- package/src/types/StackViewportProperties.ts +25 -0
- package/src/types/TransformMatrix2D.ts +4 -0
- package/src/types/ViewportInputOptions.ts +21 -0
- package/src/types/ViewportPreset.ts +14 -0
- package/src/types/VolumeLoaderFn.ts +18 -0
- package/src/types/VolumeViewportProperties.ts +14 -0
- package/src/types/index.ts +157 -0
- package/src/types/voi.ts +15 -0
- package/src/utilities/actorCheck.ts +24 -0
- package/src/utilities/applyPreset.ts +132 -0
- package/src/utilities/calculateViewportsSpatialRegistration.ts +74 -0
- package/src/utilities/calibratedPixelSpacingMetadataProvider.ts +38 -0
- package/src/utilities/createFloat32SharedArray.ts +45 -0
- package/src/utilities/createInt16SharedArray.ts +43 -0
- package/src/utilities/createLinearRGBTransferFunction.ts +22 -0
- package/src/utilities/createSigmoidRGBTransferFunction.ts +63 -0
- package/src/utilities/createUInt16SharedArray.ts +43 -0
- package/src/utilities/createUint8SharedArray.ts +45 -0
- package/src/utilities/deepFreeze.ts +19 -0
- package/src/utilities/deepMerge.ts +81 -0
- package/src/utilities/getClosestImageId.ts +80 -0
- package/src/utilities/getClosestStackImageIndexForPoint.ts +116 -0
- package/src/utilities/getImageSliceDataForVolumeViewport.ts +61 -0
- package/src/utilities/getMinMax.ts +31 -0
- package/src/utilities/getRuntimeId.ts +54 -0
- package/src/utilities/getScalarDataType.ts +31 -0
- package/src/utilities/getScalingParameters.ts +35 -0
- package/src/utilities/getSliceRange.ts +86 -0
- package/src/utilities/getSpacingInNormalDirection.ts +44 -0
- package/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +126 -0
- package/src/utilities/getViewportImageCornersInWorld.ts +102 -0
- package/src/utilities/getViewportsWithImageURI.ts +46 -0
- package/src/utilities/getViewportsWithVolumeId.ts +38 -0
- package/src/utilities/getVoiFromSigmoidRGBTransferFunction.ts +23 -0
- package/src/utilities/getVolumeActorCorners.ts +24 -0
- package/src/utilities/getVolumeSliceRangeInfo.ts +52 -0
- package/src/utilities/getVolumeViewportScrollInfo.ts +32 -0
- package/src/utilities/getVolumeViewportsContainingSameVolumes.ts +58 -0
- package/src/utilities/hasNaNValues.ts +12 -0
- package/src/utilities/imageIdToURI.ts +10 -0
- package/src/utilities/imageToWorldCoords.ts +54 -0
- package/src/utilities/index.ts +100 -0
- package/src/utilities/indexWithinDimensions.ts +27 -0
- package/src/utilities/invertRgbTransferFunction.ts +36 -0
- package/src/utilities/isEqual.ts +27 -0
- package/src/utilities/isOpposite.ts +23 -0
- package/src/utilities/isTypedArray.ts +20 -0
- package/src/utilities/loadImageToCanvas.ts +80 -0
- package/src/utilities/planar.ts +91 -0
- package/src/utilities/renderToCanvas.ts +32 -0
- package/src/utilities/scaleRgbTransferFunction.ts +37 -0
- package/src/utilities/snapFocalPointToSlice.ts +78 -0
- package/src/utilities/spatialRegistrationMetadataProvider.ts +50 -0
- package/src/utilities/transformWorldToIndex.ts +16 -0
- package/src/utilities/triggerEvent.ts +38 -0
- package/src/utilities/uuidv4.ts +13 -0
- package/src/utilities/windowLevel.ts +39 -0
- package/src/utilities/worldToImageCoords.ts +64 -0
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ICache,
|
|
3
|
+
IImage,
|
|
4
|
+
IImageVolume,
|
|
5
|
+
IGeometry,
|
|
6
|
+
IImageLoadObject,
|
|
7
|
+
IVolumeLoadObject,
|
|
8
|
+
IGeometryLoadObject,
|
|
9
|
+
ICachedImage,
|
|
10
|
+
ICachedVolume,
|
|
11
|
+
ICachedGeometry,
|
|
12
|
+
EventTypes,
|
|
13
|
+
} from '../types';
|
|
14
|
+
import { triggerEvent, imageIdToURI } from '../utilities';
|
|
15
|
+
import eventTarget from '../eventTarget';
|
|
16
|
+
import Events from '../enums/Events';
|
|
17
|
+
|
|
18
|
+
const MAX_CACHE_SIZE_1GB = 1073741824;
|
|
19
|
+
|
|
20
|
+
class Cache implements ICache {
|
|
21
|
+
private readonly _imageCache: Map<string, ICachedImage>; // volatile space
|
|
22
|
+
private readonly _volumeCache: Map<string, ICachedVolume>; // non-volatile space
|
|
23
|
+
// Todo: contour for now, but will be used for surface, etc.
|
|
24
|
+
private readonly _geometryCache: Map<string, ICachedGeometry>;
|
|
25
|
+
private _imageCacheSize: number;
|
|
26
|
+
private _volumeCacheSize: number;
|
|
27
|
+
private _maxCacheSize: number;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
// used to store image data (2d)
|
|
31
|
+
this._imageCache = new Map();
|
|
32
|
+
// used to store volume data (3d)
|
|
33
|
+
this._volumeCache = new Map();
|
|
34
|
+
// used to store object data (contour, surface, etc.)
|
|
35
|
+
this._geometryCache = new Map();
|
|
36
|
+
this._imageCacheSize = 0;
|
|
37
|
+
this._volumeCacheSize = 0;
|
|
38
|
+
this._maxCacheSize = MAX_CACHE_SIZE_1GB; // Default 1GB
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set the maximum cache Size
|
|
43
|
+
*
|
|
44
|
+
* Maximum cache size should be set before adding the data; otherwise, it
|
|
45
|
+
* will throw an error.
|
|
46
|
+
*
|
|
47
|
+
* @param newMaxCacheSize - new maximum cache size
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
public setMaxCacheSize = (newMaxCacheSize: number): void => {
|
|
51
|
+
if (!newMaxCacheSize || typeof newMaxCacheSize !== 'number') {
|
|
52
|
+
const errorMessage = `New max cacheSize ${this._maxCacheSize} should be defined and should be a number.`;
|
|
53
|
+
throw new Error(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this._maxCacheSize = newMaxCacheSize;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Checks if there is enough space in the cache for requested byte size
|
|
61
|
+
*
|
|
62
|
+
* It throws error, if the sum of volatile (image) cache and unallocated cache
|
|
63
|
+
* is less than the requested byteLength
|
|
64
|
+
*
|
|
65
|
+
* @param byteLength - byte length of requested byte size
|
|
66
|
+
*
|
|
67
|
+
* @returns - boolean indicating if there is enough space in the cache
|
|
68
|
+
*/
|
|
69
|
+
public isCacheable = (byteLength: number): boolean => {
|
|
70
|
+
const unallocatedSpace = this.getBytesAvailable();
|
|
71
|
+
const imageCacheSize = this._imageCacheSize;
|
|
72
|
+
const availableSpace = unallocatedSpace + imageCacheSize;
|
|
73
|
+
|
|
74
|
+
return availableSpace > byteLength;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns maximum CacheSize allowed
|
|
79
|
+
*
|
|
80
|
+
* @returns maximum allowed cache size
|
|
81
|
+
*/
|
|
82
|
+
public getMaxCacheSize = (): number => this._maxCacheSize;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns current size of the cache
|
|
86
|
+
*
|
|
87
|
+
* @returns current size of the cache
|
|
88
|
+
*/
|
|
89
|
+
public getCacheSize = (): number =>
|
|
90
|
+
this._imageCacheSize + this._volumeCacheSize;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns the unallocated size of the cache
|
|
94
|
+
*
|
|
95
|
+
*/
|
|
96
|
+
public getBytesAvailable(): number {
|
|
97
|
+
return this.getMaxCacheSize() - this.getCacheSize();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Deletes the imageId from the image cache
|
|
102
|
+
*
|
|
103
|
+
* @param imageId - imageId
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
private _decacheImage = (imageId: string) => {
|
|
107
|
+
const { imageLoadObject } = this._imageCache.get(imageId);
|
|
108
|
+
|
|
109
|
+
// Cancel any in-progress loading
|
|
110
|
+
if (imageLoadObject.cancelFn) {
|
|
111
|
+
imageLoadObject.cancelFn();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (imageLoadObject.decache) {
|
|
115
|
+
imageLoadObject.decache();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this._imageCache.delete(imageId);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Deletes the volumeId from the volume cache
|
|
123
|
+
*
|
|
124
|
+
* @param volumeId - volumeId
|
|
125
|
+
*
|
|
126
|
+
*/
|
|
127
|
+
private _decacheVolume = (volumeId: string) => {
|
|
128
|
+
const cachedVolume = this._volumeCache.get(volumeId);
|
|
129
|
+
const { volumeLoadObject, volume } = cachedVolume;
|
|
130
|
+
|
|
131
|
+
if (volume.cancelLoading) {
|
|
132
|
+
volume.cancelLoading();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (volume.imageData) {
|
|
136
|
+
volume.imageData = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (volumeLoadObject.cancelFn) {
|
|
140
|
+
// Cancel any in-progress loading
|
|
141
|
+
volumeLoadObject.cancelFn();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (volumeLoadObject.decache) {
|
|
145
|
+
volumeLoadObject.decache();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this._volumeCache.delete(volumeId);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Deletes all the images and volumes in the cache
|
|
153
|
+
*
|
|
154
|
+
* Relevant events are fired for each decached image (IMAGE_CACHE_IMAGE_REMOVED) and
|
|
155
|
+
* the decached volume (VOLUME_CACHE_VOLUME_REMOVED).
|
|
156
|
+
*
|
|
157
|
+
* @fires Events.IMAGE_CACHE_IMAGE_REMOVED
|
|
158
|
+
* @fires Events.VOLUME_CACHE_VOLUME_REMOVED
|
|
159
|
+
*
|
|
160
|
+
*/
|
|
161
|
+
public purgeCache = (): void => {
|
|
162
|
+
const imageIterator = this._imageCache.keys();
|
|
163
|
+
|
|
164
|
+
/* eslint-disable no-constant-condition */
|
|
165
|
+
while (true) {
|
|
166
|
+
const { value: imageId, done } = imageIterator.next();
|
|
167
|
+
|
|
168
|
+
if (done) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.removeImageLoadObject(imageId);
|
|
173
|
+
|
|
174
|
+
triggerEvent(eventTarget, Events.IMAGE_CACHE_IMAGE_REMOVED, { imageId });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.purgeVolumeCache();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Deletes all the volumes in the cache
|
|
182
|
+
*/
|
|
183
|
+
public purgeVolumeCache = (): void => {
|
|
184
|
+
const volumeIterator = this._volumeCache.keys();
|
|
185
|
+
|
|
186
|
+
/* eslint-disable no-constant-condition */
|
|
187
|
+
while (true) {
|
|
188
|
+
const { value: volumeId, done } = volumeIterator.next();
|
|
189
|
+
|
|
190
|
+
if (done) {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.removeVolumeLoadObject(volumeId);
|
|
195
|
+
|
|
196
|
+
triggerEvent(eventTarget, Events.VOLUME_CACHE_VOLUME_REMOVED, {
|
|
197
|
+
volumeId,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Purges the cache if necessary based on the requested number of bytes
|
|
204
|
+
*
|
|
205
|
+
* 1) it sorts the volatile (image) cache based on the most recent used images
|
|
206
|
+
* and starts purging from the oldest ones.
|
|
207
|
+
* Note: for a volume, if the volume-related image Ids is provided, it starts
|
|
208
|
+
* by purging the none-related image Ids (those that are not related to the
|
|
209
|
+
* current volume)
|
|
210
|
+
* 2) For a volume, if we purge all images that won't be included in this volume and still
|
|
211
|
+
* don't have enough unallocated space, purge images that will be included
|
|
212
|
+
* in this volume until we have enough space. These will need to be
|
|
213
|
+
* re-fetched, but we must do this not to straddle over the given memory
|
|
214
|
+
* limit, even for a short time, as this may crash the application.
|
|
215
|
+
*
|
|
216
|
+
* @fires Events.IMAGE_CACHE_IMAGE_REMOVED
|
|
217
|
+
*
|
|
218
|
+
* @param numBytes - Number of bytes for the image/volume that is
|
|
219
|
+
* going to be stored inside the cache
|
|
220
|
+
* @param volumeImageIds - list of imageIds that correspond to the
|
|
221
|
+
* volume whose numberOfBytes we want to store in the cache.
|
|
222
|
+
* @returns bytesAvailable or undefined in purging cache
|
|
223
|
+
* does not successfully make enough space for the requested number of bytes
|
|
224
|
+
*/
|
|
225
|
+
public decacheIfNecessaryUntilBytesAvailable(
|
|
226
|
+
numBytes: number,
|
|
227
|
+
volumeImageIds?: Array<string>
|
|
228
|
+
): number | undefined {
|
|
229
|
+
let bytesAvailable = this.getBytesAvailable();
|
|
230
|
+
|
|
231
|
+
// If max cache size has not been exceeded, do nothing
|
|
232
|
+
if (bytesAvailable >= numBytes) {
|
|
233
|
+
return bytesAvailable;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let cachedImages = Array.from(this._imageCache.values());
|
|
237
|
+
|
|
238
|
+
// Cache size has been exceeded, create list of images sorted by timeStamp
|
|
239
|
+
// So we can purge the least recently used image
|
|
240
|
+
function compare(a, b) {
|
|
241
|
+
if (a.timeStamp > b.timeStamp) {
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
if (a.timeStamp < b.timeStamp) {
|
|
245
|
+
return -1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
cachedImages.sort(compare);
|
|
252
|
+
let cachedImageIds = cachedImages.map((im) => im.imageId);
|
|
253
|
+
|
|
254
|
+
let imageIdsToPurge = cachedImageIds;
|
|
255
|
+
|
|
256
|
+
// if we are making space for a volume, we start by purging the imageIds
|
|
257
|
+
// that are not related to the volume
|
|
258
|
+
if (volumeImageIds) {
|
|
259
|
+
imageIdsToPurge = cachedImageIds.filter(
|
|
260
|
+
(id) => !volumeImageIds.includes(id)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Remove images (that are not related to the volume) from volatile cache
|
|
265
|
+
// until the requested number of bytes become available
|
|
266
|
+
for (const imageId of imageIdsToPurge) {
|
|
267
|
+
this.removeImageLoadObject(imageId);
|
|
268
|
+
|
|
269
|
+
triggerEvent(eventTarget, Events.IMAGE_CACHE_IMAGE_REMOVED, { imageId });
|
|
270
|
+
|
|
271
|
+
bytesAvailable = this.getBytesAvailable();
|
|
272
|
+
if (bytesAvailable >= numBytes) {
|
|
273
|
+
return bytesAvailable;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Remove the imageIds (both volume related and not related)
|
|
278
|
+
cachedImages = Array.from(this._imageCache.values());
|
|
279
|
+
cachedImageIds = cachedImages.map((im) => im.imageId);
|
|
280
|
+
|
|
281
|
+
// Remove volume-image Ids from volatile cache until the requested number of bytes
|
|
282
|
+
// become available
|
|
283
|
+
for (const imageId of cachedImageIds) {
|
|
284
|
+
this.removeImageLoadObject(imageId);
|
|
285
|
+
|
|
286
|
+
triggerEvent(eventTarget, Events.IMAGE_CACHE_IMAGE_REMOVED, { imageId });
|
|
287
|
+
|
|
288
|
+
bytesAvailable = this.getBytesAvailable();
|
|
289
|
+
if (bytesAvailable >= numBytes) {
|
|
290
|
+
return bytesAvailable;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Technically we should not reach here, since isCacheable will throw an
|
|
295
|
+
// error if unallocated + volatile (image) cache cannot fit the upcoming
|
|
296
|
+
// number of bytes
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Puts a new image load object into the cache
|
|
301
|
+
*
|
|
302
|
+
* First, it creates a CachedImage object and put it inside the imageCache for
|
|
303
|
+
* the imageId. After the imageLoadObject promise resolves to an image,
|
|
304
|
+
* it: 1) adds the image into the correct CachedImage object 2) increments the
|
|
305
|
+
* cache size, 3) triggers IMAGE_CACHE_IMAGE_ADDED 4) Purge the cache if
|
|
306
|
+
* necessary -- if the cache size is greater than the maximum cache size, it
|
|
307
|
+
* iterates over the imageCache and decache them one by one until the cache
|
|
308
|
+
* size becomes less than the maximum allowed cache size
|
|
309
|
+
*
|
|
310
|
+
* @fires Events.IMAGE_CACHE_IMAGE_ADDED
|
|
311
|
+
* @fires Events.CACHE_SIZE_EXCEEDED if the cache size exceeds the maximum
|
|
312
|
+
*
|
|
313
|
+
* @param imageId - ImageId for the image
|
|
314
|
+
* @param imageLoadObject - The object that is loading or loaded the image
|
|
315
|
+
*/
|
|
316
|
+
public putImageLoadObject(
|
|
317
|
+
imageId: string,
|
|
318
|
+
imageLoadObject: IImageLoadObject
|
|
319
|
+
): Promise<any> {
|
|
320
|
+
if (imageId === undefined) {
|
|
321
|
+
throw new Error('putImageLoadObject: imageId must not be undefined');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (imageLoadObject.promise === undefined) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
'putImageLoadObject: imageLoadObject.promise must not be undefined'
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (this._imageCache.has(imageId)) {
|
|
331
|
+
throw new Error('putImageLoadObject: imageId already in cache');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (
|
|
335
|
+
imageLoadObject.cancelFn &&
|
|
336
|
+
typeof imageLoadObject.cancelFn !== 'function'
|
|
337
|
+
) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
'putImageLoadObject: imageLoadObject.cancel must be a function'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const cachedImage: ICachedImage = {
|
|
344
|
+
loaded: false,
|
|
345
|
+
imageId,
|
|
346
|
+
sharedCacheKey: undefined, // The sharedCacheKey for this imageId. undefined by default
|
|
347
|
+
imageLoadObject,
|
|
348
|
+
timeStamp: Date.now(),
|
|
349
|
+
sizeInBytes: 0,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
this._imageCache.set(imageId, cachedImage);
|
|
353
|
+
|
|
354
|
+
return imageLoadObject.promise
|
|
355
|
+
.then((image: IImage) => {
|
|
356
|
+
if (!this._imageCache.get(imageId)) {
|
|
357
|
+
// If the image has been purged before being loaded, we stop here.
|
|
358
|
+
console.warn(
|
|
359
|
+
'The image was purged from the cache before it completed loading.'
|
|
360
|
+
);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (Number.isNaN(image.sizeInBytes)) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
'putImageLoadObject: image.sizeInBytes must not be undefined'
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (image.sizeInBytes.toFixed === undefined) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
'putImageLoadObject: image.sizeInBytes is not a number'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// check if there is enough space in unallocated + image Cache
|
|
376
|
+
if (!this.isCacheable(image.sizeInBytes)) {
|
|
377
|
+
throw new Error(Events.CACHE_SIZE_EXCEEDED);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// if there is, decache if necessary
|
|
381
|
+
this.decacheIfNecessaryUntilBytesAvailable(image.sizeInBytes);
|
|
382
|
+
|
|
383
|
+
cachedImage.loaded = true;
|
|
384
|
+
cachedImage.image = image;
|
|
385
|
+
cachedImage.sizeInBytes = image.sizeInBytes;
|
|
386
|
+
this._incrementImageCacheSize(cachedImage.sizeInBytes);
|
|
387
|
+
|
|
388
|
+
const eventDetails: EventTypes.ImageCacheImageAddedEventDetail = {
|
|
389
|
+
image: cachedImage,
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
triggerEvent(eventTarget, Events.IMAGE_CACHE_IMAGE_ADDED, eventDetails);
|
|
393
|
+
|
|
394
|
+
cachedImage.sharedCacheKey = image.sharedCacheKey;
|
|
395
|
+
})
|
|
396
|
+
.catch((error) => {
|
|
397
|
+
// console.warn(error)
|
|
398
|
+
this._imageCache.delete(imageId);
|
|
399
|
+
throw error;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Returns the object that is loading a given imageId
|
|
405
|
+
*
|
|
406
|
+
* @param imageId - Image ID
|
|
407
|
+
* @returns IImageLoadObject
|
|
408
|
+
*/
|
|
409
|
+
public getImageLoadObject(imageId: string): IImageLoadObject {
|
|
410
|
+
if (imageId === undefined) {
|
|
411
|
+
throw new Error('getImageLoadObject: imageId must not be undefined');
|
|
412
|
+
}
|
|
413
|
+
const cachedImage = this._imageCache.get(imageId);
|
|
414
|
+
|
|
415
|
+
if (cachedImage === undefined) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Bump time stamp for cached image
|
|
420
|
+
cachedImage.timeStamp = Date.now();
|
|
421
|
+
|
|
422
|
+
return cachedImage.imageLoadObject;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* It checks the imageCache for the provided imageId, and returns true
|
|
427
|
+
* if the image is loaded, false otherwise. Note, this only checks the imageCache
|
|
428
|
+
* and does not check the volume cache.
|
|
429
|
+
* @param imageId - image Id to check
|
|
430
|
+
* @returns boolean
|
|
431
|
+
*/
|
|
432
|
+
public isImageIdCached(imageId: string): boolean {
|
|
433
|
+
const cachedImage = this._imageCache.get(imageId);
|
|
434
|
+
|
|
435
|
+
if (!cachedImage) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return cachedImage.loaded;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Returns the volume that contains the requested imageId. It will check the
|
|
444
|
+
* imageIds inside the volume to find a match.
|
|
445
|
+
*
|
|
446
|
+
* @param imageId - ImageId
|
|
447
|
+
* @returns - Volume object
|
|
448
|
+
*/
|
|
449
|
+
public getVolumeContainingImageId(imageId: string): {
|
|
450
|
+
volume: IImageVolume;
|
|
451
|
+
imageIdIndex: number;
|
|
452
|
+
} {
|
|
453
|
+
const volumeIds = Array.from(this._volumeCache.keys());
|
|
454
|
+
const imageIdToUse = imageIdToURI(imageId);
|
|
455
|
+
|
|
456
|
+
for (const volumeId of volumeIds) {
|
|
457
|
+
const cachedVolume = this._volumeCache.get(volumeId);
|
|
458
|
+
const { volume } = cachedVolume;
|
|
459
|
+
|
|
460
|
+
if (!volume?.imageIds?.length) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const imageIdIndex = volume.getImageURIIndex(imageIdToUse);
|
|
465
|
+
|
|
466
|
+
if (imageIdIndex > -1) {
|
|
467
|
+
return { volume, imageIdIndex };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Returns the cached image from the imageCache for the requested imageId.
|
|
474
|
+
* It first strips the imageId to remove the data loading scheme.
|
|
475
|
+
*
|
|
476
|
+
* @param imageId - Image ID
|
|
477
|
+
* @returns cached image
|
|
478
|
+
*/
|
|
479
|
+
public getCachedImageBasedOnImageURI(
|
|
480
|
+
imageId: string
|
|
481
|
+
): ICachedImage | undefined {
|
|
482
|
+
const imageURIToUse = imageIdToURI(imageId);
|
|
483
|
+
|
|
484
|
+
const cachedImageIds = Array.from(this._imageCache.keys());
|
|
485
|
+
const foundImageId = cachedImageIds.find((imageId) => {
|
|
486
|
+
return imageIdToURI(imageId) === imageURIToUse;
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (!foundImageId) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return this._imageCache.get(foundImageId);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Puts a new image load object into the cache
|
|
497
|
+
*
|
|
498
|
+
* First, it creates a CachedVolume object and put it inside the volumeCache for
|
|
499
|
+
* the volumeId. After the volumeLoadObject promise resolves to a volume,
|
|
500
|
+
* it: 1) adds the volume into the correct CachedVolume object inside volumeCache
|
|
501
|
+
* 2) increments the cache size, 3) triggers VOLUME_CACHE_VOLUME_ADDED 4) Purge
|
|
502
|
+
* the cache if necessary -- if the cache size is greater than the maximum cache size, it
|
|
503
|
+
* iterates over the imageCache (not volumeCache) and decache them one by one
|
|
504
|
+
* until the cache size becomes less than the maximum allowed cache size
|
|
505
|
+
*
|
|
506
|
+
* @fires Events.VOLUME_CACHE_VOLUME_ADDED
|
|
507
|
+
*
|
|
508
|
+
* @param volumeId - volumeId of the volume
|
|
509
|
+
* @param volumeLoadObject - The object that is loading or loaded the volume
|
|
510
|
+
*/
|
|
511
|
+
public putVolumeLoadObject(
|
|
512
|
+
volumeId: string,
|
|
513
|
+
volumeLoadObject: IVolumeLoadObject
|
|
514
|
+
): Promise<any> {
|
|
515
|
+
if (volumeId === undefined) {
|
|
516
|
+
throw new Error('putVolumeLoadObject: volumeId must not be undefined');
|
|
517
|
+
}
|
|
518
|
+
if (volumeLoadObject.promise === undefined) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
'putVolumeLoadObject: volumeLoadObject.promise must not be undefined'
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (this._volumeCache.has(volumeId)) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`putVolumeLoadObject: volumeId:${volumeId} already in cache`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (
|
|
529
|
+
volumeLoadObject.cancelFn &&
|
|
530
|
+
typeof volumeLoadObject.cancelFn !== 'function'
|
|
531
|
+
) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
'putVolumeLoadObject: volumeLoadObject.cancel must be a function'
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// todo: @Erik there are two loaded flags, one inside cachedVolume and the other
|
|
538
|
+
// inside the volume.loadStatus.loaded, the actual all pixelData loaded is the
|
|
539
|
+
// loadStatus one. This causes confusion
|
|
540
|
+
const cachedVolume: ICachedVolume = {
|
|
541
|
+
loaded: false,
|
|
542
|
+
volumeId,
|
|
543
|
+
volumeLoadObject,
|
|
544
|
+
timeStamp: Date.now(),
|
|
545
|
+
sizeInBytes: 0,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
this._volumeCache.set(volumeId, cachedVolume);
|
|
549
|
+
|
|
550
|
+
return volumeLoadObject.promise
|
|
551
|
+
.then((volume: IImageVolume) => {
|
|
552
|
+
if (!this._volumeCache.get(volumeId)) {
|
|
553
|
+
// If the image has been purged before being loaded, we stop here.
|
|
554
|
+
console.warn(
|
|
555
|
+
'The image was purged from the cache before it completed loading.'
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (Number.isNaN(volume.sizeInBytes)) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
'putVolumeLoadObject: volume.sizeInBytes must not be undefined'
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (volume.sizeInBytes.toFixed === undefined) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
'putVolumeLoadObject: volume.sizeInBytes is not a number'
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// this.isCacheable is called at the volume loader, before requesting
|
|
572
|
+
// the images of the volume
|
|
573
|
+
|
|
574
|
+
this.decacheIfNecessaryUntilBytesAvailable(
|
|
575
|
+
volume.sizeInBytes,
|
|
576
|
+
// @ts-ignore: // todo ImageVolume does not have imageIds
|
|
577
|
+
volume.imageIds
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
// cachedVolume.loaded = true
|
|
581
|
+
cachedVolume.volume = volume;
|
|
582
|
+
cachedVolume.sizeInBytes = volume.sizeInBytes;
|
|
583
|
+
this._incrementVolumeCacheSize(cachedVolume.sizeInBytes);
|
|
584
|
+
|
|
585
|
+
const eventDetails: EventTypes.VolumeCacheVolumeAddedEventDetail = {
|
|
586
|
+
volume: cachedVolume,
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
triggerEvent(
|
|
590
|
+
eventTarget,
|
|
591
|
+
Events.VOLUME_CACHE_VOLUME_ADDED,
|
|
592
|
+
eventDetails
|
|
593
|
+
);
|
|
594
|
+
})
|
|
595
|
+
.catch((error) => {
|
|
596
|
+
this._volumeCache.delete(volumeId);
|
|
597
|
+
throw error;
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Returns the object that is loading a given volumeId
|
|
603
|
+
*
|
|
604
|
+
* @param volumeId - Volume ID
|
|
605
|
+
* @returns IVolumeLoadObject
|
|
606
|
+
*/
|
|
607
|
+
public getVolumeLoadObject = (volumeId: string): IVolumeLoadObject => {
|
|
608
|
+
if (volumeId === undefined) {
|
|
609
|
+
throw new Error('getVolumeLoadObject: volumeId must not be undefined');
|
|
610
|
+
}
|
|
611
|
+
const cachedVolume = this._volumeCache.get(volumeId);
|
|
612
|
+
|
|
613
|
+
if (cachedVolume === undefined) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Bump time stamp for cached volume (not used for anything for now)
|
|
618
|
+
cachedVolume.timeStamp = Date.now();
|
|
619
|
+
|
|
620
|
+
return cachedVolume.volumeLoadObject;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
public getGeometry = (geometryId: string): IGeometry => {
|
|
624
|
+
if (geometryId == null) {
|
|
625
|
+
throw new Error('getGeometry: geometryId must not be undefined');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const cachedGeometry = this._geometryCache.get(geometryId);
|
|
629
|
+
|
|
630
|
+
if (cachedGeometry === undefined) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Bump time stamp for cached geometry (not used for anything for now)
|
|
635
|
+
cachedGeometry.timeStamp = Date.now();
|
|
636
|
+
|
|
637
|
+
return cachedGeometry.geometry;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Returns the volume associated with the volumeId
|
|
642
|
+
*
|
|
643
|
+
* @param volumeId - Volume ID
|
|
644
|
+
* @returns Volume
|
|
645
|
+
*/
|
|
646
|
+
public getVolume = (volumeId: string): IImageVolume => {
|
|
647
|
+
if (volumeId === undefined) {
|
|
648
|
+
throw new Error('getVolume: volumeId must not be undefined');
|
|
649
|
+
}
|
|
650
|
+
const cachedVolume = this._volumeCache.get(volumeId);
|
|
651
|
+
|
|
652
|
+
if (cachedVolume === undefined) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Bump time stamp for cached volume (not used for anything for now)
|
|
657
|
+
cachedVolume.timeStamp = Date.now();
|
|
658
|
+
|
|
659
|
+
return cachedVolume.volume;
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Removes the image loader associated with a given Id from the cache
|
|
664
|
+
*
|
|
665
|
+
* It increases the cache size after removing the image.
|
|
666
|
+
*
|
|
667
|
+
* @fires Events.IMAGE_CACHE_IMAGE_REMOVED
|
|
668
|
+
*
|
|
669
|
+
* @param imageId - Image ID
|
|
670
|
+
*/
|
|
671
|
+
public removeImageLoadObject = (imageId: string): void => {
|
|
672
|
+
if (imageId === undefined) {
|
|
673
|
+
throw new Error('removeImageLoadObject: imageId must not be undefined');
|
|
674
|
+
}
|
|
675
|
+
const cachedImage = this._imageCache.get(imageId);
|
|
676
|
+
|
|
677
|
+
if (cachedImage === undefined) {
|
|
678
|
+
throw new Error(
|
|
679
|
+
'removeImageLoadObject: imageId was not present in imageCache'
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
this._incrementImageCacheSize(-cachedImage.sizeInBytes);
|
|
684
|
+
|
|
685
|
+
const eventDetails = {
|
|
686
|
+
imageId,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
triggerEvent(eventTarget, Events.IMAGE_CACHE_IMAGE_REMOVED, eventDetails);
|
|
690
|
+
this._decacheImage(imageId);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Removes the volume loader associated with a given Id from the cache
|
|
695
|
+
*
|
|
696
|
+
* It increases the cache size after removing the image.
|
|
697
|
+
*
|
|
698
|
+
* @fires Events.VOLUME_CACHE_VOLUME_REMOVED
|
|
699
|
+
*
|
|
700
|
+
* @param imageId - ImageId
|
|
701
|
+
*/
|
|
702
|
+
public removeVolumeLoadObject = (volumeId: string): void => {
|
|
703
|
+
if (volumeId === undefined) {
|
|
704
|
+
throw new Error('removeVolumeLoadObject: volumeId must not be undefined');
|
|
705
|
+
}
|
|
706
|
+
const cachedVolume = this._volumeCache.get(volumeId);
|
|
707
|
+
|
|
708
|
+
if (cachedVolume === undefined) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
'removeVolumeLoadObject: volumeId was not present in volumeCache'
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
this._incrementVolumeCacheSize(-cachedVolume.sizeInBytes);
|
|
715
|
+
|
|
716
|
+
const eventDetails = {
|
|
717
|
+
volume: cachedVolume,
|
|
718
|
+
volumeId,
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
triggerEvent(eventTarget, Events.VOLUME_CACHE_VOLUME_REMOVED, eventDetails);
|
|
722
|
+
this._decacheVolume(volumeId);
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
putGeometryLoadObject = (
|
|
726
|
+
geometryId: string,
|
|
727
|
+
geometryLoadObject: IGeometryLoadObject
|
|
728
|
+
): Promise<void> => {
|
|
729
|
+
if (geometryId == undefined) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
'putGeometryLoadObject: geometryId must not be undefined'
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (this._geometryCache.has(geometryId)) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
'putGeometryLoadObject: geometryId already present in geometryCache'
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const cachedGeometry: ICachedGeometry = {
|
|
742
|
+
geometryId,
|
|
743
|
+
geometryLoadObject,
|
|
744
|
+
loaded: false,
|
|
745
|
+
timeStamp: Date.now(),
|
|
746
|
+
sizeInBytes: 0,
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
this._geometryCache.set(geometryId, cachedGeometry);
|
|
750
|
+
|
|
751
|
+
return geometryLoadObject.promise
|
|
752
|
+
.then((geometry: IGeometry) => {
|
|
753
|
+
if (!this._geometryCache.has(geometryId)) {
|
|
754
|
+
console.warn(
|
|
755
|
+
'putGeometryLoadObject: geometryId was removed from geometryCache'
|
|
756
|
+
);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (Number.isNaN(geometry.sizeInBytes)) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
'putGeometryLoadObject: geometry.sizeInBytes is not a number'
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Todo: fix is cacheable
|
|
767
|
+
|
|
768
|
+
cachedGeometry.loaded = true;
|
|
769
|
+
cachedGeometry.geometry = geometry;
|
|
770
|
+
cachedGeometry.sizeInBytes = geometry.sizeInBytes;
|
|
771
|
+
|
|
772
|
+
// this._incrementGeometryCacheSize(geometry.sizeInBytes);
|
|
773
|
+
|
|
774
|
+
const eventDetails = {
|
|
775
|
+
geometry,
|
|
776
|
+
geometryId,
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
triggerEvent(
|
|
780
|
+
eventTarget,
|
|
781
|
+
Events.GEOMETRY_CACHE_GEOMETRY_ADDED,
|
|
782
|
+
eventDetails
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
return;
|
|
786
|
+
})
|
|
787
|
+
.catch((error) => {
|
|
788
|
+
this._geometryCache.delete(geometryId);
|
|
789
|
+
throw error;
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Increases the image cache size with the provided increment
|
|
795
|
+
*
|
|
796
|
+
* @param increment - bytes length
|
|
797
|
+
*/
|
|
798
|
+
private _incrementImageCacheSize = (increment: number) => {
|
|
799
|
+
this._imageCacheSize += increment;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Increases the cache size with the provided increment
|
|
804
|
+
*
|
|
805
|
+
* @param increment - bytes length
|
|
806
|
+
*/
|
|
807
|
+
private _incrementVolumeCacheSize = (increment: number) => {
|
|
808
|
+
this._volumeCacheSize += increment;
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* This module deals with Caching of images and volumes
|
|
814
|
+
* The cache has two main components: a volatile portion for images and a
|
|
815
|
+
* non-volatile portion for volumes. Individual 2D images are volatile and
|
|
816
|
+
* will be replaced by new images hitting the cache. When you allocate volumes,
|
|
817
|
+
* these are non-volatile and reserve a block of memory from the cache.
|
|
818
|
+
* Volumes must be released manually.
|
|
819
|
+
* We will have a shared block of memory allocated for the entire cache, e.g. 1GB
|
|
820
|
+
* which will be shared for images and volumes.
|
|
821
|
+
*
|
|
822
|
+
* **When a new image is added:**
|
|
823
|
+
* We check if there is enough unallocated + volatile space for the single image
|
|
824
|
+
*
|
|
825
|
+
* if so
|
|
826
|
+
* - We allocate the image in image cache, and if necessary oldest images
|
|
827
|
+
* are decached to match the maximumCacheSize criteria
|
|
828
|
+
* - If a volume contains that imageId, copy it over using TypedArray's set method.
|
|
829
|
+
* If no volumes contain the imageId, the image is fetched by image loaders
|
|
830
|
+
*
|
|
831
|
+
* If not (cache is mostly/completely full with volumes)
|
|
832
|
+
* - throw that the cache does not have enough working space to allocate the image
|
|
833
|
+
*
|
|
834
|
+
*
|
|
835
|
+
* **When a new volume is added:**
|
|
836
|
+
* Check if there is enough unallocated + volatile space to allocate the volume:
|
|
837
|
+
*
|
|
838
|
+
* If so:
|
|
839
|
+
* - Decache oldest images which won't be included in this volume until
|
|
840
|
+
* we have enough free space for the volume
|
|
841
|
+
* - If not enough space from previous space, decache images that will be included
|
|
842
|
+
* in the volume until we have enough free space (These will need to be re-fetched,
|
|
843
|
+
* but we must do this not to straddle over the given memory limit, even for a
|
|
844
|
+
* short time, as this may crash the app)
|
|
845
|
+
* - At this point, if any of the frames (indexed by imageId) are present in the volatile
|
|
846
|
+
* image cache, copy these over to the volume now
|
|
847
|
+
*
|
|
848
|
+
* If not (cache is mostly/completely full with volumes),
|
|
849
|
+
* - throw that the cache does not have enough working space to allocate the volume.
|
|
850
|
+
*
|
|
851
|
+
*/
|
|
852
|
+
const cache = new Cache();
|
|
853
|
+
export default cache;
|
|
854
|
+
export { Cache }; // for documentation
|