@@ -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,138 @@ 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+ let origin = [ 0 , 0 , 0 ]
147+ if ( meta . ImagePositionPatient !== undefined ) {
148+ origin = numArrayFromString ( meta . ImagePositionPatient )
149+ }
150+
151+ // Spacing
152+ let spacing = [ 1 , 1 ] ;
153+ if ( meta . PixelSpacing !== undefined ) {
154+ spacing = numArrayFromString ( meta . PixelSpacing ) ;
155+ }
156+ if ( meta . SliceThickness !== undefined ) {
157+ spacing . push ( Number ( meta . SliceThickness ) )
158+ } else {
159+ spacing . push ( 1 ) ;
160+ }
161+
162+ // Dimensions
163+ const size = [
164+ meta . Rows ,
165+ meta . Columns ,
166+ Object . keys ( this . images ) . length ,
167+ ]
168+
169+ // Direction matrix (3x3)
170+ let directionCosines = [ 1 , 0 , 0 , 0 , 1 , 0 ]
171+ if ( meta . ImageOrientationPatient !== undefined ) {
172+ directionCosines = numArrayFromString ( meta . ImageOrientationPatient )
173+ }
174+ const iDirCos = directionCosines . slice ( 0 , 3 )
175+ const jDirCos = directionCosines . slice ( 3 , 6 )
176+ const kDirCos = [
177+ iDirCos [ 1 ] * jDirCos [ 2 ] - iDirCos [ 2 ] * jDirCos [ 1 ] ,
178+ iDirCos [ 2 ] * jDirCos [ 0 ] - iDirCos [ 0 ] * jDirCos [ 2 ] ,
179+ iDirCos [ 0 ] * jDirCos [ 1 ] - iDirCos [ 1 ] * jDirCos [ 0 ] ,
180+ ]
181+ const direction = {
182+ rows : 3 ,
183+ columns : 3 ,
184+ data : [
185+ iDirCos [ 0 ] , jDirCos [ 0 ] , kDirCos [ 0 ] ,
186+ iDirCos [ 1 ] , jDirCos [ 1 ] , kDirCos [ 1 ] ,
187+ iDirCos [ 2 ] , jDirCos [ 2 ] , kDirCos [ 2 ] ,
188+ ] ,
189+ }
190+
191+ // Pixel data type
192+ let slope = 1
193+ if ( meta . RescaleSlope !== undefined ) {
194+ slope = Number ( meta . RescaleSlope )
195+ }
196+ let intercept = 0
197+ if ( meta . RescaleIntercept !== undefined ) {
198+ intercept = Number ( meta . RescaleIntercept )
199+ }
200+ let smallestValue = 0
201+ if ( meta . SmallestImagePixelValue !== undefined ) {
202+ smallestValue = Number ( meta . SmallestImagePixelValue )
203+ }
204+ const hasNegativeValues = ( slope < 0 ) || ( smallestValue + intercept ) < 0 ;
205+ const unsigned = meta . PixelRepresentation === 0 && ! hasNegativeValues ;
206+ const bits = meta . BitsAllocated
207+ let ArrayType
208+ let intType
209+ switch ( bits ) {
210+ case 8 :
211+ ArrayType = unsigned ? Uint8Array : Int8Array
212+ intType = unsigned ? 'uint8_t' : 'int8_t'
213+ break
214+ case 16 :
215+ ArrayType = unsigned ? Uint16Array : Int16Array
216+ intType = unsigned ? 'uint16_t' : 'int16_t'
217+ break
218+ case 32 :
219+ ArrayType = unsigned ? Uint32Array : Int32Array
220+ intType = unsigned ? 'uint32_t' : 'int32_t'
221+ break
222+ default :
223+ throw Error ( `Unknown pixel bit type (${ bits } )` )
224+ }
225+
226+ // Image info
227+ const imageType = {
228+ dimension : 3 ,
229+ componentType : intType ,
230+ pixelType : 1 , // TODO: based on meta.PhotometricInterpretation?
231+ components : meta . SamplesPerPixel ,
232+ }
233+
234+ // Dataview on pixel data
235+ const pixelDataArrays = slices . map ( ( image ) => {
236+ const value = image . metaData . PixelData
237+ return new ArrayType ( value . buffer , value . offset )
238+ } )
239+
240+ // Concatenate all pixel data
241+ const data = concatenate ( ArrayType , pixelDataArrays )
242+
243+ // Rescale
244+ const b = Number ( meta . RescaleIntercept )
245+ const m = Number ( meta . RescaleSlope )
246+ const hasIntercept = ! Number . isNaN ( b ) && b !== 0
247+ const hasSlope = ! Number . isNaN ( m ) && m !== 1
248+ let rescaleFunction
249+ if ( hasIntercept && hasSlope ) {
250+ data = data . map ( ( value ) => m * value + b )
251+ } else if ( hasIntercept ) {
252+ data = data . map ( ( value ) => value + b )
253+ } else if ( hasSlope ) {
254+ data = data . map ( ( value ) => m * value )
255+ }
256+
257+ return {
258+ imageType,
259+ name : "Image" ,
260+ origin,
261+ spacing,
262+ direction,
263+ size,
264+ data,
265+ }
266+ }
123267}
124268
125269class DICOMImage extends DICOMEntity {
@@ -147,6 +291,9 @@ class DICOMImage extends DICOMEntity {
147291 'BitsStored' ,
148292 'HighBit' ,
149293 'PixelRepresentation' ,
294+ 'PixelData' ,
295+ 'RescaleIntercept' ,
296+ 'RescaleSlope' ,
150297 ]
151298 }
152299
@@ -223,60 +370,68 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
223370 return
224371 }
225372
226- if ( element . fragments ) {
227- console . warn ( `${ tagName } contains fragments which isn't supported` )
228- return
229- }
373+ let value = undefined
230374
231- let vr = element . vr
232- if ( vr === undefined ) {
233- if ( tagInfo === undefined || tagInfo . vr === undefined ) {
234- console . warn ( `${ tagName } vr is unknown, skipping` )
375+ if ( tagName === 'PixelData' ) {
376+ if ( element . fragments ) {
377+ throw new Error ( 'Fragments/encapsulated pixel data is not supported.' ) ;
378+ } else {
379+ value = {
380+ buffer : dataSet . byteArray . buffer ,
381+ offset : element . dataOffset ,
382+ length : element . length ,
383+ }
384+ }
385+ } else {
386+ let vr = element . vr
387+ if ( vr === undefined ) {
388+ if ( tagInfo === undefined || tagInfo . vr === undefined ) {
389+ console . warn ( `${ tagName } vr is unknown, skipping` )
390+ } else {
391+ vr = tagInfo . vr
392+ }
235393 }
236- vr = tagInfo . vr
237- }
238394
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 ) {
395+ switch ( vr ) {
396+ case 'US' :
269397 value = dataSet . uint16 ( tag )
270- } else if ( element . length === 4 ) {
398+ break
399+ case 'SS' :
400+ value = dataSet . int16 ( tag )
401+ break
402+ case 'UL' :
271403 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
404+ break
405+ case 'US' :
406+ value = dataSet . int32 ( tag )
407+ break
408+ case 'FD' :
409+ value = dataSet . double ( tag )
410+ break
411+ case 'FL' :
412+ value = dataSet . float ( tag )
413+ break
414+ case 'AT' :
415+ value = `(${ dataSet . uint16 ( tag , 0 ) } ,${ dataSet . uint16 ( tag , 1 ) } )`
416+ break
417+ case 'OB' :
418+ case 'OW' :
419+ case 'UN' :
420+ case 'OF' :
421+ case 'UT' :
422+ // TODO: binary data? is this correct?
423+ if ( element . length === 2 ) {
424+ value = dataSet . uint16 ( tag )
425+ } else if ( element . length === 4 ) {
426+ value = dataSet . uint32 ( tag )
427+ } else {
428+ return
429+ }
430+ break
431+ default : //string
432+ value = dataSet . string ( tag )
433+ break
434+ }
280435 }
281436
282437 metaData [ tagName ] = value
0 commit comments