@@ -5,6 +5,18 @@ import "regenerator-runtime/runtime";
55
66import DICOM_TAG_DICT from './dicomTags'
77
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+
820class DICOMEntity {
921 constructor ( ) {
1022 this . metaData = { }
@@ -120,6 +132,114 @@ class DICOMSeries extends DICOMEntity {
120132 }
121133 this . images [ imageNumber ] = new DICOMImage ( metaData , file )
122134 }
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+ }
123243}
124244
125245class DICOMImage extends DICOMEntity {
@@ -147,6 +267,9 @@ class DICOMImage extends DICOMEntity {
147267 'BitsStored' ,
148268 'HighBit' ,
149269 'PixelRepresentation' ,
270+ 'PixelData' ,
271+ 'RescaleIntercept' ,
272+ 'RescaleSlope' ,
150273 ]
151274 }
152275
@@ -223,60 +346,86 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
223346 return
224347 }
225348
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+ }
230358
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
235387 }
236- vr = tagInfo . vr
237- }
238388
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' :
269391 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' :
271397 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+ }
280429 }
281430
282431 metaData [ tagName ] = value
0 commit comments