nipy / nibabel

Python package to access a cacophony of neuro-imaging file formats
http://nipy.org/nibabel/
Other
642 stars 257 forks source link

Nifti1Image should ensure pixdim matches affine in update_header #668

Open effigies opened 5 years ago

effigies commented 5 years ago

Nifti1Header.set_zooms() updates the pixdim field, but this doesn't translate to an updated affine. get_qform() will reflect the change, but get_sform() will not, and hence in almost all (perhaps all?) cases we'll get out of sync.

As img.affine is considered correct, update_header() should override pixdim settings with zooms derived from the affine. This may be best dealt with in some superclass of Nifti1Image, but we should not be creating invalid files.

>>> import numpy as np
>>> import nibabel as nib

>>> img = nib.Nifti1Image(np.random.random((2,3,4)), np.eye(4))
>>> img.header.set_zooms((2,2,2))
>>> img.to_filename('test.nii')
>>> reload = nib.load('test.nii')
>>> reload.affine
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> reload.header.get_zooms()
(2.0, 2.0, 2.0)

Sub-issue identified in #619.

effigies commented 5 years ago

So this may be a feature, not a bug...

https://github.com/nipy/nibabel/blob/7877add916fb1aa9732d347c21b2c2f1b1d4be45/nibabel/spatialimages.py#L475-L512

Note particularly

        # If the affine is not None, and it is different from the main affine
        # in the header, update the header
        if self._affine is None:
            return
        if np.allclose(self._affine, hdr.get_best_affine()):
            return
        self._affine2header()

This reads as if images with multiple affines may have different zooms. Presumably this derives from the qform/sform distinction, where the qform is supposed to indicate scanner coordinates, and the sform can be set to indicate locations in another space (e.g. some template) without necessarily resampling.

So perhaps it's an ambiguity of what set_zooms() means. Perhaps, in a NIfTI with a valid sform, set_zooms should actually modify the sform and not pixdim.