Skip to content

Commit 21f67ff

Browse files
committed
adds additional text from @jamie-mcclelland`s material for NiBabel section #14, including:
* more clarity on steps, * addd notes as callouts, * fixs few misspellings
1 parent 32ef744 commit 21f67ff

1 file changed

Lines changed: 106 additions & 42 deletions

File tree

episodes/itksnap.Rmd

Lines changed: 106 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ We are using CT Ventilation as a Functional Imaging Modality for Lung Cancer Rad
3131
The CT-vs-PET-Ventilation-Imaging collection is distributed under the Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/).
3232
The `CT-vs-PET-Ventilation-Imaging` dataset contains 20 lung cancer patients underwent exhale/inhale breath hold CT (BHCT), free-breathing four-dimensional CT (4DCT) and Galligas PET ventilation scans in a single session on a combined 4DPET/CT scanner.
3333

34-
We recomend using the BHCT scans for participants `CT-PET-VI-02` and `CT-PET-VI-03` showing very little motion between inhale and exhale (for PET scan and accompanying CT scan), and BHCT scans for participants `CT-PET-VI-05` and `CT-PET-VI-07` (the inhale and exhale CT scans), where 05 has a different number of slices between the inhale and exhale:
34+
We recommend using the BHCT scans for participants `CT-PET-VI-02` and `CT-PET-VI-03` showing very little motion between inhale and exhale (for PET scan and accompanying CT scan), and BHCT scans for participants `CT-PET-VI-05` and `CT-PET-VI-07` (the inhale and exhale CT scans), where 05 has a different number of slices between the inhale and exhale:
3535

3636
* CT-PET-VI-02
3737
```bash
@@ -160,7 +160,7 @@ Here, we provide instructions for handling multiple DICOM images, applying overl
160160
![Figure. ITK-SNAP cursor inspector](fig/itk-snap-cursor-inspector.png)
161161

162162
* **Load additional image using exhale scan by drag and drop**
163-
- Images can also be opened by simply dragging them on to the itksnap window.
163+
- Images can also be opened by simply dragging them on to the ITK-SNAP window.
164164
In a file explorer (Finder in Mac) go to the exhale_BH_CT folder and drag any of the dicom files on to the itksnap window.
165165
A small window will pop up asking What should itksnap do with this image?
166166
If you select Load as Main Image it will replace the current image.
@@ -191,8 +191,8 @@ Here, we provide instructions for handling multiple DICOM images, applying overl
191191
* To load the image as a colour overlay you need to use File->Add Another Image... (e.g., `5.000000-Thorax Insp 2.0 B70f-29873`).
192192
* Click on Browse and select any one of the files in the PET folder (e.g., `7.000000-Thorax Exp 2.0 B70f-73355`).
193193
* Make sure File Format is DICOM Image Series (it should be). Click Next and then Next again.
194-
* When the image has loaded select As a semi-transparent overlay. you can also set the overlay color map here - select Hot from the dropdown menu and click Next.
195-
* The image summary will then be displayed, along with a warning about possible loss of precision (which can be ignored). click Finish
194+
* When the image has loaded select As a semi-transparent overlay. You can also set the overlay color map here - select Hot from the drop-down menu and click Next.
195+
* The image summary will then be displayed, along with a warning about possible loss of precision (which can be ignored). Click Finish
196196

197197
![Figure. ITK-SNAP semi-transparent overlays](fig/itk-snap-semi-transparent-overlay.svg)
198198

@@ -221,7 +221,7 @@ Here, we provide instructions for handling multiple DICOM images, applying overl
221221

222222
![Figure. ITK-SNAP: Layer inspector with general tab](fig/itk-snap-layer-inspector-general-tab.png)
223223

224-
* **The Contrast tab** enables you to adjust how the image intensities are displayed for the different images. e.g. select the lung image and set the maximum value to 1000 (push enter after typing the number) and this will make the structures in the lung easier to see.
224+
* **The Contrast tab** enables you to adjust how the image intensities are displayed for the different images. e.g. Select the lung image and set the maximum value to 1000 (push enter after typing the number) and this will make the structures in the lung easier to see.
225225

226226
![Figure. ITK-SNAP: Layer inspector with contrast tab](fig/itk-snap-layer-inspector-contrast-tab.png)
227227

@@ -277,11 +277,17 @@ import dicom2nifti
277277
dicoms = Path("~/3_000000-CT_Lung_30_B31f-61322")
278278
dicom2nifti.convert_directory(dicoms, ".", compression=True, reorient=True)
279279
```
280+
#### Using others packages
281+
You might also be interested to look other packages:
282+
* https://github.com/rordenlab/dcm2niix
283+
* https://nipy.org/nibabel/dicom/dicom.html#dicom
284+
* https://neuroimaging-cookbook.github.io/recipes/dcm2nii_recipe/
280285

281286
## Viewing and understanding the NifTi header with NiBabel
282-
We are going to use the python package [NiBabel](https://nipy.org/nibabel/) to work with `NifTI` images.
283-
We recommend to check [documentation](https://nipy.org/nibabel/#documentation) for further deatils on using nibabel library.
284-
Please see [instructions](../_dependencies) to install such dependecies.
287+
We are going to use the python package [NiBabel](https://nipy.org/nibabel/) to upload `NifTI` images and learn some basic properties of the image using Nibabel.
288+
[`nibabel` api](https://nipy.org/nibabel/api.html#api) provides various image utilities, conversions methods, helpers.
289+
We recommend to checking [documentation](https://nipy.org/nibabel/#documentation) for further deatils on using nibabel library.
290+
Please see [instructions](https://github.com/HealthBioscienceIDEAS/Medical-Image-Registration-Short-Course/tree/main/_dependencies) to install `NiBabel` package and other dependecies.
285291

286292
### Loading images
287293
1. Open terminal, activate `mirVE` environment and run python
@@ -290,15 +296,25 @@ conda activate mirVE
290296
python
291297
```
292298

293-
2. Run python commands under `*/episodes` path:
299+
2. Importing python packages under `*/episodes` path
300+
301+
A NifTi image with extension `*.nii.gz` can be read using `nibabel`’s `load` function.
294302
```python
295303
import numpy as np
296304
import nibabel as nib
297305
import matplotlib.pyplot as plt
298306
nii3ct = nib.load("data/3_ct_lung__30__b31f.nii.gz")
299307
```
300308

301-
2. `NifTI` file properties:
309+
```callout
310+
`NiBabel`'s `load` function does not actually read the image data itself from disk, but
311+
does read and interpret the header, and provides functions for accessing the image data.
312+
Calling these functions reads the data from disk (unless it has already been read, in which
313+
case it may be cached in memory already.
314+
```
315+
316+
2. `NifTI` object type, shape/size
317+
The Nifti1Image object allows us to see the size/shape of the image and its data type.
302318
```python
303319
print(type(nii3ct))
304320
# <class 'nibabel.nifti1.Nifti1Image'>
@@ -314,6 +330,21 @@ nii3ct.shape
314330
# (512, 512, 175)
315331
```
316332

333+
3. Affine transform, mapping from voxel space to world space
334+
```callout
335+
There are two common ways of specifying the affine transformation mapping from voxel coordinates
336+
to world coordinates in the nifti header, called the `sform` and the `qform'.
337+
Another transformation is that neither the sform or qform is used the mapping is performed based just on the voxel dimensions.
338+
The **sform** directly stores the affine transformation in the header
339+
whereas the **qform** stores the affine transformation using [quaternions](https://en.wikipedia.org/wiki/Quaternion).
340+
```
341+
342+
```callout
343+
We strongly advise always using the `sform` over the `qform`, as:
344+
* It is easier to understand (at least for instructors).
345+
* It can contain shears which cannot be represented using the `qform`.
346+
```
347+
317348
```python
318349
nii3ct.affine
319350
## dicom2nifti.convert_directory(dicoms, ".", compression=True, reorient=True) # nii.gz
@@ -329,6 +360,7 @@ nii3ct.affine
329360
```
330361

331362
### Header features
363+
1. Printing `nii3ct.header` outputs
332364
```python
333365
print(nii3ct.header)
334366

@@ -378,8 +410,32 @@ print(nii3ct.header)
378410
#intent_name : b''
379411
#magic : b'n+1'
380412
```
413+
* `sform` transformation matrix
414+
You can see that the `sform` is stored in the `srow_x`, `srow_y`, and `srow_z` fields of the header.
415+
These specify the top three rows of a 4x4 matrix representing an affine transformation using homogenous coordinates.
416+
The fourth row is not stored as it is always `[0 0 0 1]`.
417+
You will notice that the sform matrix matches the affine transform in the `Nifti1Image` object.
381418

382-
### qform
419+
* `sform_code` and `qform_code`
420+
It can also be seen from the `sform_code` and `qform_code` that both the sform and qform are set for this image.
421+
There are 5 different sform/qform codes defined:
422+
423+
| Code | Label | Meaning |
424+
| --- | --- | --- |
425+
| 0 | unknown | sform not definted |
426+
| 1 | scanner | RAS+ is scanner coordinates |
427+
| 2 | aligned | RAS+ aligned to some other scan |
428+
| 3 | talairach | RAS+ in Talairach atlas space |
429+
| 4 | mni | RAS+ in MNI atlas space |
430+
431+
```callout
432+
But for most purposes all that matters is whether the code is unknown (numerical value 0) or one of the other valid values.
433+
If the code is unknown then the sform/qform is not specified (and any values provided in the sform/qform fields of the header will be ignored).
434+
For any other valid values the sform/qform is specified and the affine transform will be determined from the corresponding header values.
435+
```
436+
437+
### `qform` transform
438+
You can also see `qform` transform using the `get_qform` function, which returns the affine matrix represented by the qform:
383439
```python
384440
print(nii3ct.get_qform())
385441
## dicom2nifti.convert_directory(dicoms, ".", compression=True, reorient=True) # nii.gz
@@ -392,53 +448,58 @@ print(nii3ct.get_qform())
392448
# [ 0. -0.9765625 0. 466.01171875]
393449
# [ 0. 0. 2. -553.5 ]
394450
# [ 0. 0. 0. 1. ]]
451+
```
395452

453+
```callout
454+
However, the nifti format does not require that the sform and qform specify the same
455+
matrix, and it is not well defined what should be done when they are both provided and are
456+
different from each other.
457+
Often the `qform` is simply ignored and the `sform` is used, so the general advice is to only use the `sform` and set the `qform` to unknown to avoid any confusion.
396458
```
397459

398-
### coordinate system
460+
### Coordinate system
461+
#### LPS
399462
The original DICOM images assume the world coordinate system is LPS (i.e. values increase when moving to the left, posterior, and superior).
400463
See this helpful information on [DICOM orientations](https://nipy.org/nibabel/dicom/dicom_orientation.html), and determine if you obtained a similar affine transform from the DICOM headers.
401464
The values in the first two rows corresponding to the x and y dimensions would be the negative of those in the NifTi header.
402465

403-
### Obtaining image pixel data
404-
```python
405-
pixel_data=nii3ct.get_fdata()
406-
print(pixel_data.dtype, pixel_data.shape)
407-
#float64 (512, 512, 175)
408-
```
466+
#### RAS
467+
You will notice that the diagonal elements of the affine matrix match the voxel dimensions
468+
(as the affine matrix does not contain any rotations), but the values for the x and y
469+
dimensions are negative. This is because the voxel indices increase as you move to the left
470+
and posterior of the image/patient, but the nifti format assumes a RAS world coordinate
471+
system (i.e. the values increase as you move to the right and anterior of the patient).
472+
Therefore, the world coordinates decrease as the voxel indices increase.
409473

410-
```python
411-
plt.imshow(pixel_data[:,:,140], cmap="gray"); plt.show()
412-
#launch plot window
413-
```
474+
#### ITK-SNAP visualistaion coordinate system
475+
TOADD
414476

415477
## Modifying `NifTi` images and headers with Nibabel
416478

417-
### Using float64 data type
418-
* The image data for a Nifti1Image object can be accessed using the `get_fdata` function.
419-
Loading the data from disk and cast it to float64 type before returning the data as a numpy array.
479+
### Obtaining image pixel data
480+
The image data for a Nifti1Image object can be accessed using the `get_fdata` function.
481+
This will load the data from disk and cast it to float64 type before returning the data as a `numpy` array.
420482
```python
421483
image_data=nii3ct.get_fdata()
422484
print(type(image_data))
423485
#<class 'numpy.ndarray'>
424-
```
425486

426-
```python
427-
print(image_data.dtype)
428-
#float64
487+
print(pixel_data.dtype, pixel_data.shape)
488+
#float64 (512, 512, 175)
429489
```
430490

491+
You can also plot a particular slide, (e.g. 140) for such data.
431492
```python
432-
print(image_data.shape)
433-
#(512, 512, 175)
493+
plt.imshow(pixel_data[:,:,140], cmap="gray"); plt.show()
494+
#launch plot window
434495
```
435496

436-
* Use the `set_data_dtype` function to set the data type to float32.
437-
438-
Casting it to float64 prevents any integer-related errors and problems in downstream processing when using the data read by Nibabel.
497+
### Using float64 data type
498+
Casting image data to float64 prevents any integer-related errors and problems in downstream processing when using the data read by NiBabel.
439499
However, this does not change the type of the image stored on disk, which as we have seen is int16 for this image, and this could still lead to downstream errors or unexpected behaviour for any processing that works directly on the images stored on disk.
440-
Therefore, we are going to convert the image saved on disk to a floating point image.
441-
We are going to use 32 bit rather than 64 bit floating point, as this is usually sufficiently accurate.
500+
We are going to convert the image saved on disk to a floating point image, using the `set_data_dtype` function to set the data type to float32.
501+
Esentially, we are going to use 32 bit rather than 64 bit floating point, as this is usually sufficiently accurate.
502+
442503
```python
443504
print(nii3ct.get_data_dtype())
444505
#int16
@@ -450,10 +511,9 @@ print(nii3ct.get_data_dtype())
450511
#float32
451512
```
452513

453-
We are also going to set the qform to unknown as suggested above.
454-
This also updates the corresponding fields in the header.
455-
Use the `set_qform` function to set the `qform` code.
456-
To just set the code and not modify the other `qform` values in the header, the first input should be None.
514+
We are also going to set the `qform` to `unknown`, updating the corresponding fields in the header.
515+
We use the `set_qform` function to set the `qform` code.
516+
To just set the code and not modify the other `qform` values in the header, the first input should be `None`.
457517
It is not necessary to modify the other `qform` values as they should be ignored when the code is set to unknown.
458518

459519
```python
@@ -542,8 +602,12 @@ print(nii3ct_float32.header)
542602
#...
543603
```
544604

605+
606+
#### ITK-SNAP visualistaion of 16-bit integers vs 32 bit floats
607+
TOADD
608+
545609
### Cropping data
546-
Cropping data might offen contain background voxels which can be useful in some instances but might take up valuable RAM memory, especially for large images.
610+
Cropping data might often contain background voxels which can be useful in some instances but might take up valuable RAM memory, especially for large images.
547611

548612
* Read the image data from disk using `get_fdata` and then create a new array containing a copy of the desired slices:
549613
```python
@@ -680,7 +744,7 @@ nii3ct_float32_cropped_with_nibabel_slicer.affine
680744
### Cropped and aligned image
681745
Automated image registrations can be prone to be failure if there are very large initial differences between the images. A manual alignment is subjective, but can provide a good enough starting guess that keeps the objective, automated registration more reliable.
682746
* Manually aligning images
683-
For manaual image registration in ITK-SNAP go to Tools > image registration
747+
For manual image registration in ITK-SNAP go to Tools > image registration
684748

685749
![Figure. ITK-SNAP main window](fig/itk-snap-image-manual-registration.png)
686750

0 commit comments

Comments
 (0)