Skip to content

Clarification to docs regarding initial sform/qform code values #576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 11, 2017
Merged
57 changes: 57 additions & 0 deletions doc/source/nifti_images.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,63 @@ The algorithm is defined in the ``get_best_affine()`` method. It is:
#. If ``qform_code`` != 0 ('unknown') use the qform affine; else
#. Use the fall-back affine.

.. _default-sform-qform-codes:

Default sform and qform codes
=============================

If you create a new image, e.g.:

>>> data = np.random.random((20, 20, 20))
>>> xform = np.eye(4) * 2
>>> img = nib.nifti1.Nifti1Image(data, xform)

The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown)
respectively:

>>> img.get_sform(coded=True) # doctest: +NORMALIZE_WHITESPACE
(array([[ 2., 0., 0., 0.],
[ 0., 2., 0., 0.],
[ 0., 0., 2., 0.],
[ 0., 0., 0., 1.]]), array(2, dtype=int16))
>>> img.get_qform(coded=True)
(None, 0)

This is based on the assumption that the affine you specify for a newly
created image will align the image to some known coordinate system. According
to the `NIfTI specification <nifti1>`_, the qform is intended to encode a
transformation into scanner coordinates - for a programmatically created
image, we have no way of knowing what the scanner coordinate system is;
furthermore, the qform cannot be used to store an arbitrary affine transform,
as it is unable to encode shears. So the provided affine will be stored in the
sform, and the qform will be left uninitialised.

If you create a new image and specify an existing header, e.g.:

>>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz')
>>> n1_img = nib.load(example_ni1)
>>> new_header = header=n1_img.header.copy()
>>> new_data = np.random.random(n1_img.shape[:3])
>>> new_img = nib.nifti1.Nifti1Image(data, None, header=new_header)

then the newly created image will inherit the same sform and qform codes that
are in the provided header. However, if you create a new image with both an
affine and a header specified, e.g.:

>>> xform = np.eye(4)
>>> new_img = nib.nifti1.Nifti1Image(data, xform, header=new_header)

then the sform and qform codes will *only* be preserved if the provided affine
is the same as the affine in the provided header. If the affines do not match,
the sform and qform codes will be set to their default values of 2 and 0
respectively. This is done on the basis that, if you are changing the affine,
you are likely to be changing the space to which the affine is pointing. So
the original sform and qform codes can no longer be assumed to be valid.

If you wish to set the sform and qform affines and/or codes to some other
value, you can always set them after creation using the ``set_sform`` and
``set_qform`` methods, as described above.

************
Data scaling
************
Expand Down
15 changes: 14 additions & 1 deletion nibabel/nifti1.py
Original file line number Diff line number Diff line change
Expand Up @@ -1764,7 +1764,20 @@ def __init__(self, dataobj, affine, header=None,
if header is None and affine is not None:
self._affine2header()
# Copy docstring
__init__.doc = analyze.AnalyzeImage.__init__.__doc__
__init__.__doc__ = analyze.AnalyzeImage.__init__.__doc__ + '''
Notes
-----

If both a `header` and an `affine` are specified, and the `affine` does
not match the affine that is in the `header`, the `affine` will be used,
but the ``sform_code`` and ``qform_code`` fields in the header will be
re-initialised to their default values. This is performed on the basis
that, if you are changing the affine, you are likely to be changing the
space to which the affine is pointing. The :meth:`set_sform` and
:meth:`set_qform` methods can be used to update the codes after an image
has been created - see those methods, and the :ref:`manual
<default-sform-qform-codes>` for more details. '''


def update_header(self):
''' Harmonize header with image data and affine
Expand Down