@@ -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,114 @@ 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
+ // Rescale
221
+ const b = Number ( meta . RescaleIntercept )
222
+ const m = Number ( meta . RescaleSlope )
223
+ const hasIntercept = ! Number . isNaN ( b ) && b !== 0
224
+ const hasSlope = ! Number . isNaN ( m ) && m !== 1
225
+ let rescaleFunction
226
+ if ( hasIntercept && hasSlope ) {
227
+ data = data . map ( ( value ) => m * value + b )
228
+ } else if ( hasIntercept ) {
229
+ data = data . map ( ( value ) => value + b )
230
+ } else if ( hasSlope ) {
231
+ data = data . map ( ( value ) => m * value )
232
+ }
233
+
234
+ return {
235
+ imageType,
236
+ origin,
237
+ spacing,
238
+ direction,
239
+ size,
240
+ data,
241
+ }
242
+ }
123
243
}
124
244
125
245
class DICOMImage extends DICOMEntity {
@@ -147,6 +267,9 @@ class DICOMImage extends DICOMEntity {
147
267
'BitsStored' ,
148
268
'HighBit' ,
149
269
'PixelRepresentation' ,
270
+ 'PixelData' ,
271
+ 'RescaleIntercept' ,
272
+ 'RescaleSlope' ,
150
273
]
151
274
}
152
275
@@ -223,60 +346,86 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
223
346
return
224
347
}
225
348
226
- if ( element . fragments ) {
227
- console . warn ( `${ tagName } contains fragments which isn't supported` )
228
- return
229
- }
349
+ let value = undefined
350
+
351
+ if ( tagName === 'PixelData' ) {
352
+ if ( element . fragments ) {
353
+ let bot = element . basicOffsetTable
354
+ // if basic offset table is empty, calculate it
355
+ if ( bot . length === 0 ) {
356
+ bot = dicomParser . createJPEGBasicOffsetTable ( dataSet , element )
357
+ }
230
358
231
- let vr = element . vr
232
- if ( vr === undefined ) {
233
- if ( tagInfo === undefined || tagInfo . vr === undefined ) {
234
- console . warn ( `${ tagName } vr is unknown, skipping` )
359
+ const imageFrames = [ ]
360
+ for ( let frameIndex = 0 ; frameIndex < bot . length ; frameIndex += 1 ) {
361
+ imageFrames . push ( dicomParser . readEncapsulatedImageFrame ( dataSet , element , 0 , bot ) )
362
+ }
363
+ const buffer = imageFrames . length === 1
364
+ ? imageFrames [ 0 ]
365
+ : concatenate ( imageFrames [ 0 ] . constructor , imageFrames )
366
+ value = {
367
+ buffer,
368
+ offset : 0 ,
369
+ length : buffer . length ,
370
+ encapsulated : true ,
371
+ }
372
+ } else {
373
+ value = {
374
+ buffer : dataSet . byteArray . buffer ,
375
+ offset : element . dataOffset ,
376
+ length : element . length ,
377
+ encapsulated : false ,
378
+ }
379
+ }
380
+ } else {
381
+ let vr = element . vr
382
+ if ( vr === undefined ) {
383
+ if ( tagInfo === undefined || tagInfo . vr === undefined ) {
384
+ console . warn ( `${ tagName } vr is unknown, skipping` )
385
+ }
386
+ vr = tagInfo . vr
235
387
}
236
- vr = tagInfo . vr
237
- }
238
388
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 ) {
389
+ switch ( vr ) {
390
+ case 'US' :
269
391
value = dataSet . uint16 ( tag )
270
- } else if ( element . length === 4 ) {
392
+ break
393
+ case 'SS' :
394
+ value = dataSet . int16 ( tag )
395
+ break
396
+ case 'UL' :
271
397
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
398
+ break
399
+ case 'US' :
400
+ value = dataSet . int32 ( tag )
401
+ break
402
+ case 'FD' :
403
+ value = dataSet . double ( tag )
404
+ break
405
+ case 'FL' :
406
+ value = dataSet . float ( tag )
407
+ break
408
+ case 'AT' :
409
+ value = `(${ dataSet . uint16 ( tag , 0 ) } ,${ dataSet . uint16 ( tag , 1 ) } )`
410
+ break
411
+ case 'OB' :
412
+ case 'OW' :
413
+ case 'UN' :
414
+ case 'OF' :
415
+ case 'UT' :
416
+ // TODO: binary data? is this correct?
417
+ if ( element . length === 2 ) {
418
+ value = dataSet . uint16 ( tag )
419
+ } else if ( element . length === 4 ) {
420
+ value = dataSet . uint32 ( tag )
421
+ } else {
422
+ return
423
+ }
424
+ break
425
+ default : //string
426
+ value = dataSet . string ( tag )
427
+ break
428
+ }
280
429
}
281
430
282
431
metaData [ tagName ] = value
0 commit comments