Skip to content

Commit d02db15

Browse files
authored
Merge pull request #133 from oesteban/docs/comment-notebooks
DOC: Better document the ipython notebooks
2 parents be5341b + 5b7a43c commit d02db15

File tree

3 files changed

+165
-37
lines changed

3 files changed

+165
-37
lines changed

docs/conf.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,33 @@
222222

223223
# -- Options for versioning extension ----------------------------------------
224224
scv_show_banner = True
225+
226+
nbsphinx_prolog = """
227+
{% set docname = 'docs/' + env.doc2path(env.docname, base=None) %}
228+
229+
.. raw:: html
230+
231+
<div class="admonition note">
232+
This page was generated from
233+
<a class="reference external" href="https://github.com/poldracklab/nitransforms/blob/master\
234+
/{{ docname|e }}">{{ docname|e }}</a>.
235+
<script>
236+
if (document.location.host) {
237+
$(document.currentScript).replaceWith(
238+
'<a class="reference external" ' +
239+
'href="https://nbviewer.jupyter.org/url' +
240+
(window.location.protocol == 'https:' ? 's/' : '/') +
241+
window.location.host +
242+
window.location.pathname.slice(0, -4) +
243+
'ipynb">View in <em>nbviewer</em></a>.'
244+
);
245+
}
246+
</script>
247+
</div>
248+
249+
"""
250+
251+
# This is processed by Jinja2 and inserted after each notebook
252+
nbsphinx_epilog = """
253+
{% set docname = 'docs/notebooks' + env.doc2path(env.docname, base=None) %}
254+
"""

docs/notebooks/01_preparing_images.ipynb

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"## Preparing images"
7+
"## Preparing images\n",
8+
"\n",
9+
"This notebook uses the example NIfTI from NiBabel's documentation (\"`someones_anatomy.nii.gz`\") and creates several derived versions of it with different orientations, including oblique physical axes (i.e., the physical axes are rotated w.r.t. the canonical axes of the array), ordering of axes (e.g., RSA), and axis flips (e.g., LAS)."
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"### Preamble\n",
17+
"Prepare a Python environment and use a temporal directory for the outputs. After that, fetch the actual file from NiBabel documentation."
818
]
919
},
1020
{
@@ -75,6 +85,14 @@
7585
"!wget https://nipy.org/nibabel/_downloads/62985f4c43f499609fa65cb2eb955b79/someones_anatomy.nii.gz"
7686
]
7787
},
88+
{
89+
"cell_type": "markdown",
90+
"metadata": {},
91+
"source": [
92+
"### Load in the image\n",
93+
"Let's first visualize the example image, and retain some copies of the original header and affine."
94+
]
95+
},
7896
{
7997
"cell_type": "code",
8098
"execution_count": 3,
@@ -112,6 +130,14 @@
112130
"nii.orthoview()"
113131
]
114132
},
133+
{
134+
"cell_type": "markdown",
135+
"metadata": {},
136+
"source": [
137+
"### Generating variants\n",
138+
"Playing with the affine header and the data array ordering of axes and elements we generate several versions of the dataset with different orientations (LAS, LPS, oblique, LPS oblique)."
139+
]
140+
},
115141
{
116142
"cell_type": "code",
117143
"execution_count": 4,
@@ -151,6 +177,15 @@
151177
"lpsnew.to_filename('someones_anatomy_lpsrot.nii.gz')"
152178
]
153179
},
180+
{
181+
"cell_type": "markdown",
182+
"metadata": {},
183+
"source": [
184+
"### Quick test with AFNI's `3dWarp -deoblique`\n",
185+
"AFNI `3dWarp` comes with a `-deoblique` option that can be interesting to double check whether the examples above are useful.\n",
186+
"This example checks whether our *oblique* image's version is perceived as such by AFNI, and *realign* it with the canonical axes."
187+
]
188+
},
154189
{
155190
"cell_type": "code",
156191
"execution_count": 28,
@@ -173,6 +208,13 @@
173208
"!3dWarp -deoblique -prefix deob.nii.gz someones_anatomy_rot.nii.gz"
174209
]
175210
},
211+
{
212+
"cell_type": "markdown",
213+
"metadata": {},
214+
"source": [
215+
"AFNI stores the actual rotation matrices of the *deoblique* operation as NIfTI header extensions:"
216+
]
217+
},
176218
{
177219
"cell_type": "code",
178220
"execution_count": 30,
@@ -419,6 +461,16 @@
419461
"print(nb.load('deob.nii.gz').header.extensions[0].get_content().decode())"
420462
]
421463
},
464+
{
465+
"cell_type": "markdown",
466+
"metadata": {},
467+
"source": [
468+
"### Testing the variants with *NiTransforms*, *AFNI*, *ANTs*, and *FSL*, with a rigid-body transform\n",
469+
"Now, let's use these variants to check how they affect in concatenation with other transforms\n",
470+
"\n",
471+
"First, we check that, as *NiBabel* represents the data array disregarding the affine, the `.orthoview()` visualization of the *oblique* image shows the same apparent data orientation as for the original file."
472+
]
473+
},
422474
{
423475
"cell_type": "code",
424476
"execution_count": 5,
@@ -451,6 +503,13 @@
451503
"new.orthoview()"
452504
]
453505
},
506+
{
507+
"cell_type": "markdown",
508+
"metadata": {},
509+
"source": [
510+
"**Create a transform**. We test with a rigid-body transformation with 3 rotations and 3 translations"
511+
]
512+
},
454513
{
455514
"cell_type": "code",
456515
"execution_count": 6,
@@ -460,6 +519,14 @@
460519
"T = nb.affines.from_matvec(nb.eulerangles.euler2mat(x=0.9, y=0.001, z=0.001), [4.0, 2.0, -1.0])"
461520
]
462521
},
522+
{
523+
"cell_type": "markdown",
524+
"metadata": {},
525+
"source": [
526+
"#### *NiTransforms*\n",
527+
"Let's resample the dataset using *NiTransforms*. This result should be similar with the other libraries."
528+
]
529+
},
463530
{
464531
"cell_type": "code",
465532
"execution_count": 7,
@@ -512,6 +579,13 @@
512579
"moved.orthoview()"
513580
]
514581
},
582+
{
583+
"cell_type": "markdown",
584+
"metadata": {},
585+
"source": [
586+
"**Store the transform in other formats**. Let's leverage *NiTransforms*' features to store the transforms for ANTs, FSL, and AFNI."
587+
]
588+
},
515589
{
516590
"cell_type": "code",
517591
"execution_count": 10,
@@ -544,6 +618,13 @@
544618
"xfm.to_filename('M.afni', moving=new, fmt='afni')"
545619
]
546620
},
621+
{
622+
"cell_type": "markdown",
623+
"metadata": {},
624+
"source": [
625+
"#### Testing AFNI"
626+
]
627+
},
547628
{
548629
"cell_type": "code",
549630
"execution_count": 24,
@@ -669,6 +750,13 @@
669750
"moved.orthoview()"
670751
]
671752
},
753+
{
754+
"cell_type": "markdown",
755+
"metadata": {},
756+
"source": [
757+
"#### Testing ANTs/ITK"
758+
]
759+
},
672760
{
673761
"cell_type": "code",
674762
"execution_count": 13,
@@ -702,6 +790,13 @@
702790
"nb.load('moved-itk.nii.gz').orthoview()"
703791
]
704792
},
793+
{
794+
"cell_type": "markdown",
795+
"metadata": {},
796+
"source": [
797+
"#### Testing FSL"
798+
]
799+
},
705800
{
706801
"cell_type": "code",
707802
"execution_count": 14,
@@ -735,6 +830,14 @@
735830
"nb.load('moved-fsl.nii.gz').orthoview()"
736831
]
737832
},
833+
{
834+
"cell_type": "markdown",
835+
"metadata": {},
836+
"source": [
837+
"### Annex: understanding AFNI's deoblique\n",
838+
"The code in this section is just a preamble to the following notebook where we explore how AFNI implemented this operation."
839+
]
840+
},
738841
{
739842
"cell_type": "code",
740843
"execution_count": 35,
@@ -906,37 +1009,6 @@
9061009
"nb.load('moved-afni2.nii.gz').orthoview()\n",
9071010
"moved.orthoview()"
9081011
]
909-
},
910-
{
911-
"cell_type": "code",
912-
"execution_count": 31,
913-
"metadata": {},
914-
"outputs": [
915-
{
916-
"ename": "FileNotFoundError",
917-
"evalue": "No such file or no access: 'moved-RAS.nii.gz'",
918-
"output_type": "error",
919-
"traceback": [
920-
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
921-
"\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
922-
"\u001b[0;32m~/workspace/nibabel/nibabel/loadsave.py\u001b[0m in \u001b[0;36mload\u001b[0;34m(filename, **kwargs)\u001b[0m\n\u001b[1;32m 38\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 39\u001b[0;31m \u001b[0mstat_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 40\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mOSError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
923-
"\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'moved-RAS.nii.gz'",
924-
"\nDuring handling of the above exception, another exception occurred:\n",
925-
"\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
926-
"\u001b[0;32m<ipython-input-31-56fae86f86fd>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'moved-RAS.nii.gz'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0morthoview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mnb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'moved-affine-oblique-for-T-for.nii.gz'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0morthoview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mnb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'moved-affine-oblique-for-T-inv.nii.gz'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0morthoview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
927-
"\u001b[0;32m~/workspace/nibabel/nibabel/loadsave.py\u001b[0m in \u001b[0;36mload\u001b[0;34m(filename, **kwargs)\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0mstat_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mOSError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 41\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mFileNotFoundError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"No such file or no access: '%s'\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 42\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstat_result\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mst_size\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mImageFileError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Empty file: '%s'\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
928-
"\u001b[0;31mFileNotFoundError\u001b[0m: No such file or no access: 'moved-RAS.nii.gz'"
929-
]
930-
}
931-
],
932-
"source": []
933-
},
934-
{
935-
"cell_type": "code",
936-
"execution_count": null,
937-
"metadata": {},
938-
"outputs": [],
939-
"source": []
9401012
}
9411013
],
9421014
"metadata": {
@@ -955,9 +1027,9 @@
9551027
"name": "python",
9561028
"nbconvert_exporter": "python",
9571029
"pygments_lexer": "ipython3",
958-
"version": "3.7.3"
1030+
"version": "3.8.5"
9591031
}
9601032
},
9611033
"nbformat": 4,
9621034
"nbformat_minor": 2
963-
}
1035+
}

docs/notebooks/02_afni_deoblique.ipynb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"## AFNI Deoblique"
7+
"## AFNI Deoblique\n",
8+
"\n",
9+
"This notebook explores the implementation of *deobliquing* operations in AFNI, in order to be able to correctly manipulate transforms generated by the tool, and to produce transforms that can be correctly applied with AFNI.\n",
10+
"Most of the preparation comes from the previous notebook.\n",
11+
"\n",
12+
"### Preparation"
813
]
914
},
1015
{
@@ -199,6 +204,13 @@
199204
"print(np.linalg.inv(np.diag([-1, -1, 1, 1]).dot(R)).T)"
200205
]
201206
},
207+
{
208+
"cell_type": "markdown",
209+
"metadata": {},
210+
"source": [
211+
"### Run `3dWarp -deoblique`"
212+
]
213+
},
202214
{
203215
"cell_type": "code",
204216
"execution_count": 81,
@@ -286,6 +298,13 @@
286298
" return matvec_inv"
287299
]
288300
},
301+
{
302+
"cell_type": "markdown",
303+
"metadata": {},
304+
"source": [
305+
"### Inspecting affines and headers"
306+
]
307+
},
289308
{
290309
"cell_type": "code",
291310
"execution_count": 209,
@@ -1020,6 +1039,13 @@
10201039
"print(deob.header.extensions[0].get_content().decode())"
10211040
]
10221041
},
1042+
{
1043+
"cell_type": "markdown",
1044+
"metadata": {},
1045+
"source": [
1046+
"### Reverse-engineering matrix compositions of oblique datasets"
1047+
]
1048+
},
10231049
{
10241050
"cell_type": "code",
10251051
"execution_count": 38,
@@ -1183,9 +1209,9 @@
11831209
"name": "python",
11841210
"nbconvert_exporter": "python",
11851211
"pygments_lexer": "ipython3",
1186-
"version": "3.7.3"
1212+
"version": "3.8.5"
11871213
}
11881214
},
11891215
"nbformat": 4,
11901216
"nbformat_minor": 2
1191-
}
1217+
}

0 commit comments

Comments
 (0)