From 7bf237782dc06c5595b7e5d96a1cac7614ad573a Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 1 Mar 2022 11:58:37 +0100 Subject: [PATCH] enh: finishing the transforms module Adds a bunch of notebooks about coordinates, affines and nonlinear transforms to the jupyter book. --- _config.yml | 105 ++++++++++-------- _toc.yml | 5 + code/rotations.py | 66 +++++++++++ pages/dipy_registration.Rmd | 40 ++----- pages/ds107_sub012_highres.nii | 1 + pages/ds114_sub009_highres_brain_222.nii | 1 + pages/ds114_sub009_highres_moved.hdr | 1 + pages/ds114_sub009_highres_moved.img | 1 + pages/image_header_and_affine.Rmd | 43 +------ pages/images_and_affines.Rmd | 91 ++++++++------- ...cbm152_t1_tal_nlin_asym_09a_masked_222.nii | 1 + pages/resampling_with_ndimage.Rmd | 36 +----- pages/rotation_2d_3d.md | 40 ++++--- pages/rotations.py | 1 + requirements.txt | 2 + 15 files changed, 231 insertions(+), 203 deletions(-) create mode 100644 code/rotations.py create mode 120000 pages/ds107_sub012_highres.nii create mode 120000 pages/ds114_sub009_highres_brain_222.nii create mode 120000 pages/ds114_sub009_highres_moved.hdr create mode 120000 pages/ds114_sub009_highres_moved.img create mode 120000 pages/mni_icbm152_t1_tal_nlin_asym_09a_masked_222.nii create mode 120000 pages/rotations.py diff --git a/_config.yml b/_config.yml index aa23ce0e..4a93c7d9 100644 --- a/_config.yml +++ b/_config.yml @@ -22,31 +22,58 @@ exclude_patterns: # string_formatting.Rmd. # https://github.com/executablebooks/meta/discussions/105 # https://myst-nb.readthedocs.io/en/latest/use/orphaned_nb.html#orphaned-nb - - README.md - - todo.md - - _scripts + # OE: I have sorted the pages with a tentative sectioning + # + # Python and Numpy - pages/allclose.Rmd - - pages/anterior_cingulate.md - pages/arange.Rmd - - pages/array_reductions.md - - pages/bibliography.md - pages/boolean_indexing.Rmd - - pages/choosing_editor.md - - pages/classes.md - - pages/classes_and_labs.md - - pages/coding_style.md + - pages/array_reductions.md - pages/comparing_arrays.Rmd - pages/comparing_floats.md - pages/diag_inverse.md - - pages/diagnostics_project.md - - pages/dipy_registration.Rmd - pages/docstrings.md - pages/dot_and_outer.md - pages/dot_outer.Rmd - - pages/ds114_sub009_highres.nii - - pages/example_data.md - - pages/exercises.md - pages/floating_in_text.md + - pages/index_reshape.Rmd + - pages/keyword_arguments.Rmd + - pages/list_comprehensions.Rmd + - pages/methods_vs_functions.Rmd + - pages/nans.md + - pages/newaxis.Rmd + - pages/numpy_diag.Rmd + - pages/numpy_meshgrid.Rmd + - pages/numpy_random.Rmd + - pages/numpy_squeeze.Rmd + - pages/numpy_transpose.Rmd + - pages/on_loops.Rmd + - pages/packages_namespaces.Rmd + - pages/printing_floating.Rmd + - pages/string_literals.md + - pages/subtract_mean_math.md + - pages/subtract_means.Rmd + - pages/two_dunders.md + # Images and code and images + - pages/saving_images.Rmd + - pages/plot_lines.Rmd + - pages/subplots.Rmd + # Coordinate systems and spatial transforms + # [all transferred already] + # Into the fourth dimension + - pages/reshape_and_4d.Rmd + - pages/tr_and_headers.Rmd + # Statistics + - pages/whole_image_statistics.Rmd + - pages/pearson_functions.md + - pages/validate_against_scipy.Rmd + # Hemodynamic response modeling + - pages/anterior_cingulate.md + - pages/otsu_threshold.Rmd + - pages/introducing_nipype.Rmd + # Code organization and collaboration + - pages/choosing_editor.md + - pages/coding_style.md - pages/full_scripting.md - pages/git_videos.md - pages/git_walk_through.Rmd @@ -54,54 +81,34 @@ exclude_patterns: - pages/github_dummies_homework.md - pages/github_glm_homework.md - pages/github_pca_homework.md - - pages/glossary.md - - pages/image_header_and_affine.Rmd - - pages/images_and_affines.Rmd - - pages/index.md - - pages/index_reshape.Rmd - pages/installation_on_linux.md - pages/installation_on_mac.md - pages/installation_on_windows.md - - pages/introducing_nipype.Rmd - - pages/keyword_arguments.Rmd - - pages/list_comprehensions.Rmd + # Meta-course + - README.md + - todo.md + - _scripts + - pages/bibliography.md + - pages/classes.md + - pages/classes_and_labs.md + - pages/diagnostics_project.md + - pages/ds114_sub009_highres.nii + - pages/example_data.md + - pages/exercises.md + - pages/glossary.md + - pages/index.md - pages/logistics.md - pages/markdown.md - pages/mentors.md - - pages/methods_vs_functions.Rmd - pages/multi_model_homework.md - - pages/nans.md - - pages/newaxis.Rmd - - pages/numpy_diag.Rmd - - pages/numpy_meshgrid.Rmd - - pages/numpy_random.Rmd - - pages/numpy_squeeze.Rmd - - pages/numpy_transpose.Rmd - - pages/on_loops.Rmd - - pages/otsu_threshold.Rmd - - pages/packages_namespaces.Rmd - pages/participate.md - pages/participate_grading.md - - pages/pearson_functions.md - - pages/plot_lines.Rmd - pages/preparation.md - - pages/printing_floating.Rmd - pages/project_grading.md - pages/projects.md - - pages/resampling_with_ndimage.Rmd - - pages/reshape_and_4d.Rmd - - pages/rotation_2d_3d.md - - pages/saving_images.Rmd - - pages/string_literals.md - - pages/subplots.Rmd - - pages/subtract_mean_math.md - - pages/subtract_means.Rmd - pages/topics.md - - pages/tr_and_headers.Rmd - - pages/two_dunders.md + # Drop? - pages/using_pythonpath.Rmd - - pages/validate_against_scipy.Rmd - - pages/whole_image_statistics.Rmd # Define the name of the latex output file for PDF builds latex: diff --git a/_toc.yml b/_toc.yml index c379e9eb..61d5d4b7 100644 --- a/_toc.yml +++ b/_toc.yml @@ -26,6 +26,11 @@ parts: - file: pages/nibabel_affines - file: pages/nibabel_apply_affine - file: pages/map_coordinates + - file: pages/image_header_and_affine + - file: pages/images_and_affines + - file: pages/rotation_2d_3d + - file: pages/resampling_with_ndimage + - file: pages/dipy_registration - caption: Statistics chapters: - file: pages/mean_test_example diff --git a/code/rotations.py b/code/rotations.py new file mode 100644 index 00000000..891908e7 --- /dev/null +++ b/code/rotations.py @@ -0,0 +1,66 @@ +""" Rotation matrices for rotations around x, y, z axes + +See: https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations +""" + +import numpy as np + + +def x_rotmat(theta): + """ Rotation matrix for rotation of `theta` radians around x axis + + Parameters + ---------- + theta : scalar + Rotation angle in radians + + Returns + ------- + M : shape (3, 3) array + Rotation matrix + """ + cos_t = np.cos(theta) + sin_t = np.sin(theta) + return np.array([[1, 0, 0], + [0, cos_t, -sin_t], + [0, sin_t, cos_t]]) + + +def y_rotmat(theta): + """ Rotation matrix for rotation of `theta` radians around y axis + + Parameters + ---------- + theta : scalar + Rotation angle in radians + + Returns + ------- + M : shape (3, 3) array + Rotation matrix + """ + cos_t = np.cos(theta) + sin_t = np.sin(theta) + return np.array([[cos_t, 0, sin_t], + [0, 1, 0], + [-sin_t, 0, cos_t]]) + + +def z_rotmat(theta): + """ Rotation matrix for rotation of `theta` radians around z axis + + Parameters + ---------- + theta : scalar + Rotation angle in radians + + Returns + ------- + M : shape (3, 3) array + Rotation matrix + """ + cos_t = np.cos(theta) + sin_t = np.sin(theta) + return np.array([[cos_t, -sin_t, 0], + [sin_t, cos_t, 0], + [0, 0, 1]]) diff --git a/pages/dipy_registration.Rmd b/pages/dipy_registration.Rmd index 8b61b436..dc0778c3 100644 --- a/pages/dipy_registration.Rmd +++ b/pages/dipy_registration.Rmd @@ -6,11 +6,13 @@ jupyter: format_name: rmarkdown format_version: '1.2' jupytext_version: 1.11.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 --- -$\newcommand{L}[1]{\| #1 \|}\newcommand{VL}[1]{\L{ \vec{#1} }}\newcommand{R}[1]{\operatorname{Re}\,(#1)}\newcommand{I}[1]{\operatorname{Im}\, (#1)}$ - -## Registration with dipy +# Registration with dipy [Dipy](http://nipy.org/dipy) is a Python package for diffusion imaging. @@ -86,9 +88,9 @@ Dipy works on the image data arrays. It also needs the affine arrays of each of the images: ```{python} -moving_data = moving_img.get_data() +moving_data = moving_img.get_fdata() moving_affine = moving_img.affine -template_data = template_img.get_data() +template_data = template_img.get_fdata() template_affine = template_img.affine ``` @@ -262,31 +264,3 @@ regtools.overlay_slices(template_data, warped_moving, None, 1, regtools.overlay_slices(template_data, warped_moving, None, 2, "Template", "Transformed") ``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pages/ds107_sub012_highres.nii b/pages/ds107_sub012_highres.nii new file mode 120000 index 00000000..07e2c060 --- /dev/null +++ b/pages/ds107_sub012_highres.nii @@ -0,0 +1 @@ +../data/ds107_sub012_highres.nii \ No newline at end of file diff --git a/pages/ds114_sub009_highres_brain_222.nii b/pages/ds114_sub009_highres_brain_222.nii new file mode 120000 index 00000000..056bf8be --- /dev/null +++ b/pages/ds114_sub009_highres_brain_222.nii @@ -0,0 +1 @@ +../data/ds114_sub009_highres_brain_222.nii \ No newline at end of file diff --git a/pages/ds114_sub009_highres_moved.hdr b/pages/ds114_sub009_highres_moved.hdr new file mode 120000 index 00000000..ccf244f7 --- /dev/null +++ b/pages/ds114_sub009_highres_moved.hdr @@ -0,0 +1 @@ +../data/ds114_sub009_highres_moved.hdr \ No newline at end of file diff --git a/pages/ds114_sub009_highres_moved.img b/pages/ds114_sub009_highres_moved.img new file mode 120000 index 00000000..9c8ad1e9 --- /dev/null +++ b/pages/ds114_sub009_highres_moved.img @@ -0,0 +1 @@ +../data/ds114_sub009_highres_moved.img \ No newline at end of file diff --git a/pages/image_header_and_affine.Rmd b/pages/image_header_and_affine.Rmd index 85245b56..02531b8e 100644 --- a/pages/image_header_and_affine.Rmd +++ b/pages/image_header_and_affine.Rmd @@ -6,11 +6,13 @@ jupyter: format_name: rmarkdown format_version: '1.2' jupytext_version: 1.11.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 --- -$\newcommand{L}[1]{\| #1 \|}\newcommand{VL}[1]{\L{ \vec{#1} }}\newcommand{R}[1]{\operatorname{Re}\,(#1)}\newcommand{I}[1]{\operatorname{Im}\, (#1)}$ - -## The image header and affine +# The image header and affine See: [coordinate systems and affine transforms](http://nipy.org/nibabel/coordinate_systems.html) for an introduction. @@ -18,13 +20,6 @@ See: [coordinate systems and affine transforms](http://nipy.org/nibabel/coordina # import common modules import numpy as np np.set_printoptions(precision=4, suppress=True) # print arrays to 4DP -import matplotlib.pyplot as plt -``` - -```{python} -#: gray colormap and nearest neighbor interpolation by default -plt.rcParams['image.cmap'] = 'gray' -plt.rcParams['image.interpolation'] = 'nearest' ``` ## The image affine @@ -87,31 +82,3 @@ 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pages/images_and_affines.Rmd b/pages/images_and_affines.Rmd index 1b5ba5e1..d7bdd5ac 100644 --- a/pages/images_and_affines.Rmd +++ b/pages/images_and_affines.Rmd @@ -6,11 +6,13 @@ jupyter: format_name: rmarkdown format_version: '1.2' jupytext_version: 1.11.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 --- -$\newcommand{L}[1]{\| #1 \|}\newcommand{VL}[1]{\L{ \vec{#1} }}\newcommand{R}[1]{\operatorname{Re}\,(#1)}\newcommand{I}[1]{\operatorname{Im}\, (#1)}$ - -## Images and affines +# Images and affines See: [coordinate systems and affine transforms](http://nipy.org/nibabel/coordinate_systems.html) for background. @@ -25,20 +27,40 @@ import nibabel as nib ## Affines, inverses -We often have the situation where we compose an affine of several -transformations. We do the composing using matrix multiplication. For example, -the following code composes two rotations and a translation. We are using the -`rotations.py` module to make the 3D rotation matrices. Remember – matrix multiplication works right to left. +We often have the situation where we compose an affine of several transformations. +We do the composing using matrix multiplication. +For example, the following code composes two rotations and a translation. -```{python} -# get functions to make 3D rotation matrices -from rotations import x_rotmat, y_rotmat, z_rotmat -``` +
+ +**Remember:** +Matrix multiplication works right to left. + +
Here is a rotation matrix (3 x 3) for a rotation of -0.2 radians around the x axis: ```{python} +def x_rotmat(theta): + """ Rotation matrix for rotation of `theta` radians around x axis + + Parameters + ---------- + theta : scalar + Rotation angle in radians + + Returns + ------- + M : shape (3, 3) array + Rotation matrix + """ + cos_t = np.cos(theta) + sin_t = np.sin(theta) + return np.array([[1, 0, 0], + [0, cos_t, -sin_t], + [0, sin_t, cos_t]]) + first_rotation = x_rotmat(-0.2) first_rotation ``` @@ -55,6 +77,25 @@ first_affine Now we made a second affine matrix for a rotation around y of 0.4 radians: ```{python} +def y_rotmat(theta): + """ Rotation matrix for rotation of `theta` radians around y axis + + Parameters + ---------- + theta : scalar + Rotation angle in radians + + Returns + ------- + M : shape (3, 3) array + Rotation matrix + """ + cos_t = np.cos(theta) + sin_t = np.sin(theta) + return np.array([[cos_t, 0, sin_t], + [0, 1, 0], + [-sin_t, 0, cos_t]]) + second_affine = np.eye(4) second_affine[:3, :3] = y_rotmat(0.4) second_affine @@ -198,31 +239,3 @@ F = third_affine.dot(second_affine) F np.allclose(third_with_second, F) ``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pages/mni_icbm152_t1_tal_nlin_asym_09a_masked_222.nii b/pages/mni_icbm152_t1_tal_nlin_asym_09a_masked_222.nii new file mode 120000 index 00000000..416ff5ef --- /dev/null +++ b/pages/mni_icbm152_t1_tal_nlin_asym_09a_masked_222.nii @@ -0,0 +1 @@ +../data/mni_icbm152_t1_tal_nlin_asym_09a_masked_222.nii \ No newline at end of file diff --git a/pages/resampling_with_ndimage.Rmd b/pages/resampling_with_ndimage.Rmd index 1c5ab994..9b559175 100644 --- a/pages/resampling_with_ndimage.Rmd +++ b/pages/resampling_with_ndimage.Rmd @@ -6,11 +6,13 @@ jupyter: format_name: rmarkdown format_version: '1.2' jupytext_version: 1.11.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 --- -$\newcommand{L}[1]{\| #1 \|}\newcommand{VL}[1]{\L{ \vec{#1} }}\newcommand{R}[1]{\operatorname{Re}\,(#1)}\newcommand{I}[1]{\operatorname{Im}\, (#1)}$ - -## Resampling with scipy.ndimage +# Resampling with `scipy.ndimage` ```{python} # import common modules @@ -210,31 +212,3 @@ K.shape Remember that the `M` matrix and `translation` vector apply to the coordinates implied by the output shape. - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pages/rotation_2d_3d.md b/pages/rotation_2d_3d.md index 001cfe88..bbdafab6 100644 --- a/pages/rotation_2d_3d.md +++ b/pages/rotation_2d_3d.md @@ -1,3 +1,17 @@ +--- +jupyter: + jupytext: + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.11.5 + kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + # Rotations and rotation matrices ## Rotations in two dimensions @@ -40,19 +54,19 @@ See [rotation in 2D] for a visual proof. ## Rotations in three dimensions -Rotations in three dimensions extend simply from two dimensions. Consider a -[right-handed] set of x, y, z axes, maybe forming the x axis with your right -thumb, the y axis with your index finger, and the z axis with your middle -finger. Now look down the z axis, from positive z toward negative z. You see -the x and y axes pointing right and up respectively, on a plane in front of -you. A rotation around z leaves z unchanged, but changes x and y according to -the 2D rotation formula above: +Rotations in three dimensions extend simply from two dimensions. +Consider a [right-handed] set of x, y, z axes, maybe forming the x axis with your right thumb, the y axis with your index finger, and the z axis with your middle +finger. +Now look down the z axis, from positive z toward negative z. +You see the x and y axes pointing right and up respectively, on a plane in front of you. +A rotation around z leaves z unchanged, but changes x and y according to the 2D rotation formula above: $$ -R_z(\theta) &= \begin{bmatrix} -\cos \theta & -\sin \theta & 0 \\[3pt] -\sin \theta & \cos \theta & 0\\[3pt] -0 & 0 & 1\\ +R_z(\theta) = +\begin{bmatrix} +\cos \theta & -\sin \theta & 0 \\ +\sin \theta & \cos \theta & 0 \\ +0 & 0 & 1 \\ \end{bmatrix} $$ @@ -66,7 +80,7 @@ z' = y \sin \theta + z \cos \theta $$ $$ -R_x(\theta) &= \begin{bmatrix} +R_x(\theta) = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta \\[3pt] 0 & \sin \theta & \cos \theta \\[3pt] @@ -83,7 +97,7 @@ x' = z \sin \theta + x \cos \theta $$ $$ -R_y(\theta) &= \begin{bmatrix} +R_y(\theta) = \begin{bmatrix} \cos \theta & 0 & \sin \theta \\[3pt] 0 & 1 & 0 \\[3pt] -\sin \theta & 0 & \cos \theta \\ diff --git a/pages/rotations.py b/pages/rotations.py new file mode 120000 index 00000000..4c2effff --- /dev/null +++ b/pages/rotations.py @@ -0,0 +1 @@ +../code/rotations.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bff56cf0..019b98c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ nibabel ipython jupytext[myst] okpy +dipy +fury<0.8 # pacify dipy imports