@cornerstonejs/core 0.36.3 → 0.36.5

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.
Files changed (229) hide show
  1. package/dist/cjs/RenderingEngine/VolumeViewport3D.d.ts +1 -0
  2. package/dist/cjs/RenderingEngine/VolumeViewport3D.js +3 -0
  3. package/dist/cjs/RenderingEngine/VolumeViewport3D.js.map +1 -1
  4. package/dist/cjs/utilities/getSliceRange.js +2 -1
  5. package/dist/cjs/utilities/getSliceRange.js.map +1 -1
  6. package/dist/esm/RenderingEngine/VolumeViewport3D.d.ts +1 -0
  7. package/dist/esm/RenderingEngine/VolumeViewport3D.js +3 -0
  8. package/dist/esm/RenderingEngine/VolumeViewport3D.js.map +1 -1
  9. package/dist/esm/utilities/getSliceRange.js +2 -1
  10. package/dist/esm/utilities/getSliceRange.js.map +1 -1
  11. package/dist/umd/index.js +1 -1
  12. package/dist/umd/index.js.map +1 -1
  13. package/package.json +4 -3
  14. package/src/RenderingEngine/BaseVolumeViewport.ts +847 -0
  15. package/src/RenderingEngine/RenderingEngine.ts +1364 -0
  16. package/src/RenderingEngine/StackViewport.ts +2690 -0
  17. package/src/RenderingEngine/Viewport.ts +1244 -0
  18. package/src/RenderingEngine/VolumeViewport.ts +420 -0
  19. package/src/RenderingEngine/VolumeViewport3D.ts +46 -0
  20. package/src/RenderingEngine/getRenderingEngine.ts +34 -0
  21. package/src/RenderingEngine/helpers/addVolumesToViewports.ts +52 -0
  22. package/src/RenderingEngine/helpers/cpuFallback/colors/colormap.ts +343 -0
  23. package/src/RenderingEngine/helpers/cpuFallback/colors/index.ts +4 -0
  24. package/src/RenderingEngine/helpers/cpuFallback/colors/lookupTable.ts +469 -0
  25. package/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts +58 -0
  26. package/src/RenderingEngine/helpers/cpuFallback/rendering/calculateTransform.ts +136 -0
  27. package/src/RenderingEngine/helpers/cpuFallback/rendering/canvasToPixel.ts +25 -0
  28. package/src/RenderingEngine/helpers/cpuFallback/rendering/computeAutoVoi.ts +47 -0
  29. package/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts +38 -0
  30. package/src/RenderingEngine/helpers/cpuFallback/rendering/createViewport.ts +64 -0
  31. package/src/RenderingEngine/helpers/cpuFallback/rendering/doesImageNeedToBeRendered.ts +36 -0
  32. package/src/RenderingEngine/helpers/cpuFallback/rendering/fitToWindow.ts +22 -0
  33. package/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts +60 -0
  34. package/src/RenderingEngine/helpers/cpuFallback/rendering/generateLut.ts +83 -0
  35. package/src/RenderingEngine/helpers/cpuFallback/rendering/getDefaultViewport.ts +88 -0
  36. package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageFitScale.ts +52 -0
  37. package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageSize.ts +55 -0
  38. package/src/RenderingEngine/helpers/cpuFallback/rendering/getLut.ts +53 -0
  39. package/src/RenderingEngine/helpers/cpuFallback/rendering/getModalityLut.ts +55 -0
  40. package/src/RenderingEngine/helpers/cpuFallback/rendering/getTransform.ts +17 -0
  41. package/src/RenderingEngine/helpers/cpuFallback/rendering/getVOILut.ts +74 -0
  42. package/src/RenderingEngine/helpers/cpuFallback/rendering/initializeRenderCanvas.ts +37 -0
  43. package/src/RenderingEngine/helpers/cpuFallback/rendering/lutMatches.ts +21 -0
  44. package/src/RenderingEngine/helpers/cpuFallback/rendering/now.ts +13 -0
  45. package/src/RenderingEngine/helpers/cpuFallback/rendering/pixelToCanvas.ts +22 -0
  46. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts +193 -0
  47. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderGrayscaleImage.ts +166 -0
  48. package/src/RenderingEngine/helpers/cpuFallback/rendering/renderPseudoColorImage.ts +203 -0
  49. package/src/RenderingEngine/helpers/cpuFallback/rendering/resetCamera.ts +32 -0
  50. package/src/RenderingEngine/helpers/cpuFallback/rendering/resize.ts +109 -0
  51. package/src/RenderingEngine/helpers/cpuFallback/rendering/saveLastRendered.ts +36 -0
  52. package/src/RenderingEngine/helpers/cpuFallback/rendering/setDefaultViewport.ts +17 -0
  53. package/src/RenderingEngine/helpers/cpuFallback/rendering/setToPixelCoordinateSystem.ts +32 -0
  54. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedColorPixelDataToCanvasImageData.ts +58 -0
  55. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageData.ts +76 -0
  56. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataColorLUT.ts +60 -0
  57. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPET.ts +50 -0
  58. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUT.ts +66 -0
  59. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUTPET.ts +68 -0
  60. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataRGBA.ts +81 -0
  61. package/src/RenderingEngine/helpers/cpuFallback/rendering/storedRGBAPixelDataToCanvasImageData.ts +56 -0
  62. package/src/RenderingEngine/helpers/cpuFallback/rendering/transform.ts +126 -0
  63. package/src/RenderingEngine/helpers/cpuFallback/rendering/validator.ts +31 -0
  64. package/src/RenderingEngine/helpers/createVolumeActor.ts +103 -0
  65. package/src/RenderingEngine/helpers/createVolumeMapper.ts +37 -0
  66. package/src/RenderingEngine/helpers/getOrCreateCanvas.ts +58 -0
  67. package/src/RenderingEngine/helpers/index.ts +15 -0
  68. package/src/RenderingEngine/helpers/isRgbaSourceRgbDest.ts +1 -0
  69. package/src/RenderingEngine/helpers/setDefaultVolumeVOI.ts +227 -0
  70. package/src/RenderingEngine/helpers/setVolumesForViewports.ts +52 -0
  71. package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +14 -0
  72. package/src/RenderingEngine/helpers/viewportTypeUsesCustomRenderingPipeline.ts +7 -0
  73. package/src/RenderingEngine/helpers/volumeNewImageEventDispatcher.ts +75 -0
  74. package/src/RenderingEngine/index.ts +23 -0
  75. package/src/RenderingEngine/renderingEngineCache.ts +43 -0
  76. package/src/RenderingEngine/vtkClasses/index.js +11 -0
  77. package/src/RenderingEngine/vtkClasses/vtkOffscreenMultiRenderWindow.js +149 -0
  78. package/src/RenderingEngine/vtkClasses/vtkSharedVolumeMapper.js +52 -0
  79. package/src/RenderingEngine/vtkClasses/vtkSlabCamera.d.ts +781 -0
  80. package/src/RenderingEngine/vtkClasses/vtkSlabCamera.js +155 -0
  81. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLRenderWindow.js +47 -0
  82. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLTexture.js +272 -0
  83. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLViewNodeFactory.js +159 -0
  84. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +319 -0
  85. package/src/Settings.ts +294 -0
  86. package/src/cache/cache.ts +854 -0
  87. package/src/cache/classes/Contour.ts +70 -0
  88. package/src/cache/classes/ContourSet.ts +151 -0
  89. package/src/cache/classes/ImageVolume.ts +155 -0
  90. package/src/cache/index.ts +5 -0
  91. package/src/constants/cpuColormaps.ts +1537 -0
  92. package/src/constants/epsilon.ts +3 -0
  93. package/src/constants/index.ts +13 -0
  94. package/src/constants/mprCameraValues.ts +20 -0
  95. package/src/constants/rendering.ts +8 -0
  96. package/src/constants/viewportPresets.ts +357 -0
  97. package/src/enums/BlendModes.ts +23 -0
  98. package/src/enums/ContourType.ts +6 -0
  99. package/src/enums/Events.ts +196 -0
  100. package/src/enums/GeometryType.ts +5 -0
  101. package/src/enums/InterpolationType.ts +13 -0
  102. package/src/enums/OrientationAxis.ts +8 -0
  103. package/src/enums/RequestType.ts +13 -0
  104. package/src/enums/SharedArrayBufferModes.ts +11 -0
  105. package/src/enums/VOILUTFunctionType.ts +10 -0
  106. package/src/enums/ViewportType.ts +21 -0
  107. package/src/enums/index.ts +23 -0
  108. package/src/eventTarget.ts +67 -0
  109. package/src/getEnabledElement.ts +105 -0
  110. package/src/global.ts +8 -0
  111. package/src/index.ts +123 -0
  112. package/src/init.ts +247 -0
  113. package/src/loaders/geometryLoader.ts +108 -0
  114. package/src/loaders/imageLoader.ts +298 -0
  115. package/src/loaders/volumeLoader.ts +477 -0
  116. package/src/metaData.ts +84 -0
  117. package/src/requestPool/imageLoadPoolManager.ts +43 -0
  118. package/src/requestPool/imageRetrievalPoolManager.ts +25 -0
  119. package/src/requestPool/requestPoolManager.ts +329 -0
  120. package/src/types/ActorSliceRange.ts +17 -0
  121. package/src/types/CPUFallbackColormap.ts +23 -0
  122. package/src/types/CPUFallbackColormapData.ts +12 -0
  123. package/src/types/CPUFallbackColormapsData.ts +7 -0
  124. package/src/types/CPUFallbackEnabledElement.ts +71 -0
  125. package/src/types/CPUFallbackLUT.ts +5 -0
  126. package/src/types/CPUFallbackLookupTable.ts +17 -0
  127. package/src/types/CPUFallbackRenderingTools.ts +25 -0
  128. package/src/types/CPUFallbackTransform.ts +16 -0
  129. package/src/types/CPUFallbackViewport.ts +29 -0
  130. package/src/types/CPUFallbackViewportDisplayedArea.ts +15 -0
  131. package/src/types/CPUIImageData.ts +47 -0
  132. package/src/types/ContourData.ts +19 -0
  133. package/src/types/Cornerstone3DConfig.ts +31 -0
  134. package/src/types/CustomEventType.ts +14 -0
  135. package/src/types/EventTypes.ts +403 -0
  136. package/src/types/FlipDirection.ts +9 -0
  137. package/src/types/IActor.ts +23 -0
  138. package/src/types/ICache.ts +28 -0
  139. package/src/types/ICachedGeometry.ts +13 -0
  140. package/src/types/ICachedImage.ts +13 -0
  141. package/src/types/ICachedVolume.ts +12 -0
  142. package/src/types/ICamera.ts +36 -0
  143. package/src/types/IContour.ts +18 -0
  144. package/src/types/IContourSet.ts +56 -0
  145. package/src/types/IDynamicImageVolume.ts +18 -0
  146. package/src/types/IEnabledElement.ts +21 -0
  147. package/src/types/IGeometry.ts +12 -0
  148. package/src/types/IImage.ts +113 -0
  149. package/src/types/IImageData.ts +45 -0
  150. package/src/types/IImageVolume.ts +78 -0
  151. package/src/types/ILoadObject.ts +36 -0
  152. package/src/types/IRegisterImageLoader.ts +10 -0
  153. package/src/types/IRenderingEngine.ts +28 -0
  154. package/src/types/IStackViewport.ts +138 -0
  155. package/src/types/IStreamingImageVolume.ts +13 -0
  156. package/src/types/IStreamingVolumeProperties.ts +14 -0
  157. package/src/types/IViewport.ts +149 -0
  158. package/src/types/IViewportId.ts +9 -0
  159. package/src/types/IVolume.ts +45 -0
  160. package/src/types/IVolumeInput.ts +36 -0
  161. package/src/types/IVolumeViewport.ts +141 -0
  162. package/src/types/ImageLoaderFn.ts +16 -0
  163. package/src/types/ImageSliceData.ts +6 -0
  164. package/src/types/Mat3.ts +16 -0
  165. package/src/types/Metadata.ts +39 -0
  166. package/src/types/OrientationVectors.ts +36 -0
  167. package/src/types/Plane.ts +6 -0
  168. package/src/types/Point2.ts +6 -0
  169. package/src/types/Point3.ts +6 -0
  170. package/src/types/Point4.ts +6 -0
  171. package/src/types/ScalingParameters.ts +27 -0
  172. package/src/types/StackViewportProperties.ts +25 -0
  173. package/src/types/TransformMatrix2D.ts +4 -0
  174. package/src/types/ViewportInputOptions.ts +21 -0
  175. package/src/types/ViewportPreset.ts +14 -0
  176. package/src/types/VolumeLoaderFn.ts +18 -0
  177. package/src/types/VolumeViewportProperties.ts +14 -0
  178. package/src/types/index.ts +157 -0
  179. package/src/types/voi.ts +15 -0
  180. package/src/utilities/actorCheck.ts +24 -0
  181. package/src/utilities/applyPreset.ts +132 -0
  182. package/src/utilities/calculateViewportsSpatialRegistration.ts +74 -0
  183. package/src/utilities/calibratedPixelSpacingMetadataProvider.ts +38 -0
  184. package/src/utilities/createFloat32SharedArray.ts +45 -0
  185. package/src/utilities/createInt16SharedArray.ts +43 -0
  186. package/src/utilities/createLinearRGBTransferFunction.ts +22 -0
  187. package/src/utilities/createSigmoidRGBTransferFunction.ts +63 -0
  188. package/src/utilities/createUInt16SharedArray.ts +43 -0
  189. package/src/utilities/createUint8SharedArray.ts +45 -0
  190. package/src/utilities/deepFreeze.ts +19 -0
  191. package/src/utilities/deepMerge.ts +81 -0
  192. package/src/utilities/getClosestImageId.ts +80 -0
  193. package/src/utilities/getClosestStackImageIndexForPoint.ts +116 -0
  194. package/src/utilities/getImageSliceDataForVolumeViewport.ts +61 -0
  195. package/src/utilities/getMinMax.ts +31 -0
  196. package/src/utilities/getRuntimeId.ts +54 -0
  197. package/src/utilities/getScalarDataType.ts +31 -0
  198. package/src/utilities/getScalingParameters.ts +35 -0
  199. package/src/utilities/getSliceRange.ts +86 -0
  200. package/src/utilities/getSpacingInNormalDirection.ts +44 -0
  201. package/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +126 -0
  202. package/src/utilities/getViewportImageCornersInWorld.ts +102 -0
  203. package/src/utilities/getViewportsWithImageURI.ts +46 -0
  204. package/src/utilities/getViewportsWithVolumeId.ts +38 -0
  205. package/src/utilities/getVoiFromSigmoidRGBTransferFunction.ts +23 -0
  206. package/src/utilities/getVolumeActorCorners.ts +24 -0
  207. package/src/utilities/getVolumeSliceRangeInfo.ts +52 -0
  208. package/src/utilities/getVolumeViewportScrollInfo.ts +32 -0
  209. package/src/utilities/getVolumeViewportsContainingSameVolumes.ts +58 -0
  210. package/src/utilities/hasNaNValues.ts +12 -0
  211. package/src/utilities/imageIdToURI.ts +10 -0
  212. package/src/utilities/imageToWorldCoords.ts +54 -0
  213. package/src/utilities/index.ts +100 -0
  214. package/src/utilities/indexWithinDimensions.ts +27 -0
  215. package/src/utilities/invertRgbTransferFunction.ts +36 -0
  216. package/src/utilities/isEqual.ts +27 -0
  217. package/src/utilities/isOpposite.ts +23 -0
  218. package/src/utilities/isTypedArray.ts +20 -0
  219. package/src/utilities/loadImageToCanvas.ts +80 -0
  220. package/src/utilities/planar.ts +91 -0
  221. package/src/utilities/renderToCanvas.ts +32 -0
  222. package/src/utilities/scaleRgbTransferFunction.ts +37 -0
  223. package/src/utilities/snapFocalPointToSlice.ts +78 -0
  224. package/src/utilities/spatialRegistrationMetadataProvider.ts +50 -0
  225. package/src/utilities/transformWorldToIndex.ts +16 -0
  226. package/src/utilities/triggerEvent.ts +38 -0
  227. package/src/utilities/uuidv4.ts +13 -0
  228. package/src/utilities/windowLevel.ts +39 -0
  229. 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