@cornerstonejs/core 1.32.2 → 1.33.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 (163) hide show
  1. package/dist/cjs/RenderingEngine/StackViewport.d.ts +24 -3
  2. package/dist/cjs/RenderingEngine/StackViewport.js +47 -31
  3. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  4. package/dist/cjs/RenderingEngine/helpers/addImageSlicesToViewports.d.ts +3 -0
  5. package/dist/cjs/RenderingEngine/helpers/addImageSlicesToViewports.js +33 -0
  6. package/dist/cjs/RenderingEngine/helpers/addImageSlicesToViewports.js.map +1 -0
  7. package/dist/cjs/RenderingEngine/helpers/index.d.ts +2 -1
  8. package/dist/cjs/RenderingEngine/helpers/index.js +3 -1
  9. package/dist/cjs/RenderingEngine/helpers/index.js.map +1 -1
  10. package/dist/cjs/cache/cache.d.ts +2 -1
  11. package/dist/cjs/cache/cache.js +11 -0
  12. package/dist/cjs/cache/cache.js.map +1 -1
  13. package/dist/cjs/index.d.ts +2 -2
  14. package/dist/cjs/index.js +2 -1
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/loaders/imageLoader.d.ts +21 -1
  17. package/dist/cjs/loaders/imageLoader.js +97 -1
  18. package/dist/cjs/loaders/imageLoader.js.map +1 -1
  19. package/dist/cjs/loaders/volumeLoader.d.ts +2 -2
  20. package/dist/cjs/loaders/volumeLoader.js +6 -28
  21. package/dist/cjs/loaders/volumeLoader.js.map +1 -1
  22. package/dist/cjs/types/IImage.d.ts +1 -0
  23. package/dist/cjs/types/IStackInput.d.ts +12 -0
  24. package/dist/cjs/types/IStackInput.js +3 -0
  25. package/dist/cjs/types/IStackInput.js.map +1 -0
  26. package/dist/cjs/types/IStackViewport.d.ts +3 -0
  27. package/dist/cjs/types/PixelDataTypedArray.d.ts +1 -0
  28. package/dist/cjs/types/index.d.ts +3 -2
  29. package/dist/cjs/utilities/genericMetadataProvider.d.ts +6 -0
  30. package/dist/cjs/utilities/genericMetadataProvider.js +23 -0
  31. package/dist/cjs/utilities/genericMetadataProvider.js.map +1 -0
  32. package/dist/cjs/utilities/getBufferConfiguration.d.ts +9 -0
  33. package/dist/cjs/utilities/getBufferConfiguration.js +47 -0
  34. package/dist/cjs/utilities/getBufferConfiguration.js.map +1 -0
  35. package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.d.ts +1 -1
  36. package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js +4 -4
  37. package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
  38. package/dist/cjs/utilities/getVolumeSliceRangeInfo.d.ts +1 -1
  39. package/dist/cjs/utilities/getVolumeSliceRangeInfo.js +2 -2
  40. package/dist/cjs/utilities/getVolumeSliceRangeInfo.js.map +1 -1
  41. package/dist/cjs/utilities/getVolumeViewportScrollInfo.d.ts +1 -1
  42. package/dist/cjs/utilities/getVolumeViewportScrollInfo.js +2 -2
  43. package/dist/cjs/utilities/getVolumeViewportScrollInfo.js.map +1 -1
  44. package/dist/cjs/utilities/index.d.ts +7 -1
  45. package/dist/cjs/utilities/index.js +13 -1
  46. package/dist/cjs/utilities/index.js.map +1 -1
  47. package/dist/cjs/utilities/isValidVolume.d.ts +2 -0
  48. package/dist/cjs/utilities/isValidVolume.js +35 -0
  49. package/dist/cjs/utilities/isValidVolume.js.map +1 -0
  50. package/dist/cjs/utilities/makeVolumeMetadata.d.ts +2 -0
  51. package/dist/cjs/utilities/makeVolumeMetadata.js +55 -0
  52. package/dist/cjs/utilities/makeVolumeMetadata.js.map +1 -0
  53. package/dist/cjs/utilities/sortImageIdsAndGetSpacing.d.ts +9 -0
  54. package/dist/cjs/utilities/sortImageIdsAndGetSpacing.js +82 -0
  55. package/dist/cjs/utilities/sortImageIdsAndGetSpacing.js.map +1 -0
  56. package/dist/cjs/utilities/updateVTKImageDataWithCornerstoneImage.d.ts +4 -0
  57. package/dist/cjs/utilities/updateVTKImageDataWithCornerstoneImage.js +27 -0
  58. package/dist/cjs/utilities/updateVTKImageDataWithCornerstoneImage.js.map +1 -0
  59. package/dist/esm/RenderingEngine/StackViewport.js +46 -32
  60. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  61. package/dist/esm/RenderingEngine/helpers/addImageSlicesToViewports.js +20 -0
  62. package/dist/esm/RenderingEngine/helpers/addImageSlicesToViewports.js.map +1 -0
  63. package/dist/esm/RenderingEngine/helpers/index.js +2 -1
  64. package/dist/esm/RenderingEngine/helpers/index.js.map +1 -1
  65. package/dist/esm/cache/cache.js +11 -0
  66. package/dist/esm/cache/cache.js.map +1 -1
  67. package/dist/esm/index.js +2 -2
  68. package/dist/esm/index.js.map +1 -1
  69. package/dist/esm/loaders/imageLoader.js +94 -1
  70. package/dist/esm/loaders/imageLoader.js.map +1 -1
  71. package/dist/esm/loaders/volumeLoader.js +7 -29
  72. package/dist/esm/loaders/volumeLoader.js.map +1 -1
  73. package/dist/esm/types/IStackInput.js +2 -0
  74. package/dist/esm/types/IStackInput.js.map +1 -0
  75. package/dist/esm/utilities/genericMetadataProvider.js +20 -0
  76. package/dist/esm/utilities/genericMetadataProvider.js.map +1 -0
  77. package/dist/esm/utilities/getBufferConfiguration.js +44 -0
  78. package/dist/esm/utilities/getBufferConfiguration.js.map +1 -0
  79. package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +4 -4
  80. package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
  81. package/dist/esm/utilities/getVolumeSliceRangeInfo.js +2 -2
  82. package/dist/esm/utilities/getVolumeSliceRangeInfo.js.map +1 -1
  83. package/dist/esm/utilities/getVolumeViewportScrollInfo.js +2 -2
  84. package/dist/esm/utilities/getVolumeViewportScrollInfo.js.map +1 -1
  85. package/dist/esm/utilities/index.js +7 -1
  86. package/dist/esm/utilities/index.js.map +1 -1
  87. package/dist/esm/utilities/isValidVolume.js +29 -0
  88. package/dist/esm/utilities/isValidVolume.js.map +1 -0
  89. package/dist/esm/utilities/makeVolumeMetadata.js +52 -0
  90. package/dist/esm/utilities/makeVolumeMetadata.js.map +1 -0
  91. package/dist/esm/utilities/sortImageIdsAndGetSpacing.js +79 -0
  92. package/dist/esm/utilities/sortImageIdsAndGetSpacing.js.map +1 -0
  93. package/dist/esm/utilities/updateVTKImageDataWithCornerstoneImage.js +24 -0
  94. package/dist/esm/utilities/updateVTKImageDataWithCornerstoneImage.js.map +1 -0
  95. package/dist/types/RenderingEngine/StackViewport.d.ts +24 -3
  96. package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
  97. package/dist/types/RenderingEngine/helpers/addImageSlicesToViewports.d.ts +4 -0
  98. package/dist/types/RenderingEngine/helpers/addImageSlicesToViewports.d.ts.map +1 -0
  99. package/dist/types/RenderingEngine/helpers/index.d.ts +2 -1
  100. package/dist/types/RenderingEngine/helpers/index.d.ts.map +1 -1
  101. package/dist/types/cache/cache.d.ts +2 -1
  102. package/dist/types/cache/cache.d.ts.map +1 -1
  103. package/dist/types/index.d.ts +2 -2
  104. package/dist/types/index.d.ts.map +1 -1
  105. package/dist/types/loaders/imageLoader.d.ts +21 -1
  106. package/dist/types/loaders/imageLoader.d.ts.map +1 -1
  107. package/dist/types/loaders/volumeLoader.d.ts +2 -2
  108. package/dist/types/loaders/volumeLoader.d.ts.map +1 -1
  109. package/dist/types/types/IImage.d.ts +1 -0
  110. package/dist/types/types/IImage.d.ts.map +1 -1
  111. package/dist/types/types/IStackInput.d.ts +13 -0
  112. package/dist/types/types/IStackInput.d.ts.map +1 -0
  113. package/dist/types/types/IStackViewport.d.ts +3 -0
  114. package/dist/types/types/IStackViewport.d.ts.map +1 -1
  115. package/dist/types/types/PixelDataTypedArray.d.ts +1 -0
  116. package/dist/types/types/PixelDataTypedArray.d.ts.map +1 -1
  117. package/dist/types/types/index.d.ts +3 -2
  118. package/dist/types/types/index.d.ts.map +1 -1
  119. package/dist/types/utilities/genericMetadataProvider.d.ts +7 -0
  120. package/dist/types/utilities/genericMetadataProvider.d.ts.map +1 -0
  121. package/dist/types/utilities/getBufferConfiguration.d.ts +10 -0
  122. package/dist/types/utilities/getBufferConfiguration.d.ts.map +1 -0
  123. package/dist/types/utilities/getTargetVolumeAndSpacingInNormalDir.d.ts +1 -1
  124. package/dist/types/utilities/getTargetVolumeAndSpacingInNormalDir.d.ts.map +1 -1
  125. package/dist/types/utilities/getVolumeSliceRangeInfo.d.ts +1 -1
  126. package/dist/types/utilities/getVolumeSliceRangeInfo.d.ts.map +1 -1
  127. package/dist/types/utilities/getVolumeViewportScrollInfo.d.ts +1 -1
  128. package/dist/types/utilities/getVolumeViewportScrollInfo.d.ts.map +1 -1
  129. package/dist/types/utilities/index.d.ts +7 -1
  130. package/dist/types/utilities/index.d.ts.map +1 -1
  131. package/dist/types/utilities/isValidVolume.d.ts +3 -0
  132. package/dist/types/utilities/isValidVolume.d.ts.map +1 -0
  133. package/dist/types/utilities/makeVolumeMetadata.d.ts +3 -0
  134. package/dist/types/utilities/makeVolumeMetadata.d.ts.map +1 -0
  135. package/dist/types/utilities/sortImageIdsAndGetSpacing.d.ts +10 -0
  136. package/dist/types/utilities/sortImageIdsAndGetSpacing.d.ts.map +1 -0
  137. package/dist/types/utilities/updateVTKImageDataWithCornerstoneImage.d.ts +5 -0
  138. package/dist/types/utilities/updateVTKImageDataWithCornerstoneImage.d.ts.map +1 -0
  139. package/dist/umd/index.js +1 -1
  140. package/dist/umd/index.js.map +1 -1
  141. package/package.json +2 -2
  142. package/src/RenderingEngine/StackViewport.ts +77 -52
  143. package/src/RenderingEngine/helpers/addImageSlicesToViewports.ts +54 -0
  144. package/src/RenderingEngine/helpers/index.ts +2 -0
  145. package/src/cache/cache.ts +22 -0
  146. package/src/index.ts +2 -0
  147. package/src/loaders/imageLoader.ts +202 -2
  148. package/src/loaders/volumeLoader.ts +18 -27
  149. package/src/types/IImage.ts +2 -0
  150. package/src/types/IStackInput.ts +30 -0
  151. package/src/types/IStackViewport.ts +10 -3
  152. package/src/types/PixelDataTypedArray.ts +8 -0
  153. package/src/types/index.ts +8 -1
  154. package/src/utilities/genericMetadataProvider.ts +43 -0
  155. package/src/utilities/getBufferConfiguration.ts +69 -0
  156. package/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +9 -5
  157. package/src/utilities/getVolumeSliceRangeInfo.ts +10 -2
  158. package/src/utilities/getVolumeViewportScrollInfo.ts +5 -2
  159. package/src/utilities/index.ts +12 -0
  160. package/src/utilities/isValidVolume.ts +59 -0
  161. package/src/utilities/makeVolumeMetadata.ts +87 -0
  162. package/src/utilities/sortImageIdsAndGetSpacing.ts +174 -0
  163. package/src/utilities/updateVTKImageDataWithCornerstoneImage.ts +38 -0
@@ -1,4 +1,3 @@
1
- import CPUFallbackColormapData from './CPUFallbackColormapData';
2
1
  import CPUIImageData from './CPUIImageData';
3
2
  import ICamera from './ICamera';
4
3
  import IImageData from './IImageData';
@@ -7,9 +6,8 @@ import Point2 from './Point2';
7
6
  import Point3 from './Point3';
8
7
  import { Scaling } from './ScalingParameters';
9
8
  import StackViewportProperties from './StackViewportProperties';
10
- import { ColormapPublic } from './Colormap';
11
9
  import type IImage from './IImage';
12
-
10
+ import { IStackInput } from './IStackInput';
13
11
  /**
14
12
  * Interface for Stack Viewport
15
13
  */
@@ -93,7 +91,16 @@ export default interface IStackViewport extends IViewport {
93
91
  * Returns the currently rendered imageId
94
92
  */
95
93
  getCurrentImageId: () => string;
94
+ /**
95
+ * Add Image Slices actors to the viewport
96
+ */
97
+ addImages(
98
+ stackInputs: Array<IStackInput>,
99
+ immediateRender: boolean,
100
+ suppressEvents: boolean
101
+ );
96
102
 
103
+ getImageDataMetadata(image: IImage): any;
97
104
  /**
98
105
  * Custom rendering pipeline for the rendering for the CPU fallback
99
106
  */
@@ -5,3 +5,11 @@ export type PixelDataTypedArray =
5
5
  | Uint8Array
6
6
  | Int8Array
7
7
  | Uint8ClampedArray;
8
+
9
+ export type PixelDataTypedArrayString =
10
+ | 'Float32Array'
11
+ | 'Int16Array'
12
+ | 'Uint16Array'
13
+ | 'Uint8Array'
14
+ | 'Int8Array'
15
+ | 'Uint8ClampedArray';
@@ -62,6 +62,7 @@ import type CPUFallbackLookupTable from './CPUFallbackLookupTable';
62
62
  import type CPUFallbackLUT from './CPUFallbackLUT';
63
63
  import type CPUFallbackRenderingTools from './CPUFallbackRenderingTools';
64
64
  import type { IVolumeInput, VolumeInputCallback } from './IVolumeInput';
65
+ import type { IStackInput, StackInputCallback } from './IStackInput';
65
66
  import type * as EventTypes from './EventTypes';
66
67
  import type IRenderingEngine from './IRenderingEngine';
67
68
  import type ActorSliceRange from './ActorSliceRange';
@@ -79,7 +80,10 @@ import type { IContour } from './IContour';
79
80
  import type RGB from './RGB';
80
81
  import { ColormapPublic, ColormapRegistration } from './Colormap';
81
82
  import type { ViewportProperties } from './ViewportProperties';
82
- import type { PixelDataTypedArray } from './PixelDataTypedArray';
83
+ import type {
84
+ PixelDataTypedArray,
85
+ PixelDataTypedArrayString,
86
+ } from './PixelDataTypedArray';
83
87
  import type { ImagePixelModule } from './ImagePixelModule';
84
88
  import type { ImagePlaneModule } from './ImagePlaneModule';
85
89
  import type { AffineMatrix } from './AffineMatrix';
@@ -143,6 +147,8 @@ export type {
143
147
  IVolumeLoadObject,
144
148
  IVolumeInput,
145
149
  VolumeInputCallback,
150
+ IStackInput,
151
+ StackInputCallback,
146
152
  ViewportPreset,
147
153
  //
148
154
  Metadata,
@@ -195,6 +201,7 @@ export type {
195
201
  ColormapRegistration,
196
202
  // PixelData
197
203
  PixelDataTypedArray,
204
+ PixelDataTypedArrayString,
198
205
  ImagePixelModule,
199
206
  ImagePlaneModule,
200
207
  AffineMatrix,
@@ -0,0 +1,43 @@
1
+ import { metaData } from '..';
2
+
3
+ let state: Record<string, any> = {}; // Calibrated pixel spacing per imageId
4
+ /**
5
+ * Simple metadata provider that stores some sort of meta data for each imageId.
6
+ */
7
+ const metadataProvider = {
8
+ /**
9
+ * Adds metadata for an imageId.
10
+ * @param imageId - the imageId for the metadata to store
11
+ * @param payload - the payload
12
+ */
13
+ add: (imageId: string, payload: any): void => {
14
+ const type = payload.type;
15
+
16
+ if (!state[imageId]) {
17
+ state[imageId] = {};
18
+ }
19
+
20
+ // Create a deep copy of payload.metadata
21
+ state[imageId][type] = JSON.parse(JSON.stringify(payload.metadata));
22
+ },
23
+
24
+ /**
25
+ * Returns the metadata for an imageId if it exists.
26
+ * @param type - the type of metadata to enquire about
27
+ * @param imageId - the imageId to enquire about
28
+ */
29
+ get: (type: string, imageId: string): any => {
30
+ return state[imageId]?.[type];
31
+ },
32
+
33
+ /**
34
+ * Clears all metadata.
35
+ */
36
+ clear: (): void => {
37
+ state = {};
38
+ },
39
+ };
40
+
41
+ metaData.addProvider(metadataProvider.get);
42
+
43
+ export default metadataProvider;
@@ -0,0 +1,69 @@
1
+ import { PixelDataTypedArray, PixelDataTypedArrayString } from '../types';
2
+
3
+ /**
4
+ * Creates a target buffer based on the provided options.
5
+ *
6
+ * @param targetBufferType - The type of the target buffer.
7
+ * @param length - The length of the target buffer.
8
+ * @param options - Options for the target buffer. Currently supports
9
+ * `use16BitTexture` and `isVolumeBuffer`.
10
+ * @returns Returns an object containing the number of bytes and the type array
11
+ * constructor of the target buffer, which you then use to create the target buffer
12
+ * with new TypedArrayConstructor(length).
13
+ */
14
+ function getBufferConfiguration(
15
+ targetBufferType: PixelDataTypedArrayString,
16
+ length: number,
17
+ options: { use16BitTexture?: boolean; isVolumeBuffer?: boolean } = {}
18
+ ): {
19
+ numBytes: number;
20
+ TypedArrayConstructor: new (
21
+ length: number | SharedArrayBuffer
22
+ ) => PixelDataTypedArray;
23
+ } {
24
+ const { use16BitTexture = false, isVolumeBuffer = false } = options;
25
+
26
+ switch (targetBufferType) {
27
+ case 'Float32Array':
28
+ return { numBytes: length * 4, TypedArrayConstructor: Float32Array };
29
+ case 'Uint8Array':
30
+ return { numBytes: length, TypedArrayConstructor: Uint8Array };
31
+ case 'Uint16Array':
32
+ if (!isVolumeBuffer) {
33
+ return { numBytes: length * 2, TypedArrayConstructor: Uint16Array };
34
+ } else {
35
+ if (use16BitTexture) {
36
+ return { numBytes: length * 2, TypedArrayConstructor: Uint16Array };
37
+ } else {
38
+ console.warn(
39
+ 'Uint16Array is not supported for volume rendering, switching back to Float32Array'
40
+ );
41
+ return { numBytes: length * 4, TypedArrayConstructor: Float32Array };
42
+ }
43
+ }
44
+ case 'Int16Array':
45
+ if (!isVolumeBuffer) {
46
+ return { numBytes: length * 2, TypedArrayConstructor: Int16Array };
47
+ } else {
48
+ if (use16BitTexture) {
49
+ return { numBytes: length * 2, TypedArrayConstructor: Int16Array };
50
+ } else {
51
+ console.warn(
52
+ 'Int16Array is not supported for volume rendering, switching back to Float32Array'
53
+ );
54
+ return { numBytes: length * 4, TypedArrayConstructor: Float32Array };
55
+ }
56
+ }
57
+ default:
58
+ if (targetBufferType) {
59
+ throw new Error(
60
+ 'TargetBuffer should be Float32Array, Uint8Array, Uint16Array, or Int16Array'
61
+ );
62
+ } else {
63
+ // Use Float32Array if no targetBuffer is provided
64
+ return { numBytes: length * 4, TypedArrayConstructor: Float32Array };
65
+ }
66
+ }
67
+ }
68
+
69
+ export { getBufferConfiguration };
@@ -30,14 +30,16 @@ const isPrimaryVolume = (volume): boolean =>
30
30
  * @param camera - current camera
31
31
  * @param targetVolumeId - If a target volumeId is given that volume
32
32
  * is forced to be used.
33
- *
33
+ * @param useSlabThickness - If true, the number of steps will be calculated
34
+ * based on the slab thickness instead of the spacing in the normal direction
34
35
  * @returns An object containing the imageVolume and spacingInNormalDirection.
35
36
  *
36
37
  */
37
38
  export default function getTargetVolumeAndSpacingInNormalDir(
38
39
  viewport: IVolumeViewport,
39
40
  camera: ICamera,
40
- targetVolumeId?: string
41
+ targetVolumeId?: string,
42
+ useSlabThickness = false
41
43
  ): {
42
44
  imageVolume: IImageVolume;
43
45
  spacingInNormalDirection: number;
@@ -75,7 +77,8 @@ export default function getTargetVolumeAndSpacingInNormalDir(
75
77
  const spacingInNormalDirection = getSpacingInNormal(
76
78
  imageVolume,
77
79
  viewPlaneNormal,
78
- viewport
80
+ viewport,
81
+ useSlabThickness
79
82
  );
80
83
 
81
84
  return { imageVolume, spacingInNormalDirection, actorUID };
@@ -131,11 +134,12 @@ export default function getTargetVolumeAndSpacingInNormalDir(
131
134
  function getSpacingInNormal(
132
135
  imageVolume: IImageVolume,
133
136
  viewPlaneNormal: Point3,
134
- viewport: IVolumeViewport
137
+ viewport: IVolumeViewport,
138
+ useSlabThickness = false
135
139
  ): number {
136
140
  const { slabThickness } = viewport.getProperties();
137
141
  let spacingInNormalDirection = slabThickness;
138
- if (!slabThickness) {
142
+ if (!slabThickness || useSlabThickness === false) {
139
143
  spacingInNormalDirection = getSpacingInNormalDirection(
140
144
  imageVolume,
141
145
  viewPlaneNormal
@@ -11,11 +11,14 @@ import {
11
11
  * Calculates the slice range for the given volume based on its orientation
12
12
  * @param viewport - Volume viewport
13
13
  * @param volumeId - Id of one of the volumes loaded on the given viewport
14
+ * @param useSlabThickness - If true, the slice range will be calculated
15
+ * based on the slab thickness instead of the spacing in the normal direction
14
16
  * @returns slice range information
15
17
  */
16
18
  function getVolumeSliceRangeInfo(
17
19
  viewport: IVolumeViewport,
18
- volumeId: string
20
+ volumeId: string,
21
+ useSlabThickness = false
19
22
  ): {
20
23
  sliceRange: ActorSliceRange;
21
24
  spacingInNormalDirection: number;
@@ -24,7 +27,12 @@ function getVolumeSliceRangeInfo(
24
27
  const camera = viewport.getCamera();
25
28
  const { focalPoint, viewPlaneNormal } = camera;
26
29
  const { spacingInNormalDirection, actorUID } =
27
- getTargetVolumeAndSpacingInNormalDir(viewport, camera, volumeId);
30
+ getTargetVolumeAndSpacingInNormalDir(
31
+ viewport,
32
+ camera,
33
+ volumeId,
34
+ useSlabThickness
35
+ );
28
36
 
29
37
  if (!actorUID) {
30
38
  throw new Error(
@@ -5,14 +5,17 @@ import getVolumeSliceRangeInfo from './getVolumeSliceRangeInfo';
5
5
  * Calculates the number os steps the volume can scroll based on its orientation
6
6
  * @param viewport - Volume viewport
7
7
  * @param volumeId - Id of one of the volumes loaded on the given viewport
8
+ * @param useSlabThickness - If true, the number of steps will be calculated
9
+ * based on the slab thickness instead of the spacing in the normal direction
8
10
  * @returns number of steps the volume can scroll and its current position
9
11
  */
10
12
  function getVolumeViewportScrollInfo(
11
13
  viewport: IVolumeViewport,
12
- volumeId: string
14
+ volumeId: string,
15
+ useSlabThickness = false
13
16
  ) {
14
17
  const { sliceRange, spacingInNormalDirection, camera } =
15
- getVolumeSliceRangeInfo(viewport, volumeId);
18
+ getVolumeSliceRangeInfo(viewport, volumeId, useSlabThickness);
16
19
 
17
20
  const { min, max, current } = sliceRange;
18
21
 
@@ -49,10 +49,16 @@ import getScalingParameters from './getScalingParameters';
49
49
  import getScalarDataType from './getScalarDataType';
50
50
  import isPTPrescaledWithSUV from './isPTPrescaledWithSUV';
51
51
  import getImageLegacy from './getImageLegacy';
52
+ import sortImageIdsAndGetSpacing from './sortImageIdsAndGetSpacing';
53
+ import makeVolumeMetadata from './makeVolumeMetadata';
54
+ import genericMetadataProvider from './genericMetadataProvider';
55
+ import { isValidVolume } from './isValidVolume';
56
+ import { updateVTKImageDataWithCornerstoneImage } from './updateVTKImageDataWithCornerstoneImage';
52
57
  import ProgressiveIterator from './ProgressiveIterator';
53
58
  import decimate from './decimate';
54
59
  import imageRetrieveMetadataProvider from './imageRetrieveMetadataProvider';
55
60
  import isVideoTransferSyntax from './isVideoTransferSyntax';
61
+ import { getBufferConfiguration } from './getBufferConfiguration';
56
62
 
57
63
  // name spaces
58
64
  import * as planar from './planar';
@@ -120,5 +126,11 @@ export {
120
126
  decimate,
121
127
  imageRetrieveMetadataProvider,
122
128
  transferFunctionUtils,
129
+ updateVTKImageDataWithCornerstoneImage,
130
+ sortImageIdsAndGetSpacing,
131
+ makeVolumeMetadata,
132
+ isValidVolume,
133
+ genericMetadataProvider,
123
134
  isVideoTransferSyntax,
135
+ getBufferConfiguration,
124
136
  };
@@ -0,0 +1,59 @@
1
+ import { metaData } from '..';
2
+ import isEqual from './isEqual';
3
+
4
+ /**
5
+ * Checks if the given imageIds form a valid volume. A volume is considered valid if all imageIds
6
+ * have the same series instance UID, modality, columns, rows, image orientation patient, and pixel
7
+ * spacing.
8
+ *
9
+ * @param imageIds - The imageIds to check.
10
+ * @returns true if the imageIds form a valid volume, false otherwise.
11
+ */
12
+ function isValidVolume(imageIds: string[]): boolean {
13
+ const imageId0 = imageIds[0];
14
+
15
+ const { modality, seriesInstanceUID } = metaData.get(
16
+ 'generalSeriesModule',
17
+ imageId0
18
+ );
19
+
20
+ const {
21
+ imageOrientationPatient,
22
+ pixelSpacing,
23
+ frameOfReferenceUID,
24
+ columns,
25
+ rows,
26
+ } = metaData.get('imagePlaneModule', imageId0);
27
+
28
+ const baseMetadata = {
29
+ modality,
30
+ imageOrientationPatient,
31
+ pixelSpacing,
32
+ frameOfReferenceUID,
33
+ columns,
34
+ rows,
35
+ seriesInstanceUID,
36
+ };
37
+
38
+ const validVolume = imageIds.every((imageId) => {
39
+ const { modality, seriesInstanceUID } = metaData.get(
40
+ 'generalSeriesModule',
41
+ imageId
42
+ );
43
+ const { imageOrientationPatient, pixelSpacing, columns, rows } =
44
+ metaData.get('imagePlaneModule', imageId);
45
+
46
+ return (
47
+ seriesInstanceUID === baseMetadata.seriesInstanceUID &&
48
+ modality === baseMetadata.modality &&
49
+ columns === baseMetadata.columns &&
50
+ rows === baseMetadata.rows &&
51
+ isEqual(imageOrientationPatient, baseMetadata.imageOrientationPatient) &&
52
+ isEqual(pixelSpacing, baseMetadata.pixelSpacing)
53
+ );
54
+ });
55
+
56
+ return validVolume;
57
+ }
58
+
59
+ export { isValidVolume };
@@ -0,0 +1,87 @@
1
+ import { metaData } from '../';
2
+ import { Metadata } from '../types';
3
+
4
+ /**
5
+ * It creates a metadata object for a volume given the imageIds that compose it.
6
+ * It uses the first imageId to get the metadata.
7
+ *
8
+ * @param imageIds - array of imageIds
9
+ * @returns The volume metadata
10
+ */
11
+ export default function makeVolumeMetadata(imageIds: Array<string>): Metadata {
12
+ const imageId0 = imageIds[0];
13
+
14
+ const {
15
+ pixelRepresentation,
16
+ bitsAllocated,
17
+ bitsStored,
18
+ highBit,
19
+ photometricInterpretation,
20
+ samplesPerPixel,
21
+ } = metaData.get('imagePixelModule', imageId0);
22
+
23
+ // Add list of VOIs stored on the DICOM.
24
+ const voiLut = [];
25
+
26
+ const voiLutModule = metaData.get('voiLutModule', imageId0);
27
+
28
+ // voiLutModule is not always present
29
+ let voiLUTFunction;
30
+ if (voiLutModule) {
31
+ const { windowWidth, windowCenter } = voiLutModule;
32
+ voiLUTFunction = voiLutModule?.voiLUTFunction;
33
+
34
+ if (Array.isArray(windowWidth)) {
35
+ for (let i = 0; i < windowWidth.length; i++) {
36
+ voiLut.push({
37
+ windowWidth: windowWidth[i],
38
+ windowCenter: windowCenter[i],
39
+ });
40
+ }
41
+ } else {
42
+ voiLut.push({
43
+ windowWidth: windowWidth,
44
+ windowCenter: windowCenter,
45
+ });
46
+ }
47
+ } else {
48
+ voiLut.push({
49
+ windowWidth: undefined,
50
+ windowCenter: undefined,
51
+ });
52
+ }
53
+
54
+ const { modality, seriesInstanceUID } = metaData.get(
55
+ 'generalSeriesModule',
56
+ imageId0
57
+ );
58
+
59
+ const {
60
+ imageOrientationPatient,
61
+ pixelSpacing,
62
+ frameOfReferenceUID,
63
+ columns,
64
+ rows,
65
+ } = metaData.get('imagePlaneModule', imageId0);
66
+
67
+ // Map to dcmjs-style keywords. This is becoming the standard and makes it
68
+ // Easier to swap out cornerstoneDICOMImageLoader at a later date.
69
+ return {
70
+ BitsAllocated: bitsAllocated,
71
+ BitsStored: bitsStored,
72
+ SamplesPerPixel: samplesPerPixel,
73
+ HighBit: highBit,
74
+ PhotometricInterpretation: photometricInterpretation,
75
+ PixelRepresentation: pixelRepresentation,
76
+ Modality: modality,
77
+ ImageOrientationPatient: imageOrientationPatient,
78
+ PixelSpacing: pixelSpacing,
79
+ FrameOfReferenceUID: frameOfReferenceUID,
80
+ Columns: columns,
81
+ Rows: rows,
82
+ // This is a reshaped object and not a dicom tag:
83
+ voiLut,
84
+ VOILUTFunction: voiLUTFunction,
85
+ SeriesInstanceUID: seriesInstanceUID,
86
+ };
87
+ }
@@ -0,0 +1,174 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ import { metaData, getConfiguration } from '../';
3
+ import { Point3 } from '../types';
4
+
5
+ type SortedImageIdsItem = {
6
+ zSpacing: number;
7
+ origin: Point3;
8
+ sortedImageIds: Array<string>;
9
+ };
10
+ /**
11
+ * Given an array of imageIds, sort them based on their imagePositionPatient, and
12
+ * also returns the spacing between images and the origin of the reference image
13
+ *
14
+ * @param imageIds - array of imageIds
15
+ * @param scanAxisNormal - [x, y, z] array or gl-matrix vec3
16
+ *
17
+ * @returns The sortedImageIds, zSpacing, and origin of the first image in the series.
18
+ */
19
+ export default function sortImageIdsAndGetSpacing(
20
+ imageIds: Array<string>,
21
+ scanAxisNormal?: vec3
22
+ ): SortedImageIdsItem {
23
+ const {
24
+ imagePositionPatient: referenceImagePositionPatient,
25
+ imageOrientationPatient,
26
+ } = metaData.get('imagePlaneModule', imageIds[0]);
27
+
28
+ if (!scanAxisNormal) {
29
+ const rowCosineVec = vec3.fromValues(
30
+ imageOrientationPatient[0],
31
+ imageOrientationPatient[1],
32
+ imageOrientationPatient[2]
33
+ );
34
+ const colCosineVec = vec3.fromValues(
35
+ imageOrientationPatient[3],
36
+ imageOrientationPatient[4],
37
+ imageOrientationPatient[5]
38
+ );
39
+
40
+ scanAxisNormal = vec3.create();
41
+ vec3.cross(scanAxisNormal, rowCosineVec, colCosineVec);
42
+ }
43
+
44
+ const refIppVec = vec3.create();
45
+
46
+ // Check if we are using wadouri scheme
47
+ const usingWadoUri = imageIds[0].split(':')[0] === 'wadouri';
48
+
49
+ vec3.set(
50
+ refIppVec,
51
+ referenceImagePositionPatient[0],
52
+ referenceImagePositionPatient[1],
53
+ referenceImagePositionPatient[2]
54
+ );
55
+
56
+ let sortedImageIds: string[];
57
+ let zSpacing: number;
58
+
59
+ function getDistance(imageId: string) {
60
+ const { imagePositionPatient } = metaData.get('imagePlaneModule', imageId);
61
+
62
+ const positionVector = vec3.create();
63
+
64
+ vec3.sub(
65
+ positionVector,
66
+ referenceImagePositionPatient,
67
+ imagePositionPatient
68
+ );
69
+
70
+ return vec3.dot(positionVector, scanAxisNormal);
71
+ }
72
+
73
+ /**
74
+ * If we are using wadors and so have all image metadata cached ahead of time,
75
+ * then sort by image position in 3D space, and calculate average slice
76
+ * spacing from the entire volume. If not, then use the sampled images (1st
77
+ * and middle) to calculate slice spacing, and use the provided imageId order.
78
+ * Correct sorting must be done ahead of time.
79
+ */
80
+ if (!usingWadoUri) {
81
+ const distanceImagePairs = imageIds.map((imageId) => {
82
+ const distance = getDistance(imageId);
83
+
84
+ return {
85
+ distance,
86
+ imageId,
87
+ };
88
+ });
89
+
90
+ distanceImagePairs.sort((a, b) => b.distance - a.distance);
91
+
92
+ sortedImageIds = distanceImagePairs.map((a) => a.imageId);
93
+ const numImages = distanceImagePairs.length;
94
+
95
+ // Calculated average spacing.
96
+ // We would need to resample if these are not similar.
97
+ // It should be up to the host app to do this if it needed to.
98
+ zSpacing =
99
+ Math.abs(
100
+ distanceImagePairs[numImages - 1].distance -
101
+ distanceImagePairs[0].distance
102
+ ) /
103
+ (numImages - 1);
104
+ } else {
105
+ // Using wadouri, so we have only prefetched the first, middle, and last
106
+ // images for metadata. Assume initial imageId array order is pre-sorted,
107
+ // but check orientation.
108
+ const prefetchedImageIds = [
109
+ imageIds[0],
110
+ imageIds[Math.floor(imageIds.length / 2)],
111
+ ];
112
+ sortedImageIds = imageIds;
113
+ const firstImageDistance = getDistance(prefetchedImageIds[0]);
114
+ const middleImageDistance = getDistance(prefetchedImageIds[1]);
115
+ if (firstImageDistance - middleImageDistance < 0) {
116
+ sortedImageIds.reverse();
117
+ }
118
+
119
+ // Calculate average spacing between the first and middle prefetched images,
120
+ // otherwise fall back to DICOM `spacingBetweenSlices`
121
+ const metadataForMiddleImage = metaData.get(
122
+ 'imagePlaneModule',
123
+ prefetchedImageIds[1]
124
+ );
125
+ if (!metadataForMiddleImage) {
126
+ throw new Error('Incomplete metadata required for volume construction.');
127
+ }
128
+
129
+ const positionVector = vec3.create();
130
+
131
+ vec3.sub(
132
+ positionVector,
133
+ referenceImagePositionPatient,
134
+ metadataForMiddleImage.imagePositionPatient
135
+ );
136
+ const distanceBetweenFirstAndMiddleImages = vec3.dot(
137
+ positionVector,
138
+ scanAxisNormal
139
+ );
140
+ zSpacing =
141
+ Math.abs(distanceBetweenFirstAndMiddleImages) /
142
+ Math.floor(imageIds.length / 2);
143
+ }
144
+
145
+ const { imagePositionPatient: origin, sliceThickness } = metaData.get(
146
+ 'imagePlaneModule',
147
+ sortedImageIds[0]
148
+ );
149
+
150
+ const { strictZSpacingForVolumeViewport } = getConfiguration().rendering;
151
+
152
+ // We implemented these lines for multiframe dicom files that does not have
153
+ // position for each frame, leading to incorrect calculation of zSpacing = 0
154
+ // If possible, we use the sliceThickness, but we warn about this dicom file
155
+ // weirdness. If sliceThickness is not available, we set to 1 just to render
156
+ if (zSpacing === 0 && !strictZSpacingForVolumeViewport) {
157
+ if (sliceThickness) {
158
+ console.log('Could not calculate zSpacing. Using sliceThickness');
159
+ zSpacing = sliceThickness;
160
+ } else {
161
+ console.log(
162
+ 'Could not calculate zSpacing. The VolumeViewport visualization is compromised. Setting zSpacing to 1 to render'
163
+ );
164
+ zSpacing = 1;
165
+ }
166
+ }
167
+ const result: SortedImageIdsItem = {
168
+ zSpacing,
169
+ origin,
170
+ sortedImageIds,
171
+ };
172
+
173
+ return result;
174
+ }
@@ -0,0 +1,38 @@
1
+ import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
2
+ import { IImage, PixelDataTypedArray } from '../types';
3
+
4
+ function updateVTKImageDataWithCornerstoneImage(
5
+ sourceImageData: vtkImageData,
6
+ image: IImage
7
+ ) {
8
+ const pixelData = image.getPixelData();
9
+ const scalarData = sourceImageData
10
+ .getPointData()
11
+ .getScalars()
12
+ .getData() as PixelDataTypedArray;
13
+
14
+ // if the color image is loaded with CPU previously, it loads it
15
+ // with RGBA, and here we need to remove the A channel from the
16
+ // pixel data.
17
+ if (image.color && image.rgba) {
18
+ const newPixelData = new Uint8Array(image.columns * image.rows * 3);
19
+ for (let i = 0; i < image.columns * image.rows; i++) {
20
+ newPixelData[i * 3] = pixelData[i * 4];
21
+ newPixelData[i * 3 + 1] = pixelData[i * 4 + 1];
22
+ newPixelData[i * 3 + 2] = pixelData[i * 4 + 2];
23
+ }
24
+ // modify the image object to have the correct pixel data for later
25
+ // use.
26
+ image.rgba = false;
27
+ image.getPixelData = () => newPixelData;
28
+ scalarData.set(newPixelData);
29
+ } else {
30
+ scalarData.set(pixelData);
31
+ }
32
+
33
+ // Trigger modified on the VTK Object so the texture is updated
34
+ // TODO: evaluate directly changing things with texSubImage3D later
35
+ sourceImageData.modified();
36
+ }
37
+
38
+ export { updateVTKImageDataWithCornerstoneImage };