What is an image?#
In this exercise we explore the nature of NIfTI images.
First we will have a look at Python strings. Here is a variable called
my_string
with value “neuroimaging is hard but fair”
my_string = "neuroimaging is hard but fair"
# Show the result
my_string
'neuroimaging is hard but fair'
We can see what type of thing this variable contains (points to) using
the type
function:
type(my_string)
str
We can see how many characters the string has with the len
function:
len(my_string)
29
# The first character of the string
print(my_string[0])
n
# The last character of the string (don't forget indexing starts at 0)
print(my_string[28])
r
Now we introduce string slicing. This is where you take some sequential
characters from the string, using the colon (:
) between the square
brackets. The value before the colon is the index to the first character you
want, and the value after the colon is the index to the character after the
last character you want. It sounds strange, but you will get used to it…
# The first two characters of the string
print(my_string[0:2]) # from index 0 up to, but not including, 2
ne
# The first 5 characters of the string
print(my_string[0:5]) # from index 0 up to, but not including, 5
neuro
We will go into more details on strings and slicing soon.
Now we will try loading an example image and seeing if we can understand the image data.
Exploring modules and objects#
If you want to explore modules or objects, type their name followed by a period, and press tab to see what functions or classes are available.
Let us start by making an object to point to the current working directory. We
can do that with the Path
class from the pathlib module:
# Import the Path class
from pathlib import Path
# Path() points to the current working directory by default.
cwd = Path()
cwd
PosixPath('.')
The working directory of the notebook is the directory that contains this notebook file.
The new cwd
object is if type (class) Path
.
type(cwd)
pathlib.PosixPath
Try exploring this cwd
object now. Type cwd.
(cwd
followed by a period)
then press the Tab key, to see everything attached to the cwd
object.
Continue typing so you have cwd.absolute
, and then type ?
followed by
Return. This shows you the help for the Path
absolute
method. (Remember, a
method is a function attached to an object).
# Use this cell to explore the "cwd" object.
Using the absolute
method, we can print out the full (absolute) path to Python’s
working directory:
cwd.absolute()
PosixPath('/home/runner/work/textbook/textbook')
We next fetch a data file from the web. We have a special utility to do that,
that knows where the data files are for this course. The utility is called,
simply, nipraxis
.
# Load utility that will fetch data from the web and store it.
import nipraxis
Here we ask Nipraxis to download the data to the local hard disk.
structural_fname = nipraxis.fetch_file('ds114_sub009_highres.nii')
# Show the filename.
structural_fname
'/home/runner/.cache/nipraxis/0.5/ds114_sub009_highres.nii'
Let’s read the bytes from the image into memory using the read_bytes
method
of the Path
object:
# Open a file, read in binary bytes.
contents = Path(structural_fname).read_bytes()
How do I find out what type
of object is attached to this variable called
contents
?
# your code here
How big is this file in terms of bytes? Can you find out from the
contents
variable? (Hint: you want to know the length of
contents
).
# n_bytes = ?
If 1 mebibyte (MiB) (http://en.wikipedia.org/wiki/Megabyte) is size 1024 * 1024, what is the file size in MiB? (Hint - the right answer is between 0 and 100).
# n_mib = ?
This is a NIfTI1 format file. That means that the first 352 bytes contains the “header” that describes the parameters of the image and the data following.
We might want to print out the contents of the first 352 bytes of contents
to have a look at it.
To do this, you will need to use string slicing to get the first 352 bytes:
# Here you print out the first 352 characters of `contents`
# Your code here:
# print(...)
Which software wrote this image?
Here is the format of the NIfTI1 header : http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields
We are now going to try and work out the datatype
of this image. This is
stored in the datatype
field of the header. Careful - there is also a
data_type
field (with an underscore), which we will ignore.
Looking at the web page above, how many bytes is the datatype
value
stored in?
How would you get the bytes in contents
that contain the datatype
value?
Hint - you need slicing again, and the information from Byte offset
column in the NIfTI1 header page above):
Hint2 - if you want to work it out, don’t look at the cell further down!
# data_type_chars = ?
The datatype
value is stored in binary form (rather than text form).
The value for datatype
is stored in the header in the same format
that the computer stores the number in memory. We want to convert this
binary format to a number that Python understands. To do that, we use
the struct module.
import struct
We are going to use the struct.unpack
function. Open a new cell
below this one with b
and type struct.unpack?
followed by
Shift-Return to see the help for this function.
Now we have read the help, we know we need two things. The first is a string that give the code for the binary format of the data. This is the “format string”. The second is the string containing the bytes of the data.
We first need to specify the format of the character data. Have a look at the help on format strings in the Python documentation and the NIfTI web page above.
Here is the format specifier for our value:
fmt_specifier = 'h' # Why? (check the web pages above)
Now we read the datatype value into a number that Python understands:
# In case you didn't do this above, this is the way to get the bytes
# we need from the header.
data_type_chars = contents[70:72]
datatype = struct.unpack(fmt_specifier, data_type_chars)
print(datatype)
(16,)
This is a numerical code for a data type. What actual data type is this? (See: http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/datatype.html)
We could continue reading the NIfTI header in the same way, but luckily
someone has done that work for us. Enter the nibabel
package:
import nibabel
For now, we will use this package without worrying much about how it works.
Have a look to see what nibabel
can do by opening up a new cell with b
and typing nibabel?
and nibabel.
followed by Tab.
As with most Python packages, you can check what version of nibabel you have
by printing the __version__
variable of the package:
print(nibabel.__version__)
5.1.0
If you have a nibabel version below 3.0, please let your instructor know so they can fix that.
You can make an image object by passing an image file name to nibabel.load
:
img = nibabel.load(structural_fname)
We now have a Nibabel image object - and specifically, a Nifti type of image object.
type(img)
nibabel.nifti1.Nifti1Image
Let’s have a look at the header
that Nibabel read in when it made the image object.
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 156 256 1 1 1 1]
intent_p1 : 0.0
intent_p2 : 0.0
intent_p3 : 0.0
intent_code : none
datatype : float32
bitpix : 32
slice_start : 0
pixdim : [1. 1. 1.3002223 1. 0.00972 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'FSL5.0'
aux_file : b''
qform_code : scanner
sform_code : scanner
quatern_b : -0.11747453
quatern_c : 0.008146102
quatern_d : 0.022481605
qoffset_x : -129.82573
qoffset_y : -119.09057
qoffset_z : -143.41777
srow_x : [ 9.9885648e-01 -6.0528666e-02 1.0895197e-02 -1.2982573e+02]
srow_y : [ 4.2725317e-02 1.2630211e+00 2.3362094e-01 -1.1909057e+02]
srow_z : [-2.1454209e-02 -3.0280653e-01 9.7226673e-01 -1.4341777e+02]
intent_name : b''
magic : b'n+1'
As you can see, it has worked out the datatype for us.
Soon, we do some more work to get used to basic Python. After that we will start playing with the image using the Python tools for arrays, and for plotting.