[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