3
3
IImageVolume ,
4
4
VOIRange ,
5
5
ScalingParameters ,
6
- VOI ,
7
6
} from '../../types' ;
8
7
import { loadAndCacheImage } from '../../loaders/imageLoader' ;
9
8
import * as metaData from '../../metaData' ;
@@ -20,7 +19,6 @@ const REQUEST_TYPE = RequestType.Prefetch;
20
19
* loads the middle slice image (middle imageId) and based on its min
21
20
* and max pixel values, it calculates the VOI.
22
21
* Finally it sets the VOI on the volumeActor transferFunction
23
- * For nifti image which imageIds is empty, try to look for voiLut from its metadata
24
22
* @param volumeActor - The volume actor
25
23
* @param imageVolume - The image volume that we want to set the VOI for.
26
24
* @param useNativeDataType - The image data type is native or Float32Array
@@ -30,41 +28,29 @@ async function setDefaultVolumeVOI(
30
28
imageVolume : IImageVolume ,
31
29
useNativeDataType : boolean
32
30
) : Promise < void > {
33
- let voi ;
34
- if ( imageVolume . imageIds ?. length ) {
35
- // If the volume is composed of imageIds, we can apply a default VOI based
36
- // on either the metadata or the min/max of the middle slice.
37
- voi = getVOIFromMetadata ( imageVolume ) ;
31
+ let voi = getVOIFromMetadata ( imageVolume ) ;
38
32
39
- if ( ! voi ) {
40
- voi = await getVOIFromMinMax ( imageVolume , useNativeDataType ) ;
41
- }
33
+ if ( ! voi ) {
34
+ voi = await getVOIFromMinMax ( imageVolume , useNativeDataType ) ;
35
+ }
42
36
43
- if ( ! voi || voi . lower === undefined || voi . upper === undefined ) {
44
- throw new Error (
45
- 'Could not get VOI from metadata, nor from the min max of the image middle slice'
46
- ) ;
47
- }
48
- voi = handlePreScaledVolume ( imageVolume , voi ) ;
49
- } else if ( imageVolume . metadata ?. voiLut ?. length > 0 ) {
50
- // case: .nitfi, .nrrd
51
- const index = Math . floor ( imageVolume . metadata ?. voiLut ?. length / 2 ) ;
52
- const voiLut : VOI = imageVolume . metadata . voiLut [ index ] ;
53
- voi = windowLevel . toLowHighRange (
54
- Number ( voiLut . windowWidth ) ,
55
- Number ( voiLut . windowCenter )
37
+ if ( ! voi || voi . lower === undefined || voi . upper === undefined ) {
38
+ throw new Error (
39
+ 'Could not get VOI from metadata, nor from the min max of the image middle slice'
56
40
) ;
57
41
}
58
- if ( voi ?. lower && voi ?. upper ) {
59
- const { lower, upper } = voi ;
60
- if ( lower === 0 && upper === 0 ) {
61
- return ;
62
- }
63
- volumeActor
64
- . getProperty ( )
65
- . getRGBTransferFunction ( 0 )
66
- . setMappingRange ( lower , upper ) ;
42
+
43
+ voi = handlePreScaledVolume ( imageVolume , voi ) ;
44
+ const { lower, upper } = voi ;
45
+
46
+ if ( lower === 0 && upper === 0 ) {
47
+ return ;
67
48
}
49
+
50
+ volumeActor
51
+ . getProperty ( )
52
+ . getRGBTransferFunction ( 0 )
53
+ . setMappingRange ( lower , upper ) ;
68
54
}
69
55
70
56
function handlePreScaledVolume ( imageVolume : IImageVolume , voi : VOIRange ) {
@@ -92,35 +78,36 @@ function handlePreScaledVolume(imageVolume: IImageVolume, voi: VOIRange) {
92
78
}
93
79
94
80
/**
95
- * Get the VOI from the metadata of the middle slice of the image volume. It checks
96
- * the metadata for the VOI and if it is not found, it returns null
81
+ * Get the VOI from the metadata of the middle slice of the image volume or the metadata of the image volume
82
+ * It checks the metadata for the VOI and if it is not found, it returns null
97
83
*
98
84
* @param imageVolume - The image volume that we want to get the VOI from.
99
85
* @returns VOIRange with lower and upper values
100
86
*/
101
87
function getVOIFromMetadata ( imageVolume : IImageVolume ) : VOIRange {
102
- const { imageIds } = imageVolume ;
103
-
104
- const imageIdIndex = Math . floor ( imageIds . length / 2 ) ;
105
- const imageId = imageIds [ imageIdIndex ] ;
106
-
107
- const voiLutModule = metaData . get ( 'voiLutModule' , imageId ) ;
108
-
109
- if ( voiLutModule && voiLutModule . windowWidth && voiLutModule . windowCenter ) {
110
- const { windowWidth, windowCenter } = voiLutModule ;
111
-
112
- const voi = {
113
- windowWidth : Array . isArray ( windowWidth ) ? windowWidth [ 0 ] : windowWidth ,
114
- windowCenter : Array . isArray ( windowCenter )
115
- ? windowCenter [ 0 ]
116
- : windowCenter ,
117
- } ;
118
-
88
+ const { imageIds, metadata } = imageVolume ;
89
+ let voi ;
90
+ if ( imageIds . length ) {
91
+ const imageIdIndex = Math . floor ( imageIds . length / 2 ) ;
92
+ const imageId = imageIds [ imageIdIndex ] ;
93
+ const voiLutModule = metaData . get ( 'voiLutModule' , imageId ) ;
94
+ if ( voiLutModule && voiLutModule . windowWidth && voiLutModule . windowCenter ) {
95
+ const { windowWidth, windowCenter } = voiLutModule ;
96
+ voi = {
97
+ windowWidth : Array . isArray ( windowWidth ) ? windowWidth [ 0 ] : windowWidth ,
98
+ windowCenter : Array . isArray ( windowCenter )
99
+ ? windowCenter [ 0 ]
100
+ : windowCenter ,
101
+ } ;
102
+ }
103
+ } else {
104
+ voi = metadata ?. voiLut ?. [ 0 ] ;
105
+ }
106
+ if ( voi ) {
119
107
const { lower, upper } = windowLevel . toLowHighRange (
120
108
Number ( voi . windowWidth ) ,
121
109
Number ( voi . windowCenter )
122
110
) ;
123
-
124
111
return {
125
112
lower,
126
113
upper,
@@ -133,90 +120,92 @@ function getVOIFromMetadata(imageVolume: IImageVolume): VOIRange {
133
120
* and max pixel values, it calculates the VOI.
134
121
*
135
122
* @param imageVolume - The image volume that we want to get the VOI from.
123
+ * @param useNativeDataType - The image data type is native or Float32Array
136
124
* @returns The VOIRange with lower and upper values
137
125
*/
138
126
async function getVOIFromMinMax (
139
127
imageVolume : IImageVolume ,
140
128
useNativeDataType : boolean
141
129
) : Promise < VOIRange > {
142
130
const { imageIds } = imageVolume ;
143
- const scalarData = imageVolume . getScalarData ( ) ;
144
-
145
- // Get the middle image from the list of imageIds
146
- const imageIdIndex = Math . floor ( imageIds . length / 2 ) ;
147
- const imageId = imageVolume . imageIds [ imageIdIndex ] ;
148
- const generalSeriesModule =
149
- metaData . get ( 'generalSeriesModule' , imageId ) || { } ;
150
- const { modality } = generalSeriesModule ;
151
- const modalityLutModule = metaData . get ( 'modalityLutModule' , imageId ) || { } ;
152
-
153
- const numImages = imageIds . length ;
154
- const bytesPerImage = scalarData . byteLength / numImages ;
155
- const voxelsPerImage = scalarData . length / numImages ;
156
- const bytePerPixel = scalarData . BYTES_PER_ELEMENT ;
157
-
158
- const scalingParameters : ScalingParameters = {
159
- rescaleSlope : modalityLutModule . rescaleSlope ,
160
- rescaleIntercept : modalityLutModule . rescaleIntercept ,
161
- modality,
162
- } ;
163
-
164
- let scalingParametersToUse ;
165
- if ( modality === 'PT' ) {
166
- const suvFactor = metaData . get ( 'scalingModule' , imageId ) ;
167
-
168
- if ( suvFactor ) {
169
- scalingParametersToUse = {
170
- ...scalingParameters ,
171
- suvbw : suvFactor . suvbw ,
172
- } ;
131
+ const numImages = imageIds ?. length || imageVolume . dimensions [ 2 ] ;
132
+ let image ;
133
+ if ( imageIds ?. length ) {
134
+ // Get index of the middle image
135
+ const imageIdIndex = Math . floor ( numImages / 2 ) ;
136
+ const imageId = imageVolume . imageIds [ imageIdIndex ] ;
137
+ const generalSeriesModule =
138
+ metaData . get ( 'generalSeriesModule' , imageId ) || { } ;
139
+ const { modality } = generalSeriesModule ;
140
+ const modalityLutModule = metaData . get ( 'modalityLutModule' , imageId ) || { } ;
141
+ const scalingParameters : ScalingParameters = {
142
+ rescaleSlope : modalityLutModule . rescaleSlope ,
143
+ rescaleIntercept : modalityLutModule . rescaleIntercept ,
144
+ modality,
145
+ } ;
146
+ let scalingParametersToUse ;
147
+ if ( modality === 'PT' ) {
148
+ const suvFactor = metaData . get ( 'scalingModule' , imageId ) ;
149
+ if ( suvFactor ) {
150
+ scalingParametersToUse = {
151
+ ...scalingParameters ,
152
+ suvbw : suvFactor . suvbw ,
153
+ } ;
154
+ }
155
+ }
156
+ const options = {
157
+ targetBuffer : {
158
+ type : useNativeDataType ? undefined : 'Float32Array' ,
159
+ } ,
160
+ priority : PRIORITY ,
161
+ requestType : REQUEST_TYPE ,
162
+ useNativeDataType,
163
+ preScale : {
164
+ enabled : true ,
165
+ scalingParameters : scalingParametersToUse ,
166
+ } ,
167
+ } ;
168
+ // Loading the middle slice image for a volume has two scenarios, the first one is that
169
+ // uses the same volumeLoader which might not resolve to an image (since for performance
170
+ // reasons volumes' pixelData is set via offset and length on the volume arrayBuffer
171
+ // when each slice is loaded). The second scenario is that the image might not reach
172
+ // to the volumeLoader, and an already cached image (with Image object) is used
173
+ // instead. For the first scenario, we use the arrayBuffer of the volume to get the correct
174
+ // slice for the imageScalarData, and for the second scenario we use the getPixelData
175
+ // on the Cornerstone IImage object to get the pixel data.
176
+ // Note: we don't want to use the derived or generated images for setting the
177
+ // default VOI, because they are not the original. This is ugly but don't
178
+ // know how to do it better.
179
+ image = cache . getImage ( imageId ) ;
180
+ if ( ! imageVolume . referencedImageIds ?. length ) {
181
+ // we should ignore the cache here,
182
+ // since we want to load the image from with the most
183
+ // recent prescale settings
184
+ image = await loadAndCacheImage ( imageId , {
185
+ ...options ,
186
+ ignoreCache : true ,
187
+ } ) ;
173
188
}
174
189
}
175
-
176
- const byteOffset = imageIdIndex * bytesPerImage ;
177
-
178
- const options = {
179
- targetBuffer : {
180
- type : useNativeDataType ? undefined : 'Float32Array' ,
181
- } ,
182
- priority : PRIORITY ,
183
- requestType : REQUEST_TYPE ,
184
- useNativeDataType,
185
- preScale : {
186
- enabled : true ,
187
- scalingParameters : scalingParametersToUse ,
188
- } ,
189
- } ;
190
-
191
- // Loading the middle slice image for a volume has two scenarios, the first one is that
192
- // uses the same volumeLoader which might not resolve to an image (since for performance
193
- // reasons volumes' pixelData is set via offset and length on the volume arrayBuffer
194
- // when each slice is loaded). The second scenario is that the image might not reach
195
- // to the volumeLoader, and an already cached image (with Image object) is used
196
- // instead. For the first scenario, we use the arrayBuffer of the volume to get the correct
197
- // slice for the imageScalarData, and for the second scenario we use the getPixelData
198
- // on the Cornerstone IImage object to get the pixel data.
199
- // Note: we don't want to use the derived or generated images for setting the
200
- // default VOI, because they are not the original. This is ugly but don't
201
- // know how to do it better.
202
- let image = cache . getImage ( imageId ) ;
203
-
204
- if ( ! imageVolume . referencedImageIds ?. length ) {
205
- // we should ignore the cache here,
206
- // since we want to load the image from with the most
207
- // recent prescale settings
208
- image = await loadAndCacheImage ( imageId , { ...options , ignoreCache : true } ) ;
190
+ let imageScalarData ;
191
+ if ( image ) {
192
+ imageScalarData = image . getPixelData ( ) ;
193
+ } else {
194
+ // If image data is missing such as .nifti and .nrrd image
195
+ // calculate offset of the middle slice
196
+ const scalarData = imageVolume . getScalarData ( ) ;
197
+ const imageIdIndex = Math . floor ( numImages / 2 ) ;
198
+ const bytesPerImage = scalarData . byteLength / numImages ;
199
+ const voxelsPerImage = scalarData . length / numImages ;
200
+ const bytePerPixel = scalarData . BYTES_PER_ELEMENT ;
201
+ const byteOffset = imageIdIndex * bytesPerImage ;
202
+ imageScalarData = _getImageScalarDataFromImageVolume (
203
+ imageVolume ,
204
+ byteOffset ,
205
+ bytePerPixel ,
206
+ voxelsPerImage
207
+ ) ;
209
208
}
210
-
211
- const imageScalarData = image
212
- ? image . getPixelData ( )
213
- : _getImageScalarDataFromImageVolume (
214
- imageVolume ,
215
- byteOffset ,
216
- bytePerPixel ,
217
- voxelsPerImage
218
- ) ;
219
-
220
209
// Get the min and max pixel values of the middle slice
221
210
const { min, max } = getMinMax ( imageScalarData ) ;
222
211
@@ -233,19 +222,15 @@ function _getImageScalarDataFromImageVolume(
233
222
voxelsPerImage
234
223
) {
235
224
const { scalarData } = imageVolume ;
236
- const { volumeBuffer } = scalarData ;
225
+ const { buffer } = scalarData ;
237
226
if ( scalarData . BYTES_PER_ELEMENT !== bytePerPixel ) {
238
227
byteOffset *= scalarData . BYTES_PER_ELEMENT / bytePerPixel ;
239
228
}
240
229
241
230
const TypedArray = scalarData . constructor ;
242
231
const imageScalarData = new TypedArray ( voxelsPerImage ) ;
243
232
244
- const volumeBufferView = new TypedArray (
245
- volumeBuffer ,
246
- byteOffset ,
247
- voxelsPerImage
248
- ) ;
233
+ const volumeBufferView = new TypedArray ( buffer , byteOffset , voxelsPerImage ) ;
249
234
250
235
imageScalarData . set ( volumeBufferView ) ;
251
236
0 commit comments