/
what_is_an_image.Rmd
301 lines (224 loc) · 8.26 KB
/
what_is_an_image.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
---
jupyter:
jupytext:
notebook_metadata_filter: all,-language_info
split_at_heading: true
text_representation:
extension: .Rmd
format_name: rmarkdown
format_version: '1.2'
jupytext_version: 1.13.7
kernelspec:
display_name: Python 3
language: python
name: python3
prereqs:
- pathlib
---
# 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”
```{python}
my_string = "neuroimaging is hard but fair"
# Show the result
my_string
```
We can see what type of thing this variable contains (points to) using
the `type` function:
```{python}
type(my_string)
```
We can see how many characters the string has with the `len` function:
```{python}
len(my_string)
```
```{python}
# The first character of the string
print(my_string[0])
```
```{python}
# The last character of the string (don't forget indexing starts at 0)
print(my_string[28])
```
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…
```{python}
# The first two characters of the string
print(my_string[0:2]) # from index 0 up to, but not including, 2
```
```{python}
# The first 5 characters of the string
print(my_string[0:5]) # from index 0 up to, but not including, 5
```
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](pathlib.Rmd):
```{python}
# Import the Path class
from pathlib import Path
```
```{python}
# Path() points to the current working directory by default.
cwd = Path()
cwd
```
The *working directory* of the notebook is the directory that contains this
notebook file.
The new `cwd` object is if type (*class*) `Path`.
```{python}
type(cwd)
```
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).
```{python}
# 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*:
```{python}
cwd.absolute()
```
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`.
```{python}
# 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.
```{python}
structural_fname = nipraxis.fetch_file('ds114_sub009_highres.nii')
# Show the filename.
structural_fname
```
Let’s read the bytes from the image into memory using the `read_bytes` method
of the `Path` object:
```{python}
# 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`?
```{python}
# 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`).
```{python}
# n_bytes = ?
```
If 1 mebibyte (MiB)
([http://en.wikipedia.org/wiki/Megabyte](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).
```{python}
# n_mib = ?
```
This is a [NIfTI1 format](http://nifti.nimh.nih.gov/nifti-1) 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:
```{python}
# 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](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!
```{python}
# 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](https://docs.python.org/3/library/struct.html).
```{python}
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](https://docs.python.org/3/library/struct.html#format-characters)
in the Python documentation and the NIfTI web page above.
Here is the format specifier for our value:
```{python}
fmt_specifier = 'h' # Why? (check the web pages above)
```
Now we read the datatype value into a number that Python understands:
```{python}
# 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]
```
```{python}
datatype = struct.unpack(fmt_specifier, data_type_chars)
print(datatype)
```
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](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:
```{python}
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:
```{python}
print(nibabel.__version__)
```
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`:
```{python}
img = nibabel.load(structural_fname)
```
We now have a Nibabel image object - and specifically, a Nifti type of image object.
```{python}
type(img)
```
Let’s have a look at the `header` that Nibabel read in when it made the image object.
```{python}
print(img.header)
```
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.