@@ -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,140 @@ 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+ // 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+ }
123269}
124270
125271class DICOMImage extends DICOMEntity {
@@ -147,6 +293,9 @@ class DICOMImage extends DICOMEntity {
147293 'BitsStored' ,
148294 'HighBit' ,
149295 'PixelRepresentation' ,
296+ 'PixelData' ,
297+ 'RescaleIntercept' ,
298+ 'RescaleSlope' ,
150299 ]
151300 }
152301
@@ -223,60 +372,86 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
223372 return
224373 }
225374
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+ }
230384
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
235413 }
236- vr = tagInfo . vr
237- }
238414
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' :
269417 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' :
271423 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+ }
280455 }
281456
282457 metaData [ tagName ] = value
0 commit comments