Skip to content

Commit ca68582

Browse files
committed
Merge pull request #388 from effigies/sphinx3
MRG: Support calling Sphinx with Python 3 As of this commit, Sphinx can compile documentation when called with Python 3. The doctests in the documentation also pass in Python 2 and 3. Take the opportunity to drop some sphinx extension copies that have since struck out on their own as pip packages.
2 parents fde94f1 + 29e4c7d commit ca68582

File tree

10 files changed

+31
-841
lines changed

10 files changed

+31
-841
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ script:
107107
COVER_ARGS="--with-coverage --cover-package nibabel";
108108
fi
109109
- if [ "$DOC_DOC_TEST" == "1" ]; then
110-
pip install sphinx;
110+
pip install sphinx numpydoc texext;
111111
cd ../doc;
112112
make html;
113113
make doctest;

doc/source/conf.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,55 @@
1111
# nipype documentation build configuration file, created by
1212
# sphinx-quickstart on Mon Jul 20 12:30:18 2009.
1313
#
14-
# This file is execfile()d with the current directory set to its containing dir.
14+
# This file is exec()d with the current directory set to its containing dir.
1515
#
1616
# Note that not all possible configuration values are present in this
1717
# autogenerated file.
1818
#
1919
# All configuration values have a default; values that are commented out
2020
# serve to show the default.
2121

22-
import sys, os
22+
import sys
23+
import os
2324

2425
import nibabel
2526

27+
# Check for external Sphinx extensions we depend on
28+
try:
29+
import numpydoc
30+
except ImportError:
31+
raise RuntimeError('Need to install "numpydoc" package for doc build')
32+
try:
33+
import texext
34+
except ImportError:
35+
raise RuntimeError('Need to install "texext" package for doc build')
36+
2637
# If extensions (or modules to document with autodoc) are in another directory,
2738
# add these directories to sys.path here. If the directory is relative to the
2839
# documentation root, use os.path.abspath to make it absolute, like shown here.
2940
sys.path.append(os.path.abspath('../sphinxext'))
3041

31-
# -- General configuration -----------------------------------------------------
42+
# -- General configuration ----------------------------------------------------
3243

3344
# We load the nibabel release info into a dict by explicit execution
3445
rel = {}
35-
execfile('../../nibabel/info.py', rel)
46+
with open(os.path.join('..', '..', 'nibabel', 'info.py'), 'r') as fobj:
47+
exec(fobj.read(), rel)
3648

3749
# Write long description from info
3850
with open('_long_description.inc', 'wt') as fobj:
3951
fobj.write(rel['LONG_DESCRIPTION'])
4052

41-
# Add any Sphinx extension module names here, as strings. They can be extensions
42-
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
53+
# Add any Sphinx extension module names here, as strings. They can be
54+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
4355
extensions = ['sphinx.ext.autodoc',
4456
'sphinx.ext.doctest',
4557
#'sphinx.ext.intersphinx',
4658
'sphinx.ext.todo',
4759
'sphinx.ext.mathjax',
4860
'sphinx.ext.inheritance_diagram',
4961
'sphinx.ext.autosummary',
50-
'math_dollar', # has to go before numpydoc
51-
# we have a local copy of the extension, imported from NumPy 1.3
52-
# this also includes the docscrape* extensions
62+
'texext.math_dollar', # has to go before numpydoc
5363
'numpydoc',
5464
'only_directives',
5565
'plot_directive',
@@ -104,7 +114,7 @@
104114
# for source files.
105115
exclude_trees = ['_build']
106116

107-
# The reST default role (used for this markup: `text`) to use for all documents.
117+
# The reST default role (used for this markup: `text`) to use for all documents
108118
#default_role = None
109119

110120
# If true, '()' will be appended to :func: etc. cross-reference text.
@@ -124,7 +134,7 @@
124134
# A list of ignored prefixes for module index sorting.
125135
#modindex_common_prefix = []
126136

127-
# -- Sphinxext configuration ---------------------------------------------------
137+
# -- Sphinxext configuration --------------------------------------------------
128138

129139
# Set attributes for layout of inheritance diagrams
130140
inheritance_graph_attrs = dict(rankdir="LR", size='"6.0, 8.0"', fontsize=14,
@@ -135,7 +145,7 @@
135145
# Flag to show todo items in rendered output
136146
todo_include_todos = True
137147

138-
# -- Options for HTML output ---------------------------------------------------
148+
# -- Options for HTML output --------------------------------------------------
139149

140150
# The theme to use for HTML and HTML Help pages. Major themes that come with
141151
# Sphinx are currently 'default' and 'sphinxdoc'.
@@ -218,7 +228,7 @@
218228

219229
mathjax_path = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
220230

221-
# -- Options for LaTeX output --------------------------------------------------
231+
# -- Options for LaTeX output -------------------------------------------------
222232

223233
# The paper size ('letter' or 'a4').
224234
#latex_paper_size = 'letter'
@@ -227,11 +237,11 @@
227237
#latex_font_size = '10pt'
228238

229239
# Grouping the document tree into LaTeX files. List of tuples
230-
# (source start file, target name, title, author, documentclass [howto/manual]).
240+
# (source start file, target name, title, author,
241+
# documentclass [howto/manual]).
231242
latex_documents = [
232-
('index', 'nibabel.tex', u'NiBabel Documentation',
233-
u'NiBabel Authors', 'manual'),
234-
]
243+
('index', 'nibabel.tex', u'NiBabel Documentation', u'NiBabel Authors',
244+
'manual')]
235245

236246
# The name of an image file (relative to this directory) to place at the top of
237247
# the title page.

doc/source/gettingstarted.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ a NumPy_ array
7070
>>> data.shape
7171
(128, 96, 24, 2)
7272
>>> type(data)
73-
<type 'numpy.ndarray'>
73+
<... 'numpy.ndarray'>
7474

7575
The complete information embedded in an image header is available via a
7676
format-specific header object.

doc/source/images_and_memory.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,14 @@ proxy to return the array directly by passing ``dataobj`` to the numpy
136136
>>> proxy_img = nib.load(example_file)
137137
>>> data_array = np.asarray(proxy_img.dataobj)
138138
>>> type(data_array)
139-
<type 'numpy.ndarray'>
139+
<... 'numpy.ndarray'>
140140

141141
This also works for array images, because ``np.asarray`` returns the array:
142142

143143
>>> array_img = nib.Nifti1Image(array_data, affine)
144144
>>> data_array = np.asarray(array_img.dataobj)
145145
>>> type(data_array)
146-
<type 'numpy.ndarray'>
146+
<... 'numpy.ndarray'>
147147

148148
If you want to avoid caching you can avoid ``get_data()`` and always use
149149
``np.asarray(img.dataobj)``.

doc/source/nibabel_images.rst

12 Bytes

############## Nibabel images ############## A nibabel image object is the association of three things: * an N-D array containing the image *data*; * a (4, 4) *affine* matrix mapping array coordinates to coordinates in some RAS+ world coordinate space (:doc:`coordinate_systems`); * image metadata in the form of a *header*. **************** The image object **************** First we load some libraries we are going to need for the examples: .. testsetup:: # Work in a temporary directory import os import tempfile pwd = os.getcwd() tmp_dir = tempfile.mkdtemp() os.chdir(tmp_dir) >>> import os >>> import numpy as np There is an example image in the nibabel distribution. >>> from nibabel.testing import data_path >>> example_file = os.path.join(data_path, 'example4d.nii.gz') We load the file to create a nibabel *image object*: >>> import nibabel as nib >>> img = nib.load(example_file) The object ``img`` is an instance of a nibabel image. In fact it is an instance of a nibabel :class:`nibabel.nifti1.Nifti1Image`: >>> img As with any Python object, you can inspect ``img`` to see what attributes it has. We recommend using IPython tab completion for this, but here are some examples of interesting attributes: ``dataobj`` is the object pointing to the image array data: >>> img.dataobj See :ref:`array-proxies` for more on why this is an array *proxy*. ``affine`` is the affine array relating array coordinates from the image data array to coordinates in some RAS+ world coordinate system (:doc:`coordinate_systems`): >>> # Set numpy to print only 2 decimal digits for neatness >>> np.set_printoptions(precision=2, suppress=True) >>> img.affine array([[ -2. , 0. , 0. , 117.86], [ -0. , 1.97, -0.36, -35.72], [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]) ``header`` contains the metadata for this inage. In this case it is specifically NIfTI metadata: >>> img.header **************** The image header **************** The header of an image contains the image metadata. The information in the header will differ between different image formats. For example, the header information for a NIfTI1 format file differs from the header information for a MINC format file. Our image is a NIfTI1 format image, and it therefore has a NIfTI1 format header: >>> header = img.header >>> print(header) # doctest: +NORMALIZE_WHITESPACE object, endian='<' sizeof_hdr : 348 data_type : db_name : extents : 0 session_error : 0 regular : r dim_info : 57 dim : [ 4 128 96 24 2 1 1 1] intent_p1 : 0.0 intent_p2 : 0.0 intent_p3 : 0.0 intent_code : none datatype : int16 bitpix : 16 slice_start : 0 pixdim : [ -1. 2. 2. 2.2 2000. 1. 1. 1. ] vox_offset : 0.0 scl_slope : nan scl_inter : nan slice_end : 23 slice_code : unknown xyzt_units : 10 cal_max : 1162.0 cal_min : 0.0 slice_duration : 0.0 toffset : 0.0 glmax : 0 glmin : 0 descrip : FSL3.3 v2.25 NIfTI-1 Single file format aux_file : qform_code : scanner sform_code : scanner quatern_b : -1.94510681403e-26 quatern_c : -0.996708512306 quatern_d : -0.081068739295 qoffset_x : 117.855102539 qoffset_y : -35.7229423523 qoffset_z : -7.24879837036 srow_x : [ -2. 0. 0. 117.86] srow_y : [ -0. 1.97 -0.36 -35.72] srow_z : [ 0. 0.32 2.17 -7.25] intent_name : magic : n+1 The header of any image will normally have the following methods: * ``get_data_shape()`` to get the output shape of the image data array: >>> print(header.get_data_shape()) (128, 96, 24, 2) * ``get_data_dtype()`` to get the numpy data type in which the image data is stored (or will be stored if you save the image): >>> print(header.get_data_dtype()) int16 * ``get_zooms()`` to get the voxel sizes in millimeters: >>> print(header.get_zooms()) (2.0, 2.0, 2.1999991, 2000.0) The last value of ``header.get_zooms()`` is the time between scans in milliseconds; this is the equivalent of voxel size on the time axis. ******************** The image data array ******************** The image data array is a little more complicated, because the image array can be stored in the image object as a numpy array or stored on disk for you to access later via an *array proxy*. .. _array-proxies: Array proxies and proxy images ============================== When you load an image from disk, as we did here, the data is likely to be accessible via an array proxy. An array proxy_ is not the array itself but something that represents the array, and can provide the array when we ask for it. Our image does have an array proxy, as we have already seen: >>> img.dataobj The array proxy allows us to create the image object without immediately loading all the array data from disk. Images with an array proxy object like this one are called *proxy images* because the image data is not yet an array, but the array proxy points to (proxies) the array data on disk. You can test if the image has a array proxy like this: >>> nib.is_proxy(img.dataobj) True Array images ============ We can also create images from numpy arrays. For example: >>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) >>> affine = np.diag([1, 2, 3, 1]) >>> array_img = nib.Nifti1Image(array_data, affine) In this case the image array data is already a numpy array, and there is no version of the array on disk. The ``dataobj`` property of the image is the array itself rather than a proxy for the array: >>> array_img.dataobj array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]], dtype=int16) >>> array_img.dataobj is array_data True ``dataobj`` is an array, not an array proxy, so: >>> nib.is_proxy(array_img.dataobj) False Getting the image data the easy way =================================== For either type of image (array or proxy) you can always get the data with the :meth:`get_data() ` method. For the array image, ``get_data()`` just returns the data array: >>> image_data = array_img.get_data() >>> image_data.shape (2, 3, 4) >>> image_data is array_data True For the proxy image, the ``get_data()`` method fetches the array data from disk using the proxy, and returns the array. >>> image_data = img.get_data() >>> image_data.shape (128, 96, 24, 2) The image ``dataobj`` property is still a proxy object: >>> img.dataobj .. _proxies-caching: Proxies and caching =================== You may not want to keep loading the image data off disk every time you call ``get_data()`` on a proxy image. By default, when you call ``get_data()`` the first time on a proxy image, the image object keeps a cached copy of the loaded array. The next time you call ``img.get_data()``, the image returns the array from cache rather than loading it from disk again. >>> data_again = img.get_data() The returned data is the same (cached) copy we returned before: >>> data_again is image_data True See :doc:`images_and_memory` for more details on managing image memory and controlling the image cache. ****************** Loading and saving ****************** The ``save`` and ``load`` functions in nibabel should do all the work for you: >>> nib.save(array_img, 'my_image.nii') >>> img_again = nib.load('my_image.nii') >>> img_again.shape (2, 3, 4) You can also use the ``to_filename`` method: >>> array_img.to_filename('my_image_again.nii') >>> img_again = nib.load('my_image_again.nii') >>> img_again.shape (2, 3, 4) You can get and set the filename with ``get_filename()`` and ``set_filename()``: >>> img_again.set_filename('another_image.nii') >>> img_again.get_filename() 'another_image.nii' *************************** Details of files and images *************************** If an image can be loaded or saved on disk, the image will have an attribute called ``file_map``. ``img.file_map`` is a dictionary where the keys are the names of the files that the image uses to load / save on disk, and the values are ``FileHolder`` objects, that usually contain the filenames that the image has been loaded from or saved to. In the case of a NiFTI1 single file, this is just a single image file with a ``.nii`` or ``.nii.gz`` extension: >>> list(img_again.file_map) ['image'] >>> img_again.file_map['image'].filename 'another_image.nii' Other file types need more than one file to make up the image. The NiFTI1 pair type is one example. NIfTI pair images have one file containing the header information and another containing the image array data: >>> pair_img = nib.Nifti1Pair(array_data, np.eye(4)) >>> nib.save(pair_img, 'my_pair_image.img') >>> sorted(pair_img.file_map) ['header', 'image'] >>> pair_img.file_map['header'].filename 'my_pair_image.hdr' >>> pair_img.file_map['image'].filename 'my_pair_image.img' The older Analyze format also has a separate header and image file: >>> ana_img = nib.AnalyzeImage(array_data, np.eye(4)) >>> sorted(ana_img.file_map) ['header', 'image'] It is the contents of the ``file_map`` that gets changed when you use ``set_filename`` or ``to_filename``: >>> ana_img.set_filename('analyze_image.img') >>> ana_img.file_map['image'].filename 'analyze_image.img' >>> ana_img.file_map['header'].filename 'analyze_image.hdr' .. testcleanup:: os.chdir(pwd) import shutil shutil.rmtree(tmp_dir) .. include:: links_names.txt

Nibabel images

A nibabel image object is the association of three things:

  • an N-D array containing the image data;
  • a (4, 4) affine matrix mapping array coordinates to coordinates in some RAS+ world coordinate space (:doc:`coordinate_systems`);
  • image metadata in the form of a header.

The image object

First we load some libraries we are going to need for the examples:

.. testsetup::

    # Work in a temporary directory
    import os
    import tempfile
    pwd = os.getcwd()
    tmp_dir = tempfile.mkdtemp()
    os.chdir(tmp_dir)

>>> import os
>>> import numpy as np

There is an example image in the nibabel distribution.

>>> from nibabel.testing import data_path
>>> example_file = os.path.join(data_path, 'example4d.nii.gz')

We load the file to create a nibabel image object:

>>> import nibabel as nib
>>> img = nib.load(example_file)

The object img is an instance of a nibabel image. In fact it is an instance of a nibabel :class:`nibabel.nifti1.Nifti1Image`:

>>> img
<nibabel.nifti1.Nifti1Image object at ...>

As with any Python object, you can inspect img to see what attributes it has. We recommend using IPython tab completion for this, but here are some examples of interesting attributes:

dataobj is the object pointing to the image array data:

>>> img.dataobj
<nibabel.arrayproxy.ArrayProxy object at ...>

See :ref:`array-proxies` for more on why this is an array proxy.

affine is the affine array relating array coordinates from the image data array to coordinates in some RAS+ world coordinate system (:doc:`coordinate_systems`):

>>> # Set numpy to print only 2 decimal digits for neatness
>>> np.set_printoptions(precision=2, suppress=True)
>>> img.affine
array([[  -2.  ,    0.  ,    0.  ,  117.86],
       [  -0.  ,    1.97,   -0.36,  -35.72],
       [   0.  ,    0.32,    2.17,   -7.25],
       [   0.  ,    0.  ,    0.  ,    1.  ]])

header contains the metadata for this inage. In this case it is specifically NIfTI metadata:

>>> img.header
<nibabel.nifti1.Nifti1Header object at ...>

The image header

The header of an image contains the image metadata. The information in the header will differ between different image formats. For example, the header information for a NIfTI1 format file differs from the header information for a MINC format file.

Our image is a NIfTI1 format image, and it therefore has a NIfTI1 format header:

>>> header = img.header
>>> print(header)                           # doctest: +SKIP
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 57
dim             : [  4 128  96  24   2   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : int16
bitpix          : 16
slice_start     : 0
pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 23
slice_code      : unknown
xyzt_units      : 10
cal_max         : 1162.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
aux_file        : b''
qform_code      : scanner
sform_code      : scanner
quatern_b       : -1.94510681403e-26
quatern_c       : -0.996708512306
quatern_d       : -0.081068739295
qoffset_x       : 117.855102539
qoffset_y       : -35.7229423523
qoffset_z       : -7.24879837036
srow_x          : [  -2.      0.      0.    117.86]
srow_y          : [ -0.     1.97  -0.36 -35.72]
srow_z          : [ 0.    0.32  2.17 -7.25]
intent_name     : b''
magic           : b'n+1'

The header of any image will normally have the following methods:

  • get_data_shape() to get the output shape of the image data array:

    >>> print(header.get_data_shape())
    (128, 96, 24, 2)
  • get_data_dtype() to get the numpy data type in which the image data is stored (or will be stored if you save the image):

    >>> print(header.get_data_dtype())
    int16
  • get_zooms() to get the voxel sizes in millimeters:

    >>> print(header.get_zooms())
    (2.0, 2.0, 2.1999991, 2000.0)

    The last value of header.get_zooms() is the time between scans in milliseconds; this is the equivalent of voxel size on the time axis.

The image data array

The image data array is a little more complicated, because the image array can be stored in the image object as a numpy array or stored on disk for you to access later via an array proxy.

Array proxies and proxy images

When you load an image from disk, as we did here, the data is likely to be accessible via an array proxy. An array proxy_ is not the array itself but something that represents the array, and can provide the array when we ask for it.

Our image does have an array proxy, as we have already seen:

>>> img.dataobj
<nibabel.arrayproxy.ArrayProxy object at ...>

The array proxy allows us to create the image object without immediately loading all the array data from disk.

Images with an array proxy object like this one are called proxy images because the image data is not yet an array, but the array proxy points to (proxies) the array data on disk.

You can test if the image has a array proxy like this:

>>> nib.is_proxy(img.dataobj)
True

Array images

We can also create images from numpy arrays. For example:

>>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4))
>>> affine = np.diag([1, 2, 3, 1])
>>> array_img = nib.Nifti1Image(array_data, affine)

In this case the image array data is already a numpy array, and there is no version of the array on disk. The dataobj property of the image is the array itself rather than a proxy for the array:

>>> array_img.dataobj
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],
<BLANKLINE>
       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]], dtype=int16)
>>> array_img.dataobj is array_data
True

dataobj is an array, not an array proxy, so:

>>> nib.is_proxy(array_img.dataobj)
False

Getting the image data the easy way

For either type of image (array or proxy) you can always get the data with the :meth:`get_data() <nibabel.spatialimages.SpatialImage.get_data>` method.

For the array image, get_data() just returns the data array:

>>> image_data = array_img.get_data()
>>> image_data.shape
(2, 3, 4)
>>> image_data is array_data
True

For the proxy image, the get_data() method fetches the array data from disk using the proxy, and returns the array.

>>> image_data = img.get_data()
>>> image_data.shape
(128, 96, 24, 2)

The image dataobj property is still a proxy object:

>>> img.dataobj
<nibabel.arrayproxy.ArrayProxy object at ...>

Proxies and caching

You may not want to keep loading the image data off disk every time you call get_data() on a proxy image. By default, when you call get_data() the first time on a proxy image, the image object keeps a cached copy of the loaded array. The next time you call img.get_data(), the image returns the array from cache rather than loading it from disk again.

>>> data_again = img.get_data()

The returned data is the same (cached) copy we returned before:

>>> data_again is image_data
True

See :doc:`images_and_memory` for more details on managing image memory and controlling the image cache.

Loading and saving

The save and load functions in nibabel should do all the work for you:

>>> nib.save(array_img, 'my_image.nii')
>>> img_again = nib.load('my_image.nii')
>>> img_again.shape
(2, 3, 4)

You can also use the to_filename method:

>>> array_img.to_filename('my_image_again.nii')
>>> img_again = nib.load('my_image_again.nii')
>>> img_again.shape
(2, 3, 4)

You can get and set the filename with get_filename() and set_filename():

>>> img_again.set_filename('another_image.nii')
>>> img_again.get_filename()
'another_image.nii'

Details of files and images

If an image can be loaded or saved on disk, the image will have an attribute called file_map. img.file_map is a dictionary where the keys are the names of the files that the image uses to load / save on disk, and the values are FileHolder objects, that usually contain the filenames that the image has been loaded from or saved to. In the case of a NiFTI1 single file, this is just a single image file with a .nii or .nii.gz extension:

>>> list(img_again.file_map)
['image']
>>> img_again.file_map['image'].filename
'another_image.nii'

Other file types need more than one file to make up the image. The NiFTI1 pair type is one example. NIfTI pair images have one file containing the header information and another containing the image array data:

>>> pair_img = nib.Nifti1Pair(array_data, np.eye(4))
>>> nib.save(pair_img, 'my_pair_image.img')
>>> sorted(pair_img.file_map)
['header', 'image']
>>> pair_img.file_map['header'].filename
'my_pair_image.hdr'
>>> pair_img.file_map['image'].filename
'my_pair_image.img'

The older Analyze format also has a separate header and image file:

>>> ana_img = nib.AnalyzeImage(array_data, np.eye(4))
>>> sorted(ana_img.file_map)
['header', 'image']

It is the contents of the file_map that gets changed when you use set_filename or to_filename:

>>> ana_img.set_filename('analyze_image.img')
>>> ana_img.file_map['image'].filename
'analyze_image.img'
>>> ana_img.file_map['header'].filename
'analyze_image.hdr'
.. testcleanup::

    os.chdir(pwd)
    import shutil
    shutil.rmtree(tmp_dir)

doc/source/nifti_images.rst

17 Bytes

######################### Working with NIfTI images ######################### This page describes some features of the nibabel implementation of the NIfTI format. Generally all these features apply equally to the NIfTI 1 and the NIfTI 2 format, but we will note the differences when they come up. NIfTI 1 is much more common than NIfTI 2. .. testsetup:: # Work in a temporary directory import os import tempfile pwd = os.getcwd() tmp_dir = tempfile.mkdtemp() os.chdir(tmp_dir) ************* Preliminaries ************* We first set some display parameters to print out numpy arrays in a compact form: >>> import numpy as np >>> # Set numpy to print only 2 decimal digits for neatness >>> np.set_printoptions(precision=2, suppress=True) ******************** Example NIfTI images ******************** >>> import os >>> import nibabel as nib >>> from nibabel.testing import data_path This is the example NIfTI 1 image: >>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz') >>> n1_img = nib.load(example_ni1) >>> n1_img Here is the NIfTI 2 example image: >>> example_ni2 = os.path.join(data_path, 'example_nifti2.nii.gz') >>> n2_img = nib.load(example_ni2) >>> n2_img **************** The NIfTI header **************** The NIfTI 1 header is a small C structure of size 352 bytes. It contains the following fields: >>> n1_header = n1_img.header >>> print(n1_header) # doctest: +NORMALIZE_WHITESPACE object, endian='<' sizeof_hdr : 348 data_type : db_name : extents : 0 session_error : 0 regular : r dim_info : 57 dim : [ 4 128 96 24 2 1 1 1] intent_p1 : 0.0 intent_p2 : 0.0 intent_p3 : 0.0 intent_code : none datatype : int16 bitpix : 16 slice_start : 0 pixdim : [ -1. 2. 2. 2.2 2000. 1. 1. 1. ] vox_offset : 0.0 scl_slope : nan scl_inter : nan slice_end : 23 slice_code : unknown xyzt_units : 10 cal_max : 1162.0 cal_min : 0.0 slice_duration : 0.0 toffset : 0.0 glmax : 0 glmin : 0 descrip : FSL3.3 v2.25 NIfTI-1 Single file format aux_file : qform_code : scanner sform_code : scanner quatern_b : -1.94510681403e-26 quatern_c : -0.996708512306 quatern_d : -0.081068739295 qoffset_x : 117.855102539 qoffset_y : -35.7229423523 qoffset_z : -7.24879837036 srow_x : [ -2. 0. 0. 117.86] srow_y : [ -0. 1.97 -0.36 -35.72] srow_z : [ 0. 0.32 2.17 -7.25] intent_name : magic : n+1 The NIfTI 2 header is similar, but of length 540 bytes, with fewer fields: >>> n2_header = n2_img.header >>> print(n2_header) # doctest: +NORMALIZE_WHITESPACE object, endian='<' sizeof_hdr : 540 magic : n+2 eol_check : [13 10 26 10] datatype : int16 bitpix : 16 dim : [ 4 32 20 12 2 1 1 1] intent_p1 : 0.0 intent_p2 : 0.0 intent_p3 : 0.0 pixdim : [ -1. 2. 2. 2.2 2000. 1. 1. 1. ] vox_offset : 0 scl_slope : nan scl_inter : nan cal_max : 1162.0 cal_min : 0.0 slice_duration : 0.0 toffset : 0.0 slice_start : 0 slice_end : 23 descrip : FSL3.3 v2.25 NIfTI-1 Single file format aux_file : qform_code : scanner sform_code : scanner quatern_b : -1.94510681403e-26 quatern_c : -0.996708512306 quatern_d : -0.081068739295 qoffset_x : 117.855102539 qoffset_y : -35.7229423523 qoffset_z : -7.24879837036 srow_x : [ -2. 0. 0. 117.86] srow_y : [ -0. 1.97 -0.36 -35.72] srow_z : [ 0. 0.32 2.17 -7.25] slice_code : unknown xyzt_units : 10 intent_code : none intent_name : dim_info : 57 unused_str : You can get and set individual fields in the header using dict (mapping-type) item access. For example: >>> n1_header['cal_max'] array(1162.0, dtype=float32) >>> n1_header['cal_max'] = 1200 >>> n1_header['cal_max'] array(1200.0, dtype=float32) Check the attributes of the header for ``get_`` / ``set_`` methods to get and set various combinations of NIfTI header fields. The ``get_`` / ``set_`` methods should check and apply valid combinations of values from the header, whereas you can do anything you like with the dict / mapping item access. It is safer to use the ``get_`` / ``set_`` methods and use the mapping item access only if the ``get_`` / ``set_`` methods will not do what you want. ***************** The NIfTI affines ***************** Like other nibabel image types, NIfTI images have an affine relating the voxel coordinates to world coordinates in RAS+ space: >>> n1_img.affine array([[ -2. , 0. , 0. , 117.86], [ -0. , 1.97, -0.36, -35.72], [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]) Unlike other formats, the NIfTI header format can specify this affine in one of three ways |--| the *sform* affine, the *qform* affine and the *fall-back header* affine. Nibabel uses an :ref:`algorithm ` to chose which of these three it will use for the overall image ``affine``. The sform affine ================ The header stores the three first rows of the 4 by 4 affine in the header fields ``srow_x``, ``srow_y``, ``srow_z``. The header does not store the fourth row because it is always ``[0, 0, 0, 1]`` (see :doc:`coordinate_systems`). You can get the sform affine specifically with the ``get_sform()`` method of the image or the header. For example: >>> print(n1_header['srow_x']) [ -2. 0. 0. 117.86] >>> print(n1_header['srow_y']) [ -0. 1.97 -0.36 -35.72] >>> print(n1_header['srow_z']) [ 0. 0.32 2.17 -7.25] >>> print(n1_header.get_sform()) [[ -2. 0. 0. 117.86] [ -0. 1.97 -0.36 -35.72] [ 0. 0.32 2.17 -7.25] [ 0. 0. 0. 1. ]] This affine is valid only if the ``sform_code`` is not zero. >>> print(n1_header['sform_code']) 1 The different sform code values specify which RAS+ space the sform affine refers to, with these interpretations: ==== ========= =========== Code Label Meaning ==== ========= =========== 0 unknown sform not defined 1 scanner RAS+ in scanner coordinates 2 aligned RAS+ aligned to some other scan 3 talairach RAS+ in Talairach atlas space 4 mni RAS+ in MNI atlas space ==== ========= =========== In our case the code is 1, meaning "scanner" alignment. You can get the affine and the code using the ``coded=True`` argument to ``get_sform()``: >>> print(n1_header.get_sform(coded=True)) (array([[ -2. , 0. , 0. , 117.86], [ -0. , 1.97, -0.36, -35.72], [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]), array(1, dtype=int16)) You can set the sform with with the ``get_sform()`` method of the header and the image. >>> n1_header.set_sform(np.diag([2, 3, 4, 1])) >>> n1_header.get_sform() array([[ 2., 0., 0., 0.], [ 0., 3., 0., 0.], [ 0., 0., 4., 0.], [ 0., 0., 0., 1.]]) Set the affine and code using the ``code`` parameter to ``set_sform()``: >>> n1_header.set_sform(np.diag([3, 4, 5, 1]), code='mni') >>> n1_header.get_sform(coded=True) (array([[ 3., 0., 0., 0.], [ 0., 4., 0., 0.], [ 0., 0., 5., 0.], [ 0., 0., 0., 1.]]), array(4, dtype=int16)) The qform affine ================ This affine can be calculated from a combination of the voxel sizes (entries 1 through 4 of the ``pixdim`` field), a sign flip called ``qfac`` stored in entry 0 of ``pixdim``, and a `quaternion `_ that can be reconstructed from fields ``quatern_b``, ``quatern_c``, ``quatern_d``. See the code for the :meth:`get_qform() method ` for details. You can get and set the qform affine using the equivalent methods to those for the sform: ``get_qform()``, ``set_qform()``. >>> n1_header.get_qform(coded=True) (array([[ -2. , 0. , 0. , 117.86], [ -0. , 1.97, -0.36, -35.72], [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]), array(1, dtype=int16)) 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. ************ Data scaling ************ NIfTI uses a simple scheme for data scaling. By default, nibabel will take care of this scaling for you, but there may be times that you want to control the data scaling yourself. If so, the next section describes how the scaling works and the nibabel implementation of same. There are two scaling fields in the header called ``scl_slope`` and ``scl_inter``. The output data from a NIfTI image comes from: #. Loading the binary data from the image file; #. Casting the numbers to the binary format given in the header and returned by ``get_data_dtype()``; #. Reshaping to the output image shape; #. Multiplying the result by the header ``scl_slope`` value, if both of ``scl_slope`` and ``scl_inter`` are defined; #. Adding the value header ``scl_slope`` value to the result, if both of ``scl_slope`` and ``scl_inter`` are defined; 'Defined' means, the value is not NaN (not a number). All this gets built into the array proxy when you load a NIfTI image. When you load an image, the header scaling values automatically get set to NaN (undefined) to mark the fact that the scaling values have been consumed by the read. The scaling values read from the header on load only appear in the array proxy object. To see how this works, let's make a new image with some scaling: >>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4)) >>> affine = np.diag([1, 2, 3, 1]) >>> array_img = nib.Nifti1Image(array_data, affine) >>> array_header = array_img.header The default scaling values are NaN (undefined): >>> array_header['scl_slope'] array(nan, dtype=float32) >>> array_header['scl_inter'] array(nan, dtype=float32) You can get the scaling values with the ``get_slope_inter()`` method: >>> array_header.get_slope_inter() (None, None) None corresponds to the NaN scaling value (undefined). We can set them in the image header, so they get saved to the header when the image is written. We can do this by setting the fields directly, or with ``set_slope_inter()``: >>> array_header.set_slope_inter(2, 10) >>> array_header.get_slope_inter() (2.0, 10.0) >>> array_header['scl_slope'] array(2.0, dtype=float32) >>> array_header['scl_inter'] array(10.0, dtype=float32) Setting the scale factors in the header has no effect on the image data before we save and load again: >>> array_img.get_data() array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]], dtype=int16) Now we save the image and load it again: >>> nib.save(array_img, 'scaled_image.nii') >>> scaled_img = nib.load('scaled_image.nii') The data array has the scaling applied: >>> scaled_img.get_data() memmap([[[ 10., 12., 14., 16.], [ 18., 20., 22., 24.], [ 26., 28., 30., 32.]], [[ 34., 36., 38., 40.], [ 42., 44., 46., 48.], [ 50., 52., 54., 56.]]]) The header for the loaded image has had the scaling reset to undefined, to mark the fact that the scaling has been "consumed" by the load: >>> scaled_img.header.get_slope_inter() (None, None) The original slope and intercept are still accessible in the array proxy object: >>> scaled_img.dataobj.slope 2.0 >>> scaled_img.dataobj.inter 10.0 If the header scaling is undefined when we save the image, nibabel will try to find an optimum slope and intercept to best preserve the precision of the data in the output data type. Because nibabel will set the scaling to undefined when loading the image, or creating a new image, this is the default behavior. .. testcleanup:: os.chdir(pwd) import shutil shutil.rmtree(tmp_dir) .. include:: links_names.txt

Working with NIfTI images

This page describes some features of the nibabel implementation of the NIfTI format. Generally all these features apply equally to the NIfTI 1 and the NIfTI 2 format, but we will note the differences when they come up. NIfTI 1 is much more common than NIfTI 2.

.. testsetup::

    # Work in a temporary directory
    import os
    import tempfile
    pwd = os.getcwd()
    tmp_dir = tempfile.mkdtemp()
    os.chdir(tmp_dir)

Preliminaries

We first set some display parameters to print out numpy arrays in a compact form:

>>> import numpy as np
>>> # Set numpy to print only 2 decimal digits for neatness
>>> np.set_printoptions(precision=2, suppress=True)

Example NIfTI images

>>> import os
>>> import nibabel as nib
>>> from nibabel.testing import data_path

This is the example NIfTI 1 image:

>>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz')
>>> n1_img = nib.load(example_ni1)
>>> n1_img
<nibabel.nifti1.Nifti1Image object at ...>

Here is the NIfTI 2 example image:

>>> example_ni2 = os.path.join(data_path, 'example_nifti2.nii.gz')
>>> n2_img = nib.load(example_ni2)
>>> n2_img
<nibabel.nifti2.Nifti2Image object at ...>

The NIfTI header

The NIfTI 1 header is a small C structure of size 352 bytes. It contains the following fields:

>>> n1_header = n1_img.header
>>> print(n1_header)                     # doctest: +SKIP
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 57
dim             : [  4 128  96  24   2   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : int16
bitpix          : 16
slice_start     : 0
pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 23
slice_code      : unknown
xyzt_units      : 10
cal_max         : 1162.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
aux_file        : b''
qform_code      : scanner
sform_code      : scanner
quatern_b       : -1.94510681403e-26
quatern_c       : -0.996708512306
quatern_d       : -0.081068739295
qoffset_x       : 117.855102539
qoffset_y       : -35.7229423523
qoffset_z       : -7.24879837036
srow_x          : [  -2.      0.      0.    117.86]
srow_y          : [ -0.     1.97  -0.36 -35.72]
srow_z          : [ 0.    0.32  2.17 -7.25]
intent_name     : b''
magic           : b'n+1'

The NIfTI 2 header is similar, but of length 540 bytes, with fewer fields:

>>> n2_header = n2_img.header
>>> print(n2_header)                     # doctest: +SKIP
    <class 'nibabel.nifti2.Nifti2Header'> object, endian='<'
    sizeof_hdr      : 540
    magic           : b'n+2'
    eol_check       : [13 10 26 10]
    datatype        : int16
    bitpix          : 16
    dim             : [ 4 32 20 12  2  1  1  1]
    intent_p1       : 0.0
    intent_p2       : 0.0
    intent_p3       : 0.0
    pixdim          : [   -1.      2.      2.      2.2  2000.      1.      1.      1. ]
    vox_offset      : 0
    scl_slope       : nan
    scl_inter       : nan
    cal_max         : 1162.0
    cal_min         : 0.0
    slice_duration  : 0.0
    toffset         : 0.0
    slice_start     : 0
    slice_end       : 23
    descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
    aux_file        : b''
    qform_code      : scanner
    sform_code      : scanner
    quatern_b       : -1.94510681403e-26
    quatern_c       : -0.996708512306
    quatern_d       : -0.081068739295
    qoffset_x       : 117.855102539
    qoffset_y       : -35.7229423523
    qoffset_z       : -7.24879837036
    srow_x          : [  -2.      0.      0.    117.86]
    srow_y          : [ -0.     1.97  -0.36 -35.72]
    srow_z          : [ 0.    0.32  2.17 -7.25]
    slice_code      : unknown
    xyzt_units      : 10
    intent_code     : none
    intent_name     : b''
    dim_info        : 57
    unused_str      : b''

You can get and set individual fields in the header using dict (mapping-type) item access. For example:

>>> n1_header['cal_max']
array(1162.0, dtype=float32)
>>> n1_header['cal_max'] = 1200
>>> n1_header['cal_max']
array(1200.0, dtype=float32)

Check the attributes of the header for get_ / set_ methods to get and set various combinations of NIfTI header fields.

The get_ / set_ methods should check and apply valid combinations of values from the header, whereas you can do anything you like with the dict / mapping item access. It is safer to use the get_ / set_ methods and use the mapping item access only if the get_ / set_ methods will not do what you want.

The NIfTI affines

Like other nibabel image types, NIfTI images have an affine relating the voxel coordinates to world coordinates in RAS+ space:

>>> n1_img.affine
array([[  -2.  ,    0.  ,    0.  ,  117.86],
       [  -0.  ,    1.97,   -0.36,  -35.72],
       [   0.  ,    0.32,    2.17,   -7.25],
       [   0.  ,    0.  ,    0.  ,    1.  ]])

Unlike other formats, the NIfTI header format can specify this affine in one of three ways |--| the sform affine, the qform affine and the fall-back header affine.

Nibabel uses an :ref:`algorithm <choosing-image-affine>` to chose which of these three it will use for the overall image affine.

The sform affine

The header stores the three first rows of the 4 by 4 affine in the header fields srow_x, srow_y, srow_z. The header does not store the fourth row because it is always [0, 0, 0, 1] (see :doc:`coordinate_systems`).

You can get the sform affine specifically with the get_sform() method of the image or the header.

For example:

>>> print(n1_header['srow_x'])
[  -2.      0.      0.    117.86]
>>> print(n1_header['srow_y'])
[ -0.     1.97  -0.36 -35.72]
>>> print(n1_header['srow_z'])
[ 0.    0.32  2.17 -7.25]
>>> print(n1_header.get_sform())
[[  -2.      0.      0.    117.86]
 [  -0.      1.97   -0.36  -35.72]
 [   0.      0.32    2.17   -7.25]
 [   0.      0.      0.      1.  ]]

This affine is valid only if the sform_code is not zero.

>>> print(n1_header['sform_code'])
1

The different sform code values specify which RAS+ space the sform affine refers to, with these interpretations:

Code Label Meaning
0 unknown sform not defined
1 scanner RAS+ in scanner coordinates
2 aligned RAS+ aligned to some other scan
3 talairach RAS+ in Talairach atlas space
4 mni RAS+ in MNI atlas space

In our case the code is 1, meaning "scanner" alignment.

You can get the affine and the code using the coded=True argument to get_sform():

>>> print(n1_header.get_sform(coded=True))
(array([[  -2.  ,    0.  ,    0.  ,  117.86],
       [  -0.  ,    1.97,   -0.36,  -35.72],
       [   0.  ,    0.32,    2.17,   -7.25],
       [   0.  ,    0.  ,    0.  ,    1.  ]]), array(1, dtype=int16))

You can set the sform with with the get_sform() method of the header and the image.

>>> n1_header.set_sform(np.diag([2, 3, 4, 1]))
>>> n1_header.get_sform()
array([[ 2.,  0.,  0.,  0.],
       [ 0.,  3.,  0.,  0.],
       [ 0.,  0.,  4.,  0.],
       [ 0.,  0.,  0.,  1.]])

Set the affine and code using the code parameter to set_sform():

>>> n1_header.set_sform(np.diag([3, 4, 5, 1]), code='mni')
>>> n1_header.get_sform(coded=True)
(array([[ 3.,  0.,  0.,  0.],
       [ 0.,  4.,  0.,  0.],
       [ 0.,  0.,  5.,  0.],
       [ 0.,  0.,  0.,  1.]]), array(4, dtype=int16))

The qform affine

This affine can be calculated from a combination of the voxel sizes (entries 1 through 4 of the pixdim field), a sign flip called qfac stored in entry 0 of pixdim, and a quaternion that can be reconstructed from fields quatern_b, quatern_c, quatern_d.

See the code for the :meth:`get_qform() method <nibabel.nifti1.Nifti1Header.get_qform>` for details.

You can get and set the qform affine using the equivalent methods to those for the sform: get_qform(), set_qform().

>>> n1_header.get_qform(coded=True)
(array([[  -2.  ,    0.  ,    0.  ,  117.86],
       [  -0.  ,    1.97,   -0.36,  -35.72],
       [   0.  ,    0.32,    2.17,   -7.25],
       [   0.  ,    0.  ,    0.  ,    1.  ]]), array(1, dtype=int16))

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 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:

  1. If sform_code != 0 ('unknown') use the sform affine; else
  2. If qform_code != 0 ('unknown') use the qform affine; else
  3. Use the fall-back affine.

Data scaling

NIfTI uses a simple scheme for data scaling.

By default, nibabel will take care of this scaling for you, but there may be times that you want to control the data scaling yourself. If so, the next section describes how the scaling works and the nibabel implementation of same.

There are two scaling fields in the header called scl_slope and scl_inter.

The output data from a NIfTI image comes from:

  1. Loading the binary data from the image file;
  2. Casting the numbers to the binary format given in the header and returned by get_data_dtype();
  3. Reshaping to the output image shape;
  4. Multiplying the result by the header scl_slope value, if both of scl_slope and scl_inter are defined;
  5. Adding the value header scl_slope value to the result, if both of scl_slope and scl_inter are defined;

'Defined' means, the value is not NaN (not a number).

All this gets built into the array proxy when you load a NIfTI image.

When you load an image, the header scaling values automatically get set to NaN (undefined) to mark the fact that the scaling values have been consumed by the read. The scaling values read from the header on load only appear in the array proxy object.

To see how this works, let's make a new image with some scaling:

>>> array_data = np.arange(24, dtype=np.int16).reshape((2, 3, 4))
>>> affine = np.diag([1, 2, 3, 1])
>>> array_img = nib.Nifti1Image(array_data, affine)
>>> array_header = array_img.header

The default scaling values are NaN (undefined):

>>> array_header['scl_slope']
array(nan, dtype=float32)
>>> array_header['scl_inter']
array(nan, dtype=float32)

You can get the scaling values with the get_slope_inter() method:

>>> array_header.get_slope_inter()
(None, None)

None corresponds to the NaN scaling value (undefined).

We can set them in the image header, so they get saved to the header when the image is written. We can do this by setting the fields directly, or with set_slope_inter():

>>> array_header.set_slope_inter(2, 10)
>>> array_header.get_slope_inter()
(2.0, 10.0)
>>> array_header['scl_slope']
array(2.0, dtype=float32)
>>> array_header['scl_inter']
array(10.0, dtype=float32)

Setting the scale factors in the header has no effect on the image data before we save and load again:

>>> array_img.get_data()
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],
<BLANKLINE>
       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]], dtype=int16)

Now we save the image and load it again:

>>> nib.save(array_img, 'scaled_image.nii')
>>> scaled_img = nib.load('scaled_image.nii')

The data array has the scaling applied:

>>> scaled_img.get_data()
memmap([[[ 10.,  12.,  14.,  16.],
        [ 18.,  20.,  22.,  24.],
        [ 26.,  28.,  30.,  32.]],
<BLANKLINE>
       [[ 34.,  36.,  38.,  40.],
        [ 42.,  44.,  46.,  48.],
        [ 50.,  52.,  54.,  56.]]])

The header for the loaded image has had the scaling reset to undefined, to mark the fact that the scaling has been "consumed" by the load:

>>> scaled_img.header.get_slope_inter()
(None, None)

The original slope and intercept are still accessible in the array proxy object:

>>> scaled_img.dataobj.slope
2.0
>>> scaled_img.dataobj.inter
10.0

If the header scaling is undefined when we save the image, nibabel will try to find an optimum slope and intercept to best preserve the precision of the data in the output data type. Because nibabel will set the scaling to undefined when loading the image, or creating a new image, this is the default behavior.

.. testcleanup::

    os.chdir(pwd)
    import shutil
    shutil.rmtree(tmp_dir)

0 commit comments

Comments
 (0)