@@ -5,6 +5,18 @@ import "regenerator-runtime/runtime";
5
5
6
6
import DICOM_TAG_DICT from './dicomTags'
7
7
8
+ function concatenate ( resultConstructor , arrays ) {
9
+ const totalLength = arrays . reduce ( ( total , arr ) => {
10
+ return total + arr . length
11
+ } , 0 ) ;
12
+ const result = new resultConstructor ( totalLength ) ;
13
+ arrays . reduce ( ( offset , arr ) => {
14
+ result . set ( arr , offset ) ;
15
+ return offset + arr . length ;
16
+ } , 0 ) ;
17
+ return result ;
18
+ }
19
+
8
20
class DICOMEntity {
9
21
constructor ( ) {
10
22
this . metaData = { }
@@ -120,6 +132,140 @@ class DICOMSeries extends DICOMEntity {
120
132
}
121
133
this . images [ imageNumber ] = new DICOMImage ( metaData , file )
122
134
}
135
+
136
+ getImageData ( ) {
137
+ function numArrayFromString ( str , separator = '\\' ) {
138
+ const strArray = str . split ( separator )
139
+ return strArray . map ( Number )
140
+ }
141
+
142
+ const slices = Object . values ( this . images )
143
+ const meta = slices [ 0 ] . metaData
144
+
145
+ // Origin
146
+ const origin = numArrayFromString ( meta . ImagePositionPatient )
147
+
148
+ // Spacing
149
+ const spacing = numArrayFromString ( meta . PixelSpacing )
150
+ spacing . push ( Number ( meta . SliceThickness ) ) // TODO: or SpacingBetweenSlices?
151
+
152
+ // Dimensions
153
+ const size = [
154
+ meta . Rows ,
155
+ meta . Columns ,
156
+ Object . keys ( this . images ) . length ,
157
+ ]
158
+
159
+ // Direction matrix (3x3)
160
+ const directionCosines = numArrayFromString ( meta . ImageOrientationPatient )
161
+ const iDirCos = directionCosines . slice ( 0 , 3 )
162
+ const jDirCos = directionCosines . slice ( 3 , 6 )
163
+ const kDirCos = [
164
+ iDirCos [ 1 ] * jDirCos [ 2 ] - iDirCos [ 2 ] * jDirCos [ 1 ] ,
165
+ iDirCos [ 2 ] * jDirCos [ 0 ] - iDirCos [ 0 ] * jDirCos [ 2 ] ,
166
+ iDirCos [ 0 ] * jDirCos [ 1 ] - iDirCos [ 1 ] * jDirCos [ 0 ] ,
167
+ ]
168
+ const direction = {
169
+ data : [
170
+ iDirCos [ 0 ] , jDirCos [ 0 ] , kDirCos [ 0 ] ,
171
+ iDirCos [ 1 ] , jDirCos [ 1 ] , kDirCos [ 1 ] ,
172
+ iDirCos [ 2 ] , jDirCos [ 2 ] , kDirCos [ 2 ] ,
173
+ ] ,
174
+ }
175
+
176
+ // Pixel data type
177
+ const unsigned = ( meta . PixelRepresentation === 0 ) && ( meta . RescaleIntercept > 0 )
178
+ const bits = meta . BitsAllocated
179
+ let ArrayType
180
+ let intType
181
+ switch ( bits ) {
182
+ case 8 :
183
+ ArrayType = unsigned ? Uint8Array : Int8Array
184
+ intType = unsigned ? 'uint8_t' : 'int8_t'
185
+ break
186
+ case 16 :
187
+ ArrayType = unsigned ? Uint16Array : Int16Array
188
+ intType = unsigned ? 'uint16_t' : 'int16_t'
189
+ break
190
+ case 32 :
191
+ ArrayType = unsigned ? Uint32Array : Int32Array
192
+ intType = unsigned ? 'uint32_t' : 'int32_t'
193
+ break
194
+ default :
195
+ throw Error ( `Unknown pixel bit type (${ bits } )` )
196
+ }
197
+
198
+ // Image info
199
+ const imageType = {
200
+ dimension : 3 ,
201
+ componentType : intType ,
202
+ pixelType : 1 , // TODO: based on meta.PhotometricInterpretation?
203
+ components : meta . SamplesPerPixel ,
204
+ }
205
+
206
+ // Dataview on pixel data
207
+ const pixelDataArrays = slices . map ( ( image ) => {
208
+ const value = image . metaData . PixelData
209
+ if ( value . buffer . constructor === ArrayType && value . offset === 0 ) {
210
+ return value . buffer
211
+ }
212
+ return new ArrayType ( value . buffer , value . offset )
213
+ } )
214
+
215
+ // Concatenate all pixel data
216
+ const data = pixelDataArrays . length === 1
217
+ ? pixelDataArrays [ 0 ]
218
+ : concatenate ( ArrayType , pixelDataArrays )
219
+
220
+ // Masking bits
221
+ let maskFunction
222
+ if ( meta . BitsStored !== bits ) {
223
+ let mask = ''
224
+ for ( let i = 0 ; i < bits ; i += 1 ) {
225
+ if ( i < meta . HighBit - meta . BitsStored || i > meta . HighBit ) {
226
+ mask += '0'
227
+ } else {
228
+ mask += '1'
229
+ }
230
+ }
231
+ maskFunction = ( value , index ) => { data [ index ] = value & mask }
232
+ }
233
+
234
+ // Rescale
235
+ const b = Number ( meta . RescaleIntercept )
236
+ const m = Number ( meta . RescaleSlope )
237
+ const hasIntercept = ! Number . isNaN ( b ) && b !== 0
238
+ const hasSlope = ! Number . isNaN ( m ) && m !== 1
239
+ let rescaleFunction
240
+ if ( hasIntercept && hasSlope ) {
241
+ rescaleFunction = ( value , index ) => { data [ index ] = m * value + b }
242
+ } else if ( hasIntercept ) {
243
+ rescaleFunction = ( value , index ) => { data [ index ] = value + b }
244
+ } else if ( hasSlope ) {
245
+ rescaleFunction = ( value , index ) => { data [ index ] = m * value }
246
+ }
247
+
248
+ // Apply transformations if needed
249
+ if ( maskFunction && rescaleFunction ) {
250
+ data . forEach ( ( _ , index ) => {
251
+ maskFunction ( data [ index ] , index )
252
+ rescaleFunction ( data [ index ] , index )
253
+ } )
254
+ } else if ( maskFunction ) {
255
+ data . forEach ( maskFunction )
256
+ } else if ( rescaleFunction ) {
257
+ data . forEach ( rescaleFunction )
258
+ }
259
+
260
+ return {
261
+ imageType,
262
+ origin,
263
+ spacing,
264
+ direction,
265
+ size,
266
+ data,
267
+ }
268
+ }
123
269
}
124
270
125
271
class DICOMImage extends DICOMEntity {
@@ -147,6 +293,9 @@ class DICOMImage extends DICOMEntity {
147
293
'BitsStored' ,
148
294
'HighBit' ,
149
295
'PixelRepresentation' ,
296
+ 'PixelData' ,
297
+ 'RescaleIntercept' ,
298
+ 'RescaleSlope' ,
150
299
]
151
300
}
152
301
@@ -223,60 +372,86 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
223
372
return
224
373
}
225
374
226
- if ( element . fragments ) {
227
- console . warn ( `${ tagName } contains fragments which isn't supported` )
228
- return
229
- }
375
+ let value = undefined
376
+
377
+ if ( tagName === 'PixelData' ) {
378
+ if ( element . fragments ) {
379
+ let bot = element . basicOffsetTable
380
+ // if basic offset table is empty, calculate it
381
+ if ( bot . length === 0 ) {
382
+ bot = dicomParser . createJPEGBasicOffsetTable ( dataSet , element )
383
+ }
230
384
231
- let vr = element . vr
232
- if ( vr === undefined ) {
233
- if ( tagInfo === undefined || tagInfo . vr === undefined ) {
234
- console . warn ( `${ tagName } vr is unknown, skipping` )
385
+ const imageFrames = [ ]
386
+ for ( let frameIndex = 0 ; frameIndex < bot . length ; frameIndex += 1 ) {
387
+ imageFrames . push ( dicomParser . readEncapsulatedImageFrame ( dataSet , element , 0 , bot ) )
388
+ }
389
+ const buffer = imageFrames . length === 1
390
+ ? imageFrames [ 0 ]
391
+ : concatenate ( imageFrames [ 0 ] . constructor , imageFrames )
392
+ value = {
393
+ buffer,
394
+ offset : 0 ,
395
+ length : buffer . length ,
396
+ encapsulated : true ,
397
+ }
398
+ } else {
399
+ value = {
400
+ buffer : dataSet . byteArray . buffer ,
401
+ offset : element . dataOffset ,
402
+ length : element . length ,
403
+ encapsulated : false ,
404
+ }
405
+ }
406
+ } else {
407
+ let vr = element . vr
408
+ if ( vr === undefined ) {
409
+ if ( tagInfo === undefined || tagInfo . vr === undefined ) {
410
+ console . warn ( `${ tagName } vr is unknown, skipping` )
411
+ }
412
+ vr = tagInfo . vr
235
413
}
236
- vr = tagInfo . vr
237
- }
238
414
239
- let value = undefined
240
- switch ( vr ) {
241
- case 'US' :
242
- value = dataSet . uint16 ( tag )
243
- break
244
- case 'SS' :
245
- value = dataSet . int16 ( tag )
246
- break
247
- case 'UL' :
248
- value = dataSet . uint32 ( tag )
249
- break
250
- case 'US' :
251
- value = dataSet . int32 ( tag )
252
- break
253
- case 'FD' :
254
- value = dataSet . double ( tag )
255
- break
256
- case 'FL' :
257
- value = dataSet . float ( tag )
258
- break
259
- case 'AT' :
260
- value = `(${ dataSet . uint16 ( tag , 0 ) } ,${ dataSet . uint16 ( tag , 1 ) } )`
261
- break
262
- case 'OB' :
263
- case 'OW' :
264
- case 'UN' :
265
- case 'OF' :
266
- case 'UT' :
267
- // TODO: binary data? is this correct?
268
- if ( element . length === 2 ) {
415
+ switch ( vr ) {
416
+ case 'US' :
269
417
value = dataSet . uint16 ( tag )
270
- } else if ( element . length === 4 ) {
418
+ break
419
+ case 'SS' :
420
+ value = dataSet . int16 ( tag )
421
+ break
422
+ case 'UL' :
271
423
value = dataSet . uint32 ( tag )
272
- } else {
273
- // don't store binary data, only meta data
274
- return
275
- }
276
- break
277
- default : //string
278
- value = dataSet . string ( tag )
279
- break
424
+ break
425
+ case 'US' :
426
+ value = dataSet . int32 ( tag )
427
+ break
428
+ case 'FD' :
429
+ value = dataSet . double ( tag )
430
+ break
431
+ case 'FL' :
432
+ value = dataSet . float ( tag )
433
+ break
434
+ case 'AT' :
435
+ value = `(${ dataSet . uint16 ( tag , 0 ) } ,${ dataSet . uint16 ( tag , 1 ) } )`
436
+ break
437
+ case 'OB' :
438
+ case 'OW' :
439
+ case 'UN' :
440
+ case 'OF' :
441
+ case 'UT' :
442
+ // TODO: binary data? is this correct?
443
+ if ( element . length === 2 ) {
444
+ value = dataSet . uint16 ( tag )
445
+ } else if ( element . length === 4 ) {
446
+ value = dataSet . uint32 ( tag )
447
+ } else {
448
+ return
449
+ }
450
+ break
451
+ default : //string
452
+ value = dataSet . string ( tag )
453
+ break
454
+ }
280
455
}
281
456
282
457
metaData [ tagName ] = value
0 commit comments