@@ -34,9 +34,9 @@ async function extract(buffer: Uint8Array, nbytes: number): Promise<Uint8Array<A
3434
3535export async function loadHeader ( file : BIDSFile ) : Promise < NiftiHeader > {
3636 const buf = await readBytes ( file , 1024 )
37+ let header
3738 try {
3839 const data = isCompressed ( buf . buffer ) ? await extract ( buf , 540 ) : buf . slice ( 0 , 540 )
39- let header
4040 if ( isNIFTI1 ( data . buffer ) ) {
4141 header = new NIFTI1 ( )
4242 // Truncate to 348 bytes to avoid attempting to parse extensions
@@ -48,29 +48,30 @@ export async function loadHeader(file: BIDSFile): Promise<NiftiHeader> {
4848 if ( ! header ) {
4949 throw { code : 'NIFTI_HEADER_UNREADABLE' }
5050 }
51- const ndim = header . dims [ 0 ]
52- return {
53- dim : header . dims ,
54- // Hack: round pixdim to 3 decimal places; schema should add rounding function
55- pixdim : header . pixDims . map ( ( pixdim ) => Math . round ( pixdim * 1000 ) / 1000 ) ,
56- shape : header . dims . slice ( 1 , ndim + 1 ) ,
57- voxel_sizes : header . pixDims . slice ( 1 , ndim + 1 ) ,
58- dim_info : {
59- freq : header . dim_info & 0x03 ,
60- phase : ( header . dim_info >> 2 ) & 0x03 ,
61- slice : ( header . dim_info >> 4 ) & 0x03 ,
62- } ,
63- xyzt_units : {
64- xyz : [ 'unknown' , 'meter' , 'mm' , 'um' ] [ header . xyzt_units & 0x03 ] ,
65- t : [ 'unknown' , 'sec' , 'msec' , 'usec' ] [ ( header . xyzt_units >> 3 ) & 0x03 ] ,
66- } ,
67- qform_code : header . qform_code ,
68- sform_code : header . sform_code ,
69- axis_codes : axisCodes ( header . affine ) ,
70- } as NiftiHeader
7151 } catch ( err ) {
7252 throw { code : 'NIFTI_HEADER_UNREADABLE' }
7353 }
54+
55+ const ndim = header . dims [ 0 ]
56+ return {
57+ dim : header . dims ,
58+ // Hack: round pixdim to 3 decimal places; schema should add rounding function
59+ pixdim : header . pixDims . map ( ( pixdim ) => Math . round ( pixdim * 1000 ) / 1000 ) ,
60+ shape : header . dims . slice ( 1 , ndim + 1 ) ,
61+ voxel_sizes : header . pixDims . slice ( 1 , ndim + 1 ) ,
62+ dim_info : {
63+ freq : header . dim_info & 0x03 ,
64+ phase : ( header . dim_info >> 2 ) & 0x03 ,
65+ slice : ( header . dim_info >> 4 ) & 0x03 ,
66+ } ,
67+ xyzt_units : {
68+ xyz : [ 'unknown' , 'meter' , 'mm' , 'um' ] [ header . xyzt_units & 0x03 ] ,
69+ t : [ 'unknown' , 'sec' , 'msec' , 'usec' ] [ ( header . xyzt_units >> 3 ) & 0x03 ] ,
70+ } ,
71+ qform_code : header . qform_code ,
72+ sform_code : header . sform_code ,
73+ axis_codes : axisCodes ( header . affine ) ,
74+ } as NiftiHeader
7475}
7576
7677/** Vector addition */
@@ -124,13 +125,18 @@ function argMax(arr: number[]): number {
124125 *
125126 * @returns character codes describing the orientation of an image affine.
126127 */
127- export function axisCodes ( affine : number [ ] [ ] ) : string [ ] {
128+ export function axisCodes ( affine : number [ ] [ ] ) : string [ ] | null {
128129 // This function is an extract of the Python function transforms3d.affines.decompose44
129130 // (https://github.com/matthew-brett/transforms3d/blob/6a43a98/transforms3d/affines.py#L10-L153)
130131 //
131132 // As an optimization, this only orthogonalizes the basis,
132133 // and does not normalize to unit vectors.
133134
135+ // Bad qforms result in NaNs in the rotation matrix
136+ if ( affine . some ( ( row ) => row . some ( ( val ) => ! Number . isFinite ( val ) ) ) ) {
137+ return null
138+ }
139+
134140 // Operate on columns, which are the cosines that project input coordinates onto output axes
135141 const [ cosX , cosY , cosZ ] = [ 0 , 1 , 2 ] . map ( ( j ) => [ 0 , 1 , 2 ] . map ( ( i ) => affine [ i ] [ j ] ) )
136142
@@ -148,7 +154,7 @@ export function axisCodes(affine: number[][]): string[] {
148154
149155 // Check that indices are 0, 1 and 2 in some order
150156 if ( maxIndices . toSorted ( ) . some ( ( idx , i ) => idx !== i ) ) {
151- throw { key : 'AMBIGUOUS_AFFINE' }
157+ throw { code : 'AMBIGUOUS_AFFINE' }
152158 }
153159
154160 // Positive/negative codes for each world axis
0 commit comments