From 2cd8e147475855f72890fb36b0657ee135940e89 Mon Sep 17 00:00:00 2001 From: James Ryan Date: Wed, 1 Nov 2023 11:49:25 +1100 Subject: [PATCH 1/5] Add documentation on transformations --- docs/index.rst | 1 + docs/transform.rst | 5 ++++ transform.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 docs/transform.rst create mode 100644 transform.md diff --git a/docs/index.rst b/docs/index.rst index d44b710..8ce4356 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ Welcome to affinder's documentation! :maxdepth: 2 usage + transform .. include:: ../README.md diff --git a/docs/transform.rst b/docs/transform.rst new file mode 100644 index 0000000..3e8d8aa --- /dev/null +++ b/docs/transform.rst @@ -0,0 +1,5 @@ +Using skimage.transform.warp() +============== + +.. include:: ../transform.md + :parser: myst_parser.sphinx_ diff --git a/transform.md b/transform.md new file mode 100644 index 0000000..676416f --- /dev/null +++ b/transform.md @@ -0,0 +1,72 @@ +# Transformations +Lets assume some user has found a desired allignment and is ready to apply a transformation. +This section discusses nuance when applying atransformation to an image. + +## Scipy +When using `scipy.ndimage.affine_transform()` you must use the inverse transformation matrix i.e, the transformation from the reference image to the moving image. + +## Skimage +Similar to scipy, when using `skimage.transform.warp()`, you must use the inverse transfromation matrix. +Additionally, skimage uses X/Y coordinate conventions whereas napari uses NumPy like row/col coordinate conventions. This means that to use warp() correctly, you must first transpose the 0th and 1st row and column of the transformation matrix. e.g, + +```python +def matrix_rc2xy(affine_matrix): + swapped_cols = affine_matrix[:, [1, 0, 2]] + swapped_rows = swapped_cols[[1, 0, 2], :] + return swapped_rows +``` + +## Examples +### Scipy + + + + + +Please note that when using skimage.transform.warp(): + +- napari uses row column coordinate conventions, whereas skimage.transform.warp uses XY coordinate conventions. +- Additionally, ndimage.affine_transform and skimage.transform.warp expect the inverse transformation matrix, that is, the transformation from the reference image to the moving image. This is because when you want to create a new image, you need to find a value for every target pixel, so you want to go from every new pixel coordinate to the place it came from in the image you're transforming. + +please refer to the following code for reference on how to correctly use skimage.transform.warp(): + +```python +from skimage import data, transform +from scipy import ndimage as ndi +import napari +import numpy as np + +image0 = data.camera() +image1 = transform.rotate(image0[100:, 32:496], 60) + +viewer = napari.Viewer() +l0 = viewer.add_image(image0, colormap='bop blue', blending='additive') +l1 = viewer.add_image(image1, colormap='bop purple', blending='additive') + +qtwidget, widget = viewer.window.add_plugin_dock_widget( + 'affinder', 'Start affinder' + ) +widget.reference.bind(l0) +widget.moving.bind(l1) +widget() + +viewer.layers['image0_pts'].data = np.array([[148.19396647, 234.87779732], + [484.56804381, 240.55720892], + [474.77521025, 385.88403205]]) +viewer.layers['image1_pts'].data = np.array([[150.02534429, 80.65355322], + [314.75696913, 375.13825634], + [184.33085012, 439.81718637]]) + +def matrix_rc2xy(affine_matrix): + swapped_cols = affine_matrix[:, [1, 0, 2]] + swapped_rows = swapped_cols[[1, 0, 2], :] + return swapped_rows + +mat = np.asarray(l1.affine) +tfd_ndi = ndi.affine_transform(image1, np.linalg.inv(mat)) +viewer.add_image(tfd_ndi, colormap='bop orange', blending='additive') +tfd_skim = transform.warp(image1, np.linalg.inv(matrix_rc2xy(mat))) +viewer.add_image(tfd_skim, colormap='bop orange', blending='additive', visible=False) + +napari.run() +``` From 3eeac45807674399da853e185403b2452cfea19f Mon Sep 17 00:00:00 2001 From: James Ryan Date: Wed, 1 Nov 2023 13:28:38 +1100 Subject: [PATCH 2/5] update documentation --- transform.md | 62 +++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/transform.md b/transform.md index 676416f..ce021b9 100644 --- a/transform.md +++ b/transform.md @@ -1,34 +1,11 @@ -# Transformations -Lets assume some user has found a desired allignment and is ready to apply a transformation. -This section discusses nuance when applying atransformation to an image. - -## Scipy -When using `scipy.ndimage.affine_transform()` you must use the inverse transformation matrix i.e, the transformation from the reference image to the moving image. - -## Skimage -Similar to scipy, when using `skimage.transform.warp()`, you must use the inverse transfromation matrix. -Additionally, skimage uses X/Y coordinate conventions whereas napari uses NumPy like row/col coordinate conventions. This means that to use warp() correctly, you must first transpose the 0th and 1st row and column of the transformation matrix. e.g, - -```python -def matrix_rc2xy(affine_matrix): - swapped_cols = affine_matrix[:, [1, 0, 2]] - swapped_rows = swapped_cols[[1, 0, 2], :] - return swapped_rows -``` - -## Examples -### Scipy - - +# Using a saved transform matrix +Lets say we have used affinder to regester out moving image with the reference image and now we wish to use the resulting transform matrix for downstream analysis. +The guide describes the required changes to make the matrix compatable with scipy and skimage transformations. - - -Please note that when using skimage.transform.warp(): - -- napari uses row column coordinate conventions, whereas skimage.transform.warp uses XY coordinate conventions. -- Additionally, ndimage.affine_transform and skimage.transform.warp expect the inverse transformation matrix, that is, the transformation from the reference image to the moving image. This is because when you want to create a new image, you need to find a value for every target pixel, so you want to go from every new pixel coordinate to the place it came from in the image you're transforming. - -please refer to the following code for reference on how to correctly use skimage.transform.warp(): +## Getting an affinder matrix +Consider some image and its respective affine matrix. for this example we will use skimag's data.camera. +The code below loads the image and then rotates it to generate our moving image. +We then programatically lauch th affinder widget and add some points to get our transform matrix ```python from skimage import data, transform @@ -57,16 +34,31 @@ viewer.layers['image1_pts'].data = np.array([[150.02534429, 80.65355322], [314.75696913, 375.13825634], [184.33085012, 439.81718637]]) +mat = np.asarray(l1.affine) +``` + +At this point, `mat` holds the information required to transform the **moving** image to the **reference** image. +Before this matrix can be used however, it requires some processing which differs depending on which transformation method is to be used. + +## Scipy + +`scipy.ndimage.affine_transform()` transforms from the reference image to the moving image, therefore we should use the inverese matrix from our affinder output. That's because when you want to create a new image, you need to find a value for every target pixel, so you want to go from every new pixel coordinate to the place it came from in the image you're transforming. + +```python +tfd_ndi = ndi.affine_transform(image1, np.linalg.inv(mat)) +viewer.add_image(tfd_ndi, colormap='bop orange', blending='additive') +``` + +## Skimage +Similar to scipy, when using `skimage.transform.warp()`, it transforms from reference to moving, so you must use the inverse transfromation matrix. +Additionally, skimage uses X/Y coordinate conventions whereas napari uses NumPy like row/col coordinate conventions. This means that to use warp() correctly, you must first transpose the 0th and 1st row and column of the transformation matrix. e.g, + +```python def matrix_rc2xy(affine_matrix): swapped_cols = affine_matrix[:, [1, 0, 2]] swapped_rows = swapped_cols[[1, 0, 2], :] return swapped_rows -mat = np.asarray(l1.affine) -tfd_ndi = ndi.affine_transform(image1, np.linalg.inv(mat)) -viewer.add_image(tfd_ndi, colormap='bop orange', blending='additive') tfd_skim = transform.warp(image1, np.linalg.inv(matrix_rc2xy(mat))) viewer.add_image(tfd_skim, colormap='bop orange', blending='additive', visible=False) - -napari.run() ``` From 47e24cc3294cc6f9e27260cbbb850d03e96fc430 Mon Sep 17 00:00:00 2001 From: James Ryan Date: Wed, 1 Nov 2023 13:41:48 +1100 Subject: [PATCH 3/5] tidy documentation --- transform.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transform.md b/transform.md index ce021b9..0108b2e 100644 --- a/transform.md +++ b/transform.md @@ -42,7 +42,7 @@ Before this matrix can be used however, it requires some processing which differ ## Scipy -`scipy.ndimage.affine_transform()` transforms from the reference image to the moving image, therefore we should use the inverese matrix from our affinder output. That's because when you want to create a new image, you need to find a value for every target pixel, so you want to go from every new pixel coordinate to the place it came from in the image you're transforming. +`scipy.ndimage.affine_transform()` expects a matrix that transforms from the **reference** image to the **moving** image, therefore we should use the inverse matrix from our affinder output. That's because when you want to create a new image, you need to find a value for every target pixel, so you want to go from every new pixel coordinate to the place it came from in the image you're transforming. ```python tfd_ndi = ndi.affine_transform(image1, np.linalg.inv(mat)) @@ -51,7 +51,7 @@ viewer.add_image(tfd_ndi, colormap='bop orange', blending='additive') ## Skimage Similar to scipy, when using `skimage.transform.warp()`, it transforms from reference to moving, so you must use the inverse transfromation matrix. -Additionally, skimage uses X/Y coordinate conventions whereas napari uses NumPy like row/col coordinate conventions. This means that to use warp() correctly, you must first transpose the 0th and 1st row and column of the transformation matrix. e.g, +Additionally, skimage uses X/Y coordinate conventions whereas napari uses NumPy-like row/col coordinate conventions. This means that to use warp() correctly, you must first transpose the 0th and 1st row and column of the transformation matrix, as below. ```python def matrix_rc2xy(affine_matrix): @@ -62,3 +62,5 @@ def matrix_rc2xy(affine_matrix): tfd_skim = transform.warp(image1, np.linalg.inv(matrix_rc2xy(mat))) viewer.add_image(tfd_skim, colormap='bop orange', blending='additive', visible=False) ``` + +This should be fixed in skimage 2.0 as it moves fully to NumPy-like coordinates. From a5245eff03a96a81b7c5ca2f54332d3669104021 Mon Sep 17 00:00:00 2001 From: jamesyan-git Date: Thu, 2 Nov 2023 10:40:23 +1100 Subject: [PATCH 4/5] change theme to PyData --- docs/conf.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 2d32dd0..40579e9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -111,7 +111,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'furo' +html_theme = 'pydata_sphinx_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/setup.cfg b/setup.cfg index e5cc4e3..b82045e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,7 @@ testing = napari[pyqt5]!=0.4.18 zarr docs = - furo + pydata-sphinx-theme myst-parser [options.package_data] affinder = napari.yaml From 860fb027fef8e8833346d68f09bf62af9553f542 Mon Sep 17 00:00:00 2001 From: jamesyan-git Date: Thu, 2 Nov 2023 10:48:41 +1100 Subject: [PATCH 5/5] fix documentation title --- docs/transform.rst | 2 +- transform.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/transform.rst b/docs/transform.rst index 3e8d8aa..02b3c6e 100644 --- a/docs/transform.rst +++ b/docs/transform.rst @@ -1,4 +1,4 @@ -Using skimage.transform.warp() +Using a saved transform matrix ============== .. include:: ../transform.md diff --git a/transform.md b/transform.md index 0108b2e..1ce927f 100644 --- a/transform.md +++ b/transform.md @@ -1,4 +1,3 @@ -# Using a saved transform matrix Lets say we have used affinder to regester out moving image with the reference image and now we wish to use the resulting transform matrix for downstream analysis. The guide describes the required changes to make the matrix compatable with scipy and skimage transformations.