From 4e128b8c0f3a8283c080ca2f6706537eace83ec1 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:05:41 +0000 Subject: [PATCH 01/10] DOC: Explicitly describe behaviour when an affine and a header are specified during Nifti1Image creation. --- nibabel/nifti1.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 4d766a76f5..c00d200483 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1764,7 +1764,19 @@ 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__ + ''' + .. note:: 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 for more + details. . + ''' + def update_header(self): ''' Harmonize header with image data and affine From 581a143838016ce84e930301d8db768583f2cc68 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:32:16 +0000 Subject: [PATCH 02/10] DOC: Add a new section in the Nifti images user doc page specifying how the sform/qform codes are initialised. --- doc/source/nifti_images.rst | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 257465f3a9..4ced8dde6f 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -281,6 +281,61 @@ the sform: ``get_qform()``, ``set_qform()``. The qform also has a corresponding ``qform_code`` with the same interpretation as the `sform_code`. +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) +(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 `_, 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. + The fall-back header affine =========================== From 2de656be37f6fa03d804be1a331a6ffb08353be4 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:40:49 +0000 Subject: [PATCH 03/10] DOC: Move that new nifti images section to the bottom --- doc/source/nifti_images.rst | 68 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 4ced8dde6f..00302a5fee 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -281,6 +281,41 @@ the sform: ``get_qform()``, ``set_qform()``. The qform also has a corresponding ``qform_code`` with the same interpretation as the `sform_code`. +The fall-back header affine +=========================== + +This is the affine of last resort, constructed only from the ``pixdim`` voxel +sizes. The `NIfTI specification `_ says that this should set the +first voxel in the image as [0, 0, 0] in world coordinates, but we nibabblers +follow SPM_ in preferring to set the central voxel to have [0, 0, 0] world +coordinate. The NIfTI spec also implies that the image should be assumed to be +in RAS+ *voxel* orientation for this affine (see :doc:`coordinate_systems`). +Again like SPM, we prefer to assume LAS+ voxel orientation by default. + +You can always get the fall-back affine with ``get_base_affine()``: + +>>> n1_header.get_base_affine() +array([[ -2. , 0. , 0. , 127. ], + [ 0. , 2. , 0. , -95. ], + [ 0. , 0. , 2.2, -25.3], + [ 0. , 0. , 0. , 1. ]]) + +.. _choosing-image-affine: + +Choosing the image affine +========================= + +Given there are three possible affines defined in the NIfTI header, nibabel +has to chose which of these to use for the image ``affine``. + +The algorithm is defined in the ``get_best_affine()`` method. It is: + +#. If ``sform_code`` != 0 ('unknown') use the sform affine; else +#. If ``qform_code`` != 0 ('unknown') use the qform affine; else +#. Use the fall-back affine. + +.. _default-sform-qform-codes: + Default sform and qform codes ============================= @@ -336,39 +371,6 @@ 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. -The fall-back header affine -=========================== - -This is the affine of last resort, constructed only from the ``pixdim`` voxel -sizes. The `NIfTI specification `_ says that this should set the -first voxel in the image as [0, 0, 0] in world coordinates, but we nibabblers -follow SPM_ in preferring to set the central voxel to have [0, 0, 0] world -coordinate. The NIfTI spec also implies that the image should be assumed to be -in RAS+ *voxel* orientation for this affine (see :doc:`coordinate_systems`). -Again like SPM, we prefer to assume LAS+ voxel orientation by default. - -You can always get the fall-back affine with ``get_base_affine()``: - ->>> n1_header.get_base_affine() -array([[ -2. , 0. , 0. , 127. ], - [ 0. , 2. , 0. , -95. ], - [ 0. , 0. , 2.2, -25.3], - [ 0. , 0. , 0. , 1. ]]) - -.. _choosing-image-affine: - -Choosing the image affine -========================= - -Given there are three possible affines defined in the NIfTI header, nibabel -has to chose which of these to use for the image ``affine``. - -The algorithm is defined in the ``get_best_affine()`` method. It is: - -#. If ``sform_code`` != 0 ('unknown') use the sform affine; else -#. If ``qform_code`` != 0 ('unknown') use the qform affine; else -#. Use the fall-back affine. - ************ Data scaling ************ From 041be2f0681bd23a89d83573c2853d5423ff4c8e Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:41:01 +0000 Subject: [PATCH 04/10] DOC: Link to relevant section of user docs from Nifti1Pair docs --- nibabel/nifti1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index c00d200483..407a0b88d9 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1773,8 +1773,8 @@ def __init__(self, dataobj, affine, header=None, 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 for more - details. . + after an image has been created - see those methods, and + the :ref:`manual ` for more details. ''' From d158f50124267a88389a343a7bc04b4224706fb6 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 17:35:08 +0000 Subject: [PATCH 05/10] DOC: typo --- nibabel/nifti1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 407a0b88d9..3a80964de8 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1769,7 +1769,7 @@ def __init__(self, dataobj, affine, header=None, ``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 + 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 From 1b8190854787cf84e6a397ab213d34e65cb3a03e Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:19:10 +0000 Subject: [PATCH 06/10] DOC: Use numpydoc convention, not .rst --- nibabel/nifti1.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 3a80964de8..29269f3455 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1765,17 +1765,18 @@ def __init__(self, dataobj, affine, header=None, self._affine2header() # Copy docstring __init__.__doc__ = analyze.AnalyzeImage.__init__.__doc__ + ''' - .. note:: 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 ` for more details. - ''' + Notes + ----- + + If both a ``header`` and an ``affine`` are specified, andq 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 + ` for more details. ''' def update_header(self): From d42d7ee45a52ef653e3a496902457265e3eb1b6f Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:22:34 +0000 Subject: [PATCH 07/10] DOC: doctest clause --- doc/source/nifti_images.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 00302a5fee..f6fc803d09 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -328,7 +328,7 @@ If you create a new image, e.g.: The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown) respectively: ->>> img.get_sform(coded=True) +>>> img.get_sform(coded=True) # doctest: +NORMALIZE_WHITESPACE (array([[ 2., 0., 0., 0.], [ 0., 2., 0., 0.], [ 0., 0., 2., 0.], From 92a8acb23b9636f15b6f5505e5bbd60a768a1e2c Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:24:55 +0000 Subject: [PATCH 08/10] DOC: Typo --- nibabel/nifti1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 29269f3455..b4f31da843 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1768,7 +1768,7 @@ def __init__(self, dataobj, affine, header=None, Notes ----- - If both a ``header`` and an ``affine`` are specified, andq the ``affine`` + 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 From a6b4d83044f07ec31787727757dad6f3b5bbbd9f Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Sat, 11 Nov 2017 12:17:20 +0000 Subject: [PATCH 09/10] DOC: Small adjustments to grammar/flow --- doc/source/nifti_images.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index f6fc803d09..2f8a7cd0db 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -353,14 +353,14 @@ If you create a new image and specify an existing header, e.g.: >>> 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 +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 +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, From 188b1940793f9aeb4fb84b002598292bc95fefb9 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Sat, 11 Nov 2017 12:26:28 +0000 Subject: [PATCH 10/10] DOC: Single backticks when referring to parameters in documentation --- nibabel/nifti1.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index b4f31da843..ff3ee32118 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1768,12 +1768,12 @@ def __init__(self, dataobj, affine, header=None, 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 + 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 ` for more details. '''