The image header and affine#

See: coordinate systems and affine transforms for an introduction.

# import common modules
import numpy as np
np.set_printoptions(precision=4, suppress=True)  # print arrays to 4DP

The image affine#

So far we have not paid much attention to the image header. We first saw the image header in What is an image?.

From that exploration, we found that image consists of:

  • the array data;

  • metadata (data about the array data).

The header contains the metadata for the image.

One piece of metadata, is the image affine.

Here we fetch the image file, and load the image.

# Load the function to fetch the data file we need.
import nipraxis
# Fetch structural image
structural_fname = nipraxis.fetch_file('ds107_sub012_highres.nii')
# Show the file names
structural_fname
Downloading file 'ds107_sub012_highres.nii' from 'https://raw.githubusercontent.com/nipraxis/nipraxis-data/0.5/ds107_sub012_highres.nii' to '/home/runner/.cache/nipraxis/0.5'.
'/home/runner/.cache/nipraxis/0.5/ds107_sub012_highres.nii'

Load the image:

import nibabel as nib
img = nib.load(structural_fname)
img.affine
array([[   1.    ,    0.    ,    0.    , -127.    ],
       [   0.    ,    1.    ,    0.    ,  -83.3253],
       [   0.    ,    0.    ,    1.    ,  -90.0533],
       [   0.    ,    0.    ,    0.    ,    1.    ]])

As you can imagine, nibabel is getting the affine from the header:

print(img.header)
<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        : 0
dim             : [  3 256 208 192   1   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. 1. 1. 1. 0. 0. 0. 0.]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 0
slice_code      : unknown
xyzt_units      : 10
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'FSL4.0'
aux_file        : b''
qform_code      : scanner
sform_code      : scanner
quatern_b       : 0.0
quatern_c       : 0.0
quatern_d       : 0.0
qoffset_x       : -127.0
qoffset_y       : -83.3253
qoffset_z       : -90.05328
srow_x          : [   1.    0.    0. -127.]
srow_y          : [  0.       1.       0.     -83.3253]
srow_z          : [  0.       0.       1.     -90.0533]
intent_name     : b''
magic           : b'n+1'

Notice the srow_x, srow_y, srow_z fields in the header, that contain the affine for this image. It is not always this simple though – see http://nifti.nimh.nih.gov/nifti-1 for more details. In general, nibabel will take care of this for you, by extracting the affine from the header, and returning it via img.affine.

Nifti images can also be .img, .hdr pairs#

So far, all the images we have seen have been NIfTI format images, stored in a single file with a .nii extension. The single file contains the header information, and the image array data.

The NIfTI format also allows the image to be stored as two files, one with extension .img storing the image array data, and another with extension .hdr storing the header. These are called NIfTI pair images.

For example, consider this pair of files:

# File containing image data.
struct_img_fname = nipraxis.fetch_file('ds114_sub009_highres_moved.img')
print(struct_img_fname)
# File containing image header.
struct_hdr_fname = nipraxis.fetch_file('ds114_sub009_highres_moved.hdr')
print(struct_hdr_fname)
Downloading file 'ds114_sub009_highres_moved.img' from 'https://raw.githubusercontent.com/nipraxis/nipraxis-data/0.5/ds114_sub009_highres_moved.img' to '/home/runner/.cache/nipraxis/0.5'.
Downloading file 'ds114_sub009_highres_moved.hdr' from 'https://raw.githubusercontent.com/nipraxis/nipraxis-data/0.5/ds114_sub009_highres_moved.hdr' to '/home/runner/.cache/nipraxis/0.5'.
/home/runner/.cache/nipraxis/0.5/ds114_sub009_highres_moved.img
/home/runner/.cache/nipraxis/0.5/ds114_sub009_highres_moved.hdr

We now have ds114_sub009_highres_moved.img and ds114_sub009_highres_moved.hdr. These two files together form one NIfTI image. You can load these with nibabel in the usual way:

pair_img = nib.load(struct_img_fname)
pair_img.affine
array([[   0.9416,   -0.4311,   -0.0586,  -98.8336],
       [   0.336 ,    1.1887,    0.2264, -164.1377],
       [  -0.0215,   -0.3028,    0.9723, -158.4178],
       [   0.    ,    0.    ,    0.    ,    1.    ]])

This form of the NIfTI image is getting less common, because it is inconvenient to have to keep the .img and .hdr files together, but you may still find them used. They have only one advantage, which is that, if some software wants to change only the header information, it only has to rewrite a small .hdr file, rather than the whole .nii file containing the image data and the header.