@cornerstonejs/core 1.42.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js +3 -0
  2. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  3. package/dist/cjs/RenderingEngine/StackViewport.js +2 -1
  4. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  5. package/dist/cjs/RenderingEngine/Viewport.d.ts +1 -0
  6. package/dist/cjs/RenderingEngine/Viewport.js +3 -0
  7. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  8. package/dist/cjs/RenderingEngine/helpers/createVolumeActor.js +2 -1
  9. package/dist/cjs/RenderingEngine/helpers/createVolumeActor.js.map +1 -1
  10. package/dist/cjs/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +1 -1
  11. package/dist/cjs/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js.map +1 -1
  12. package/dist/cjs/cache/cache.d.ts +6 -3
  13. package/dist/cjs/cache/cache.js +43 -6
  14. package/dist/cjs/cache/cache.js.map +1 -1
  15. package/dist/cjs/cache/classes/ImageVolume.d.ts +25 -5
  16. package/dist/cjs/cache/classes/ImageVolume.js +302 -13
  17. package/dist/cjs/cache/classes/ImageVolume.js.map +1 -1
  18. package/dist/cjs/eventTarget.d.ts +1 -0
  19. package/dist/cjs/eventTarget.js +13 -1
  20. package/dist/cjs/eventTarget.js.map +1 -1
  21. package/dist/cjs/init.js +2 -0
  22. package/dist/cjs/init.js.map +1 -1
  23. package/dist/cjs/loaders/imageLoader.d.ts +6 -1
  24. package/dist/cjs/loaders/imageLoader.js +26 -12
  25. package/dist/cjs/loaders/imageLoader.js.map +1 -1
  26. package/dist/cjs/loaders/volumeLoader.d.ts +13 -9
  27. package/dist/cjs/loaders/volumeLoader.js +58 -5
  28. package/dist/cjs/loaders/volumeLoader.js.map +1 -1
  29. package/dist/cjs/types/BoundsLPS.d.ts +3 -0
  30. package/dist/cjs/types/BoundsLPS.js +3 -0
  31. package/dist/cjs/types/BoundsLPS.js.map +1 -0
  32. package/dist/cjs/types/Cornerstone3DConfig.d.ts +1 -0
  33. package/dist/cjs/types/IDynamicImageVolume.d.ts +2 -2
  34. package/dist/cjs/types/IImage.d.ts +5 -0
  35. package/dist/cjs/types/IImageVolume.d.ts +7 -2
  36. package/dist/cjs/types/ILoadObject.d.ts +2 -2
  37. package/dist/cjs/types/IViewport.d.ts +1 -0
  38. package/dist/cjs/types/IVolume.d.ts +3 -26
  39. package/dist/cjs/types/ImageVolumeProps.d.ts +6 -0
  40. package/dist/cjs/types/ImageVolumeProps.js +3 -0
  41. package/dist/cjs/types/ImageVolumeProps.js.map +1 -0
  42. package/dist/cjs/types/VolumeProps.d.ts +27 -0
  43. package/dist/cjs/types/VolumeProps.js +3 -0
  44. package/dist/cjs/types/VolumeProps.js.map +1 -0
  45. package/dist/cjs/types/index.d.ts +5 -2
  46. package/dist/cjs/utilities/VoxelManager.d.ts +2 -2
  47. package/dist/cjs/utilities/VoxelManager.js +13 -4
  48. package/dist/cjs/utilities/VoxelManager.js.map +1 -1
  49. package/dist/cjs/utilities/cacheUtils.d.ts +2 -0
  50. package/dist/cjs/utilities/cacheUtils.js +96 -0
  51. package/dist/cjs/utilities/cacheUtils.js.map +1 -0
  52. package/dist/cjs/utilities/convertStackToVolumeViewport.d.ts +12 -0
  53. package/dist/cjs/utilities/convertStackToVolumeViewport.js +61 -0
  54. package/dist/cjs/utilities/convertStackToVolumeViewport.js.map +1 -0
  55. package/dist/cjs/utilities/convertVolumeToStackViewport.d.ts +9 -0
  56. package/dist/cjs/utilities/convertVolumeToStackViewport.js +95 -0
  57. package/dist/cjs/utilities/convertVolumeToStackViewport.js.map +1 -0
  58. package/dist/cjs/utilities/generateVolumePropsFromImageIds.d.ts +3 -0
  59. package/dist/cjs/utilities/generateVolumePropsFromImageIds.js +124 -0
  60. package/dist/cjs/utilities/generateVolumePropsFromImageIds.js.map +1 -0
  61. package/dist/cjs/utilities/index.d.ts +6 -1
  62. package/dist/cjs/utilities/index.js +12 -1
  63. package/dist/cjs/utilities/index.js.map +1 -1
  64. package/dist/cjs/utilities/planar.d.ts +1 -1
  65. package/dist/cjs/utilities/planar.js +5 -1
  66. package/dist/cjs/utilities/planar.js.map +1 -1
  67. package/dist/cjs/utilities/roundNumber.d.ts +4 -0
  68. package/dist/cjs/utilities/roundNumber.js +36 -0
  69. package/dist/cjs/utilities/roundNumber.js.map +1 -0
  70. package/dist/esm/RenderingEngine/BaseVolumeViewport.js +3 -0
  71. package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  72. package/dist/esm/RenderingEngine/StackViewport.js +2 -1
  73. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  74. package/dist/esm/RenderingEngine/Viewport.js +3 -0
  75. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  76. package/dist/esm/RenderingEngine/helpers/createVolumeActor.js +1 -1
  77. package/dist/esm/RenderingEngine/helpers/createVolumeActor.js.map +1 -1
  78. package/dist/esm/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +1 -1
  79. package/dist/esm/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js.map +1 -1
  80. package/dist/esm/cache/cache.js +43 -6
  81. package/dist/esm/cache/cache.js.map +1 -1
  82. package/dist/esm/cache/classes/ImageVolume.js +279 -14
  83. package/dist/esm/cache/classes/ImageVolume.js.map +1 -1
  84. package/dist/esm/eventTarget.js +13 -1
  85. package/dist/esm/eventTarget.js.map +1 -1
  86. package/dist/esm/init.js +2 -0
  87. package/dist/esm/init.js.map +1 -1
  88. package/dist/esm/loaders/imageLoader.js +23 -9
  89. package/dist/esm/loaders/imageLoader.js.map +1 -1
  90. package/dist/esm/loaders/volumeLoader.js +58 -5
  91. package/dist/esm/loaders/volumeLoader.js.map +1 -1
  92. package/dist/esm/types/BoundsLPS.js +2 -0
  93. package/dist/esm/types/BoundsLPS.js.map +1 -0
  94. package/dist/esm/types/ImageVolumeProps.js +2 -0
  95. package/dist/esm/types/ImageVolumeProps.js.map +1 -0
  96. package/dist/esm/types/VolumeProps.js +2 -0
  97. package/dist/esm/types/VolumeProps.js.map +1 -0
  98. package/dist/esm/utilities/VoxelManager.js +13 -4
  99. package/dist/esm/utilities/VoxelManager.js.map +1 -1
  100. package/dist/esm/utilities/cacheUtils.js +65 -0
  101. package/dist/esm/utilities/cacheUtils.js.map +1 -0
  102. package/dist/esm/utilities/convertStackToVolumeViewport.js +49 -0
  103. package/dist/esm/utilities/convertStackToVolumeViewport.js.map +1 -0
  104. package/dist/esm/utilities/convertVolumeToStackViewport.js +58 -0
  105. package/dist/esm/utilities/convertVolumeToStackViewport.js.map +1 -0
  106. package/dist/esm/utilities/generateVolumePropsFromImageIds.js +118 -0
  107. package/dist/esm/utilities/generateVolumePropsFromImageIds.js.map +1 -0
  108. package/dist/esm/utilities/index.js +6 -1
  109. package/dist/esm/utilities/index.js.map +1 -1
  110. package/dist/esm/utilities/planar.js +5 -1
  111. package/dist/esm/utilities/planar.js.map +1 -1
  112. package/dist/esm/utilities/roundNumber.js +33 -0
  113. package/dist/esm/utilities/roundNumber.js.map +1 -0
  114. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
  115. package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
  116. package/dist/types/RenderingEngine/Viewport.d.ts +1 -0
  117. package/dist/types/RenderingEngine/Viewport.d.ts.map +1 -1
  118. package/dist/types/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.d.ts.map +1 -1
  119. package/dist/types/cache/cache.d.ts +6 -3
  120. package/dist/types/cache/cache.d.ts.map +1 -1
  121. package/dist/types/cache/classes/ImageVolume.d.ts +25 -5
  122. package/dist/types/cache/classes/ImageVolume.d.ts.map +1 -1
  123. package/dist/types/eventTarget.d.ts +1 -0
  124. package/dist/types/eventTarget.d.ts.map +1 -1
  125. package/dist/types/loaders/imageLoader.d.ts +6 -1
  126. package/dist/types/loaders/imageLoader.d.ts.map +1 -1
  127. package/dist/types/loaders/volumeLoader.d.ts +13 -9
  128. package/dist/types/loaders/volumeLoader.d.ts.map +1 -1
  129. package/dist/types/types/BoundsLPS.d.ts +4 -0
  130. package/dist/types/types/BoundsLPS.d.ts.map +1 -0
  131. package/dist/types/types/Cornerstone3DConfig.d.ts +1 -0
  132. package/dist/types/types/Cornerstone3DConfig.d.ts.map +1 -1
  133. package/dist/types/types/IDynamicImageVolume.d.ts +2 -2
  134. package/dist/types/types/IDynamicImageVolume.d.ts.map +1 -1
  135. package/dist/types/types/IImage.d.ts +5 -0
  136. package/dist/types/types/IImage.d.ts.map +1 -1
  137. package/dist/types/types/IImageVolume.d.ts +7 -2
  138. package/dist/types/types/IImageVolume.d.ts.map +1 -1
  139. package/dist/types/types/ILoadObject.d.ts +2 -2
  140. package/dist/types/types/ILoadObject.d.ts.map +1 -1
  141. package/dist/types/types/IViewport.d.ts +1 -0
  142. package/dist/types/types/IViewport.d.ts.map +1 -1
  143. package/dist/types/types/IVolume.d.ts +3 -26
  144. package/dist/types/types/IVolume.d.ts.map +1 -1
  145. package/dist/types/types/ImageVolumeProps.d.ts +7 -0
  146. package/dist/types/types/ImageVolumeProps.d.ts.map +1 -0
  147. package/dist/types/types/VolumeProps.d.ts +28 -0
  148. package/dist/types/types/VolumeProps.d.ts.map +1 -0
  149. package/dist/types/types/index.d.ts +5 -2
  150. package/dist/types/types/index.d.ts.map +1 -1
  151. package/dist/types/utilities/VoxelManager.d.ts +2 -2
  152. package/dist/types/utilities/VoxelManager.d.ts.map +1 -1
  153. package/dist/types/utilities/cacheUtils.d.ts +3 -0
  154. package/dist/types/utilities/cacheUtils.d.ts.map +1 -0
  155. package/dist/types/utilities/convertStackToVolumeViewport.d.ts +13 -0
  156. package/dist/types/utilities/convertStackToVolumeViewport.d.ts.map +1 -0
  157. package/dist/types/utilities/convertVolumeToStackViewport.d.ts +10 -0
  158. package/dist/types/utilities/convertVolumeToStackViewport.d.ts.map +1 -0
  159. package/dist/types/utilities/generateVolumePropsFromImageIds.d.ts +4 -0
  160. package/dist/types/utilities/generateVolumePropsFromImageIds.d.ts.map +1 -0
  161. package/dist/types/utilities/index.d.ts +6 -1
  162. package/dist/types/utilities/index.d.ts.map +1 -1
  163. package/dist/types/utilities/planar.d.ts +1 -1
  164. package/dist/types/utilities/planar.d.ts.map +1 -1
  165. package/dist/types/utilities/roundNumber.d.ts +5 -0
  166. package/dist/types/utilities/roundNumber.d.ts.map +1 -0
  167. package/dist/umd/index.js +1 -1
  168. package/dist/umd/index.js.map +1 -1
  169. package/package.json +3 -3
  170. package/src/RenderingEngine/BaseVolumeViewport.ts +4 -1
  171. package/src/RenderingEngine/StackViewport.ts +3 -1
  172. package/src/RenderingEngine/Viewport.ts +9 -1
  173. package/src/RenderingEngine/helpers/createVolumeActor.ts +1 -1
  174. package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +3 -26
  175. package/src/cache/cache.ts +91 -7
  176. package/src/cache/classes/ImageVolume.ts +535 -21
  177. package/src/eventTarget.ts +19 -1
  178. package/src/init.ts +2 -2
  179. package/src/loaders/imageLoader.ts +58 -10
  180. package/src/loaders/volumeLoader.ts +139 -23
  181. package/src/types/BoundsLPS.ts +5 -0
  182. package/src/types/Cornerstone3DConfig.ts +12 -0
  183. package/src/types/IDynamicImageVolume.ts +2 -2
  184. package/src/types/IImage.ts +6 -0
  185. package/src/types/IImageVolume.ts +14 -2
  186. package/src/types/ILoadObject.ts +2 -2
  187. package/src/types/IViewport.ts +2 -1
  188. package/src/types/IVolume.ts +4 -41
  189. package/src/types/ImageVolumeProps.ts +15 -0
  190. package/src/types/VolumeProps.ts +57 -0
  191. package/src/types/index.ts +7 -2
  192. package/src/utilities/VoxelManager.ts +17 -6
  193. package/src/utilities/cacheUtils.ts +121 -0
  194. package/src/utilities/convertStackToVolumeViewport.ts +115 -0
  195. package/src/utilities/convertVolumeToStackViewport.ts +125 -0
  196. package/src/utilities/generateVolumePropsFromImageIds.ts +183 -0
  197. package/src/utilities/index.ts +11 -0
  198. package/src/utilities/planar.ts +12 -1
  199. package/src/utilities/roundNumber.ts +56 -0
@@ -3,7 +3,7 @@ import type Cornerstone3DConfig from './Cornerstone3DConfig';
3
3
  import type ICamera from './ICamera';
4
4
  import type IEnabledElement from './IEnabledElement';
5
5
  import type ICache from './ICache';
6
- import type { IVolume, VolumeScalarData } from './IVolume';
6
+ import type { IVolume } from './IVolume';
7
7
  import type { VOI, VOIRange } from './voi';
8
8
  import type DisplayArea from './displayArea';
9
9
  import type ImageLoaderFn from './ImageLoaderFn';
@@ -106,6 +106,9 @@ import type {
106
106
  VideoViewportInput,
107
107
  } from './VideoViewportTypes';
108
108
  import type BoundsIJK from './BoundsIJK';
109
+ import type { ImageVolumeProps } from './ImageVolumeProps';
110
+ import type { VolumeProps } from './VolumeProps';
111
+ import type BoundsLPS from './BoundsLPS';
109
112
 
110
113
  export type {
111
114
  // config
@@ -118,9 +121,9 @@ export type {
118
121
  IEnabledElement,
119
122
  ICache,
120
123
  IVolume,
121
- VolumeScalarData,
122
124
  IViewportId,
123
125
  IImageVolume,
126
+ ImageVolumeProps,
124
127
  IDynamicImageVolume,
125
128
  IRenderingEngine,
126
129
  ScalingParameters,
@@ -214,6 +217,8 @@ export type {
214
217
  InternalVideoCamera,
215
218
  VideoViewportInput,
216
219
  BoundsIJK,
220
+ BoundsLPS,
217
221
  Color,
218
222
  ColorLUT,
223
+ VolumeProps,
219
224
  };
@@ -1,4 +1,4 @@
1
- import type { BoundsIJK, Point3, VolumeScalarData } from '../types';
1
+ import type { BoundsIJK, Point3, PixelDataTypedArray } from '../types';
2
2
 
3
3
  /**
4
4
  * This is a simple, standard interface to values associated with a voxel.
@@ -12,7 +12,7 @@ export default class VoxelManager<T> {
12
12
  ] as BoundsIJK;
13
13
 
14
14
  // Provide direct access to the underlying data, if any
15
- public scalarData: VolumeScalarData;
15
+ public scalarData: PixelDataTypedArray;
16
16
  public map: Map<number, T>;
17
17
  public sourceVoxelManager: VoxelManager<T>;
18
18
  public isInObject: (pointIPS, pointIJK) => boolean;
@@ -216,10 +216,21 @@ export default class VoxelManager<T> {
216
216
  * Extends the bounds of this object to include the specified point
217
217
  */
218
218
  public static addBounds(bounds: BoundsIJK, point: Point3) {
219
- bounds.forEach((bound, index) => {
220
- bound[0] = Math.min(point[index], bound[0]);
221
- bound[1] = Math.max(point[index], bound[1]);
222
- });
219
+ if (!bounds) {
220
+ bounds = [
221
+ [Infinity, -Infinity],
222
+ [Infinity, -Infinity],
223
+ [Infinity, -Infinity],
224
+ ];
225
+ }
226
+
227
+ // Directly update the bounds for each axis
228
+ bounds[0][0] = Math.min(point[0], bounds[0][0]);
229
+ bounds[0][1] = Math.max(point[0], bounds[0][1]);
230
+ bounds[1][0] = Math.min(point[1], bounds[1][0]);
231
+ bounds[1][1] = Math.max(point[1], bounds[1][1]);
232
+ bounds[2][0] = Math.min(point[2], bounds[2][0]);
233
+ bounds[2][1] = Math.max(point[2], bounds[2][1]);
223
234
  }
224
235
 
225
236
  /**
@@ -0,0 +1,121 @@
1
+ import cache, { ImageVolume } from '../cache';
2
+ import { Events } from '../enums';
3
+ import eventTarget from '../eventTarget';
4
+ import { getConfiguration, getShouldUseSharedArrayBuffer } from '../init';
5
+
6
+ /**
7
+ * This function will check if the cache optimization is enabled and if it is
8
+ * it will check if the created volume was derived from an already cached stack
9
+ * of images, if so it will go back to the image cache and create a view at the
10
+ * correct offset of the bigger volume array buffer, this will save memory.
11
+ *
12
+ * @param volumeId - The volumeId that will be checked for cache optimization
13
+ */
14
+ export function setupCacheOptimizationEventListener(volumeId) {
15
+ const { enableCacheOptimization } = getConfiguration();
16
+ const shouldUseSAB = getShouldUseSharedArrayBuffer();
17
+
18
+ const performOptimization = enableCacheOptimization && shouldUseSAB;
19
+ if (!performOptimization) {
20
+ return;
21
+ }
22
+
23
+ eventTarget.addEventListenerOnce(
24
+ Events.IMAGE_VOLUME_LOADING_COMPLETED,
25
+ (evt) => {
26
+ if (evt.detail.volumeId !== volumeId) {
27
+ return;
28
+ }
29
+
30
+ const volume = cache.getVolume(volumeId);
31
+
32
+ performCacheOptimizationForVolume(volume);
33
+ }
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Performs cache optimization for a volume by replacing the pixel data of each image
39
+ * in the image cache (if found) with a view of the volume's scalar data.
40
+ * @param options - The options for cache optimization.
41
+ * @param options.volumeId - The ID of the volume.
42
+ */
43
+ export function performCacheOptimizationForVolume(volume) {
44
+ if (!(volume instanceof ImageVolume)) {
45
+ return;
46
+ }
47
+
48
+ const scalarData = volume.getScalarData();
49
+
50
+ volume.imageCacheOffsetMap.size > 0
51
+ ? _processImageCacheOffsetMap(volume, scalarData)
52
+ : _processVolumeImages(volume, scalarData);
53
+ }
54
+
55
+ /**
56
+ * This function will process the volume images and replace the pixel data of each
57
+ * image in the image cache (if found) with a view of the volume's scalar data.
58
+ * This function is used when the volume is derived from an already cached stack
59
+ * of images.
60
+ *
61
+ * @param volume - The volume to process.
62
+ * @param scalarData - The scalar data to use for the volume.
63
+ */
64
+ function _processImageCacheOffsetMap(volume, scalarData) {
65
+ volume.imageCacheOffsetMap.forEach(({ offset }, imageId) => {
66
+ const image = cache.getImage(imageId);
67
+ if (!image) {
68
+ return;
69
+ }
70
+
71
+ _updateImageWithScalarDataView(image, scalarData, offset);
72
+ cache.decrementImageCacheSize(image.sizeInBytes);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * This function will process the volume images and replace the pixel data of each
78
+ * image in the image cache (if found) with a view of the volume's scalar data.
79
+ * This function is used when the volume is not derived from an already cached stack
80
+ * of images.
81
+ *
82
+ * @param volume - The volume to process.
83
+ * @param scalarData - The scalar data to use for the volume.
84
+ */
85
+ function _processVolumeImages(volume, scalarData) {
86
+ volume.imageIds.forEach((imageId) => {
87
+ const image = cache.getImage(imageId);
88
+ if (!image) {
89
+ return;
90
+ }
91
+
92
+ const index = volume.getImageIdIndex(imageId);
93
+ const offset = index * image.getPixelData().byteLength;
94
+
95
+ _updateImageWithScalarDataView(image, scalarData, offset);
96
+ cache.decrementImageCacheSize(image.sizeInBytes);
97
+ });
98
+ }
99
+
100
+ function _updateImageWithScalarDataView(image, scalarData, offset) {
101
+ const pixelData = image.imageFrame
102
+ ? image.imageFrame.pixelData
103
+ : image.getPixelData();
104
+
105
+ const view = new pixelData.constructor(
106
+ scalarData.buffer,
107
+ offset,
108
+ pixelData.length
109
+ );
110
+
111
+ image.getPixelData = () => view;
112
+
113
+ if (image.imageFrame) {
114
+ image.imageFrame.pixelData = view;
115
+ }
116
+
117
+ image.bufferView = {
118
+ buffer: scalarData.buffer,
119
+ offset,
120
+ };
121
+ }
@@ -0,0 +1,115 @@
1
+ import { IStackViewport, IVolumeViewport, Point3 } from '../types';
2
+ import { setVolumesForViewports } from '../RenderingEngine/helpers';
3
+ import {
4
+ createAndCacheVolume,
5
+ getUnknownVolumeLoaderSchema,
6
+ } from '../loaders/volumeLoader';
7
+ import { Events, OrientationAxis, ViewportType } from '../enums';
8
+
9
+ /**
10
+ * Converts a stack viewport to a volume viewport.
11
+ *
12
+ * @param params - The parameters for the conversion.
13
+ * @param params.viewport - The stack viewport to convert.
14
+ * @param params.options - The options for the conversion.
15
+ * @param [params.options.volumeId] - The volumeId that will get generated, it should have the volume loader schema defined if not we will use the default one.
16
+ * @param [params.options.viewportId] - The viewportId that will get used for new viewport. If not provided, the stack viewport id will be used.
17
+ * @param [params.options.background] - The background color of the volume viewport.
18
+ * @returns The converted volume viewport.
19
+ */
20
+ async function convertStackToVolumeViewport({
21
+ viewport,
22
+ options,
23
+ }: {
24
+ viewport: IStackViewport;
25
+ options: {
26
+ volumeId: string;
27
+ viewportId?: string;
28
+ background?: Point3;
29
+ orientation?: OrientationAxis;
30
+ };
31
+ }): Promise<IVolumeViewport> {
32
+ const renderingEngine = viewport.getRenderingEngine();
33
+
34
+ let { volumeId } = options;
35
+
36
+ // if there is no loader schema for the volume Id, we will use the default one
37
+ // which we can get from the volume loader
38
+ if (volumeId.split(':').length === 1) {
39
+ const schema = getUnknownVolumeLoaderSchema();
40
+ volumeId = `${schema}:${volumeId}`;
41
+ }
42
+
43
+ const { id, element } = viewport;
44
+ const viewportId = options.viewportId || id;
45
+
46
+ const imageIds = viewport.getImageIds();
47
+
48
+ // It is important to keep the camera before enabling the viewport
49
+ const prevCamera = viewport.getCamera();
50
+
51
+ // this will disable the stack viewport and remove it from the toolGroup
52
+ renderingEngine.enableElement({
53
+ viewportId,
54
+ type: ViewportType.ORTHOGRAPHIC,
55
+ element,
56
+ defaultOptions: {
57
+ background: options.background,
58
+ orientation: options.orientation,
59
+ },
60
+ });
61
+
62
+ // Ideally here we should be able to just create a local volume and not use the
63
+ // volume louder but we don't know if the stack is already pre-cached for all its
64
+ // imageIds or not so we just let the loader handle it and we have cache
65
+ // optimizations in place to avoid fetching the same imageId if it is already
66
+ // cached
67
+ const volume = await createAndCacheVolume(volumeId, {
68
+ imageIds,
69
+ });
70
+
71
+ volume.load();
72
+
73
+ // we should get the new viewport from the renderingEngine since the stack viewport
74
+ // was disabled and replaced with a volume viewport of the same id
75
+ const volumeViewport = <IVolumeViewport>(
76
+ renderingEngine.getViewport(viewportId)
77
+ );
78
+
79
+ setVolumesForViewports(
80
+ renderingEngine,
81
+ [
82
+ {
83
+ volumeId,
84
+ },
85
+ ],
86
+ [viewportId]
87
+ );
88
+
89
+ const volumeViewportNewVolumeHandler = () => {
90
+ volumeViewport.setCamera({
91
+ ...prevCamera,
92
+ });
93
+ volumeViewport.render();
94
+
95
+ element.removeEventListener(
96
+ Events.VOLUME_VIEWPORT_NEW_VOLUME,
97
+ volumeViewportNewVolumeHandler
98
+ );
99
+ };
100
+
101
+ const addVolumeViewportNewVolumeListener = () => {
102
+ element.addEventListener(
103
+ Events.VOLUME_VIEWPORT_NEW_VOLUME,
104
+ volumeViewportNewVolumeHandler
105
+ );
106
+ };
107
+
108
+ addVolumeViewportNewVolumeListener();
109
+
110
+ volumeViewport.render();
111
+
112
+ return volumeViewport;
113
+ }
114
+
115
+ export { convertStackToVolumeViewport };
@@ -0,0 +1,125 @@
1
+ import * as Types from '../types';
2
+ import cache, { ImageVolume } from '../cache';
3
+ import { ViewportType } from '../enums';
4
+
5
+ /**
6
+ * Converts a volume viewport to a stack viewport.
7
+ *
8
+ * @param params - The parameters for the conversion.
9
+ * @param params.viewport - The volume viewport to convert.
10
+ * @param params.options - The conversion options.
11
+ * @param [params.options.viewportId] - The new stackViewportId, If not provided, the volume viewport id will be used.
12
+ * @param [params.options.background] - The background color of the stack viewport.
13
+ * @param [params.options.decache] - Whether to decache the volume. Defaults to false.
14
+ *
15
+ * @returns The converted stack viewport.
16
+ */
17
+ async function convertVolumeToStackViewport({
18
+ viewport,
19
+ options,
20
+ }: {
21
+ viewport: Types.IVolumeViewport;
22
+ options: {
23
+ viewportId?: string;
24
+ background?: Types.Point3;
25
+ };
26
+ }): Promise<Types.IStackViewport> {
27
+ const volumeViewport = viewport;
28
+ const { id, element } = volumeViewport;
29
+ const renderingEngine = viewport.getRenderingEngine();
30
+ const imageIdIndex = viewport.getCurrentImageIdIndex();
31
+
32
+ const { background } = options;
33
+ const viewportId = options.viewportId || id;
34
+
35
+ const actorEntry = volumeViewport.getDefaultActor();
36
+ const { uid: volumeId } = actorEntry;
37
+ const volume = cache.getVolume(volumeId) as Types.IImageVolume;
38
+
39
+ if (!(volume instanceof ImageVolume)) {
40
+ throw new Error(
41
+ 'Currently, you cannot decache a volume that is not an ImageVolume. So, unfortunately, volumes such as nifti (which are basic Volume, without imageIds) cannot be decached.'
42
+ );
43
+ }
44
+
45
+ const viewportInput = {
46
+ viewportId,
47
+ type: ViewportType.STACK,
48
+ element,
49
+ defaultOptions: {
50
+ background,
51
+ },
52
+ };
53
+
54
+ renderingEngine.enableElement(viewportInput);
55
+
56
+ // Get the stack viewport that was created
57
+ const stackViewport = <Types.IStackViewport>(
58
+ renderingEngine.getViewport(viewportId)
59
+ );
60
+
61
+ // So here we have two scenarios that we need to handle:
62
+ // 1. the volume was derived from a stack and we need to decache it, this is easy
63
+ // since we just need purge the volume from the cache and those images will get
64
+ // their copy of the image back
65
+ // 2. It was actually a native volume and we need to decache it, this is a bit more
66
+ // complicated since then we need to decide on the imageIds for it to get
67
+ // decached to
68
+ const hasCachedImages = volume.imageCacheOffsetMap.size > 0;
69
+ // Initialize the variable to hold the final result
70
+ let isAllImagesCached = false;
71
+
72
+ if (hasCachedImages) {
73
+ // Check if every imageId in the volume is in the _imageCache
74
+ isAllImagesCached = volume.imageIds.every((imageId) =>
75
+ cache.getImage(imageId)
76
+ );
77
+ }
78
+
79
+ const volumeUsedInOtherViewports = renderingEngine
80
+ .getVolumeViewports()
81
+ .find((vp) => vp.hasVolumeId(volumeId));
82
+
83
+ volume.decache(!volumeUsedInOtherViewports && isAllImagesCached);
84
+
85
+ const stack = [...volume.imageIds].reverse();
86
+
87
+ let imageIdIndexToJump = Math.max(
88
+ volume.imageIds.length - imageIdIndex - 1,
89
+ 0
90
+ );
91
+
92
+ // Check to see if the image is already cached or not. If it's not, we will use another
93
+ // nearby imageId for the first image to jump to. There seem to be a lot of side effects
94
+ // if we jump to an image that is not cached in stack viewport while we convert
95
+ // from a volume viewport. For example, if we switch back and forth between stack and volume,
96
+ // and then try to jump to an image that is not cached, the image will not render at
97
+ // all when the full volume is filled. I'm not sure why yet.
98
+ const imageToJump = cache.getImage(stack[imageIdIndexToJump]);
99
+ if (!imageToJump) {
100
+ let minDistance = Infinity;
101
+ let minDistanceIndex = null;
102
+
103
+ stack.forEach((imageId, index) => {
104
+ const image = cache.getImage(imageId);
105
+ if (image) {
106
+ const distance = Math.abs(imageIdIndexToJump - index);
107
+ if (distance < minDistance) {
108
+ minDistance = distance;
109
+ minDistanceIndex = index;
110
+ }
111
+ }
112
+ });
113
+
114
+ imageIdIndexToJump = minDistanceIndex;
115
+ }
116
+
117
+ await stackViewport.setStack(stack, imageIdIndexToJump);
118
+
119
+ // Render the image
120
+ stackViewport.render();
121
+
122
+ return stackViewport;
123
+ }
124
+
125
+ export { convertVolumeToStackViewport };
@@ -0,0 +1,183 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ import { getConfiguration, getShouldUseSharedArrayBuffer } from '../init';
3
+ import createFloat32SharedArray from './createFloat32SharedArray';
4
+ import createInt16SharedArray from './createInt16SharedArray';
5
+ import createUint16SharedArray from './createUInt16SharedArray';
6
+ import createUint8SharedArray from './createUint8SharedArray';
7
+ import getScalingParameters from './getScalingParameters';
8
+ import makeVolumeMetadata from './makeVolumeMetadata';
9
+ import sortImageIdsAndGetSpacing from './sortImageIdsAndGetSpacing';
10
+ import { ImageVolumeProps, Mat3, Point3 } from '../types';
11
+ import cache from '../cache';
12
+ import { Events } from '../enums';
13
+
14
+ function generateVolumePropsFromImageIds(
15
+ imageIds: string[],
16
+ volumeId: string
17
+ ): ImageVolumeProps {
18
+ const { useNorm16Texture, preferSizeOverAccuracy } =
19
+ getConfiguration().rendering;
20
+
21
+ const use16BitDataType = useNorm16Texture || preferSizeOverAccuracy;
22
+
23
+ const volumeMetadata = makeVolumeMetadata(imageIds);
24
+
25
+ // For a streaming volume, the data type cannot rely on cswil to load
26
+ // the proper array buffer type. This is because the target buffer container
27
+ // must be decided ahead of time.
28
+ // TODO: move this logic into CSWIL to avoid logic duplication.
29
+ // We check if scaling parameters are negative we choose Int16 instead of
30
+ // Uint16 for cases where BitsAllocated is 16.
31
+ const imageIdIndex = Math.floor(imageIds.length / 2);
32
+ const imageId = imageIds[imageIdIndex];
33
+ const scalingParameters = getScalingParameters(imageId);
34
+ const hasNegativeRescale =
35
+ scalingParameters.rescaleIntercept < 0 ||
36
+ scalingParameters.rescaleSlope < 0;
37
+
38
+ // The prescale is ALWAYS used with modality LUT, so we can assume that
39
+ // if the rescale slope is not an integer, we need to use Float32
40
+ const hasFloatRescale =
41
+ scalingParameters.rescaleIntercept % 1 !== 0 ||
42
+ scalingParameters.rescaleSlope % 1 !== 0;
43
+
44
+ const {
45
+ BitsAllocated,
46
+ PixelRepresentation,
47
+ PhotometricInterpretation,
48
+ ImageOrientationPatient,
49
+ PixelSpacing,
50
+ Columns,
51
+ Rows,
52
+ } = volumeMetadata;
53
+
54
+ const rowCosineVec = vec3.fromValues(
55
+ ImageOrientationPatient[0],
56
+ ImageOrientationPatient[1],
57
+ ImageOrientationPatient[2]
58
+ );
59
+ const colCosineVec = vec3.fromValues(
60
+ ImageOrientationPatient[3],
61
+ ImageOrientationPatient[4],
62
+ ImageOrientationPatient[5]
63
+ );
64
+
65
+ const scanAxisNormal = vec3.create();
66
+
67
+ vec3.cross(scanAxisNormal, rowCosineVec, colCosineVec);
68
+
69
+ const { zSpacing, origin, sortedImageIds } = sortImageIdsAndGetSpacing(
70
+ imageIds,
71
+ scanAxisNormal
72
+ );
73
+
74
+ const numFrames = imageIds.length;
75
+
76
+ // Spacing goes [1] then [0], as [1] is column spacing (x) and [0] is row spacing (y)
77
+ const spacing = <Point3>[PixelSpacing[1], PixelSpacing[0], zSpacing];
78
+ const dimensions = <Point3>[Columns, Rows, numFrames];
79
+ const direction = [
80
+ ...rowCosineVec,
81
+ ...colCosineVec,
82
+ ...scanAxisNormal,
83
+ ] as Mat3;
84
+ const signed = PixelRepresentation === 1;
85
+ const numComponents = PhotometricInterpretation === 'RGB' ? 3 : 1;
86
+ const useSharedArrayBuffer = getShouldUseSharedArrayBuffer();
87
+ const length = dimensions[0] * dimensions[1] * dimensions[2];
88
+ const handleCache = (sizeInBytes) => {
89
+ if (!cache.isCacheable(sizeInBytes)) {
90
+ throw new Error(Events.CACHE_SIZE_EXCEEDED);
91
+ }
92
+ cache.decacheIfNecessaryUntilBytesAvailable(sizeInBytes);
93
+ };
94
+
95
+ let scalarData, sizeInBytes;
96
+ switch (BitsAllocated) {
97
+ case 8:
98
+ if (signed) {
99
+ throw new Error(
100
+ '8 Bit signed images are not yet supported by this plugin.'
101
+ );
102
+ }
103
+ sizeInBytes = length * numComponents;
104
+ handleCache(sizeInBytes);
105
+ scalarData = useSharedArrayBuffer
106
+ ? createUint8SharedArray(length * numComponents)
107
+ : new Uint8Array(length * numComponents);
108
+ break;
109
+
110
+ case 16:
111
+ // Temporary fix for 16 bit images to use Float32
112
+ // until the new dicom image loader handler the conversion
113
+ // correctly
114
+ if (!use16BitDataType || hasFloatRescale) {
115
+ sizeInBytes = length * 4;
116
+ scalarData = useSharedArrayBuffer
117
+ ? createFloat32SharedArray(length)
118
+ : new Float32Array(length);
119
+
120
+ break;
121
+ }
122
+
123
+ sizeInBytes = length * 2;
124
+ if (signed || hasNegativeRescale) {
125
+ handleCache(sizeInBytes);
126
+ scalarData = useSharedArrayBuffer
127
+ ? createInt16SharedArray(length)
128
+ : new Int16Array(length);
129
+ break;
130
+ }
131
+
132
+ if (!signed && !hasNegativeRescale) {
133
+ handleCache(sizeInBytes);
134
+ scalarData = useSharedArrayBuffer
135
+ ? createUint16SharedArray(length)
136
+ : new Uint16Array(length);
137
+ break;
138
+ }
139
+
140
+ // Default to Float32 again
141
+ sizeInBytes = length * 4;
142
+ handleCache(sizeInBytes);
143
+ scalarData = useSharedArrayBuffer
144
+ ? createFloat32SharedArray(length)
145
+ : new Float32Array(length);
146
+ break;
147
+
148
+ case 24:
149
+ sizeInBytes = length * numComponents;
150
+ handleCache(sizeInBytes);
151
+
152
+ // hacky because we don't support alpha channel in dicom
153
+ scalarData = useSharedArrayBuffer
154
+ ? createUint8SharedArray(length * numComponents)
155
+ : new Uint8Array(length * numComponents);
156
+ break;
157
+ case 32:
158
+ sizeInBytes = length * 4;
159
+ handleCache(sizeInBytes);
160
+ scalarData = useSharedArrayBuffer
161
+ ? createFloat32SharedArray(length)
162
+ : new Float32Array(length);
163
+ break;
164
+ default:
165
+ throw new Error(
166
+ `Bits allocated of ${BitsAllocated} is not defined to generate scalarData for the volume.`
167
+ );
168
+ }
169
+
170
+ return {
171
+ dimensions,
172
+ spacing,
173
+ origin,
174
+ direction,
175
+ scalarData,
176
+ sizeInBytes,
177
+ metadata: volumeMetadata,
178
+ imageIds: sortedImageIds,
179
+ volumeId,
180
+ };
181
+ }
182
+
183
+ export { generateVolumePropsFromImageIds };
@@ -61,13 +61,18 @@ import decimate from './decimate';
61
61
  import imageRetrieveMetadataProvider from './imageRetrieveMetadataProvider';
62
62
  import isVideoTransferSyntax from './isVideoTransferSyntax';
63
63
  import { getBufferConfiguration } from './getBufferConfiguration';
64
+ import { generateVolumePropsFromImageIds } from './generateVolumePropsFromImageIds';
65
+ import { convertStackToVolumeViewport } from './convertStackToVolumeViewport';
66
+ import { convertVolumeToStackViewport } from './convertVolumeToStackViewport';
64
67
  import VoxelManager from './VoxelManager';
68
+ import roundNumber, { roundToPrecision } from './roundNumber';
65
69
 
66
70
  // name spaces
67
71
  import * as planar from './planar';
68
72
  import * as windowLevel from './windowLevel';
69
73
  import * as colormap from './colormap';
70
74
  import * as transferFunctionUtils from './transferFunctionUtils';
75
+ import * as cacheUtils from './cacheUtils';
71
76
 
72
77
  export {
73
78
  eventListener,
@@ -139,4 +144,10 @@ export {
139
144
  isVideoTransferSyntax,
140
145
  getBufferConfiguration,
141
146
  VoxelManager,
147
+ generateVolumePropsFromImageIds,
148
+ convertStackToVolumeViewport,
149
+ convertVolumeToStackViewport,
150
+ cacheUtils,
151
+ roundNumber,
152
+ roundToPrecision,
142
153
  };
@@ -28,11 +28,22 @@ function linePlaneIntersection(p0: Point3, p1: Point3, plane: Plane): Point3 {
28
28
  * It returns the plane equation defined by a point and a normal vector.
29
29
  * @param normal - normal vector
30
30
  * @param point - a point on the plane
31
+ * @param normalized - if true, the values of the plane equation will be normalized
31
32
  * @returns - [A, B,C, D] of plane equation A*X + B*Y + C*Z = D
32
33
  */
33
- function planeEquation(normal: Point3, point: Point3 | vec3): Plane {
34
+ function planeEquation(
35
+ normal: Point3,
36
+ point: Point3 | vec3,
37
+ normalized = false
38
+ ): Plane {
34
39
  const [A, B, C] = normal;
35
40
  const D = A * point[0] + B * point[1] + C * point[2];
41
+
42
+ if (normalized) {
43
+ const length = Math.sqrt(A * A + B * B + C * C);
44
+ return [A / length, B / length, C / length, D / length];
45
+ }
46
+
36
47
  return [A, B, C, D];
37
48
  }
38
49