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.