[Neuroimaging] [nibabel] sform/qform flipping left - right in the affine and (possibly) fsl

Matthew Brett matthew.brett at gmail.com
Mon Jul 11 04:29:54 EDT 2016


Hi,

On Sun, Jul 10, 2016 at 11:48 PM, Samuel St-Jean <stjeansam at gmail.com> wrote:
> So, if nibabel reads and write everything correctly, must be something down
> the fsl path doing non cool stuff. Question now is if I should bother fixing
> it and risk breaking stuff for other people as unseen side effects.
> At least I learned stuff, had no idea qform can not store all possible
> transformations, so that explains why it differs slightly from the sform
> after saving back.
>
> ---------
>
> And one week later (this above was an earlier draft) I got around to
> actually fix it, but I cannot explain why. If I save back the header with
> it, I do not experience the flip. If I tell nibabel to create an header
> because I did not pass anything, it seems to put back my data in RAS (think
> I saw this on the website, but I though it was only for internal
> representation).
>
> So, saving back header + I change the dtype manually (since the dtype from
> the header is used, which is almost always int16 to float32 for me) does not
> flip anything. Although I still can't figure out why the created header
> gives me flipped data, here is a small excerpt of said header.
>
> Nibabel saved back original header
>
> qform_name     Scanner Anat
> qform_code     1
> qto_xyz:1      -1.796652  0.000000  -0.000000  112.420448
> qto_xyz:2      0.000000  1.795833  -0.054337  -90.885376
> qto_xyz:3      0.000000  0.054236  1.799180  54.572971
> qto_xyz:4      0.000000  0.000000  0.000000  1.000000
> qform_xorient  Right-to-Left
> qform_yorient  Posterior-to-Anterior
> qform_zorient  Inferior-to-Superior
> sform_name     Scanner Anat
> sform_code     1
> sto_xyz:1      -1.796652  0.000000  0.000000  112.420448
> sto_xyz:2      0.000000  1.795833  -0.054337  -90.885376
> sto_xyz:3      0.000000  0.054236  1.799180  54.572971
> sto_xyz:4      0.000000  0.000000  0.000000  1.000000
> sform_xorient  Right-to-Left
> sform_yorient  Posterior-to-Anterior
> sform_zorient  Inferior-to-Superior
>
> Nibabel recreated header
>
>
> qform_name     Unknown
> qform_code     0
> qto_xyz:1      1.796652  0.000000  0.000000  0.000000
> qto_xyz:2      0.000000  1.796652  0.000000  0.000000
> qto_xyz:3      0.000000  0.000000  1.800000  0.000000
> qto_xyz:4      0.000000  0.000000  0.000000  1.000000
> qform_xorient  Left-to-Right
> qform_yorient  Posterior-to-Anterior
> qform_zorient  Inferior-to-Superior
> sform_name     Aligned Anat
> sform_code     2
> sto_xyz:1      -1.796652  0.000000  0.000000  112.420448
> sto_xyz:2      0.000000  1.795833  -0.054337  -90.885376
> sto_xyz:3      0.000000  0.054236  1.799180  54.572971
> sto_xyz:4      0.000000  0.000000  0.000000  1.000000
> sform_xorient  Right-to-Left
> sform_yorient  Posterior-to-Anterior
> sform_zorient  Inferior-to-Superior

> Notice how the qform has shearing in the first case (and original also),
> while it has flipped orientation and no shearing in the recreated header
> case.

I don't think the qform does have shearing, it has a small rotation -
see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations .

Playing with nibabel and fsl, I think what is happening is the FSL is
doing something weird to the qform, when the qform code is 0 -
'unknown'.  The qform is correctly stored in the header, according to
nibabel.  This script:

```
import numpy as np
np.set_printoptions(suppress=True, precision=4)

import nibabel as nib

aff = np.array([[-1.796652, 0.000000, 0.000000,  112.420448],
                [ 0.000000, 1.795833, -0.054337,  -90.885376 ],
                [ 0.000000, 0.054236, 1.799180,  54.572971],
                [ 0.000000, 0.000000, 0.000000, 1.000000]])
data = np.zeros((2, 3, 4))

img = nib.Nifti1Image(data, aff)
nib.save(img, 'out.nii')

img2 = nib.load('out.nii')
print(img2.get_qform())
```

gives:

[[  -1.7967    0.        0.0008  112.4204]
 [  -0.        1.7958   -0.0543  -90.8854]
 [   0.0008    0.0542    1.7992   54.573 ]
 [   0.        0.        0.        1.    ]]

whereas `fslhd` on the same file gives the same output as you got:

qto_xyz:1      1.796652  0.000000  0.000000  0.000000
qto_xyz:2      0.000000  1.796652  0.000000  0.000000
qto_xyz:3      0.000000  0.000000  1.800000  0.000000
qto_xyz:4      0.000000  0.000000  0.000000  1.000000

> So, two questions :
> - Is there any adverse effect to using original headers if I know for
> certain nothing changed except dtype?

Nothing that I can think of.

> - Well, I still can't figure out the flipping (is it the same when
> transformation would be applied in world space? So it still is flipped for
> all software in image space in this case then)
> or is that some nibabel RAS convention? I am still confused as to what
> happened to the orientation for the recreated header.

I think the difference must be in the interpretation that [whatever
software shows the flip] gives to a header with a qform with code 0
(unknown).   If you want to solve this, you might want to set the
qform explicitly, with something like 'img.set_qform(affine,
'aligned')' before you save the image.

Cheers,

Matthew


More information about the Neuroimaging mailing list