Skip to content

Commit

Permalink
merging from upstream main
Browse files Browse the repository at this point in the history
  • Loading branch information
thanushipeiris committed Nov 24, 2023
2 parents e1b4417 + e9bd67c commit 58987df
Show file tree
Hide file tree
Showing 15 changed files with 319 additions and 151 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U setuptools setuptools_scm wheel twine
pip install -U setuptools setuptools_scm wheel twine build
- name: Build and publish
env:
TWINE_USERNAME: __token__
Expand Down
File renamed without changes.
99 changes: 0 additions & 99 deletions .napari/DESCRIPTION.md

This file was deleted.

124 changes: 83 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,99 @@
# affinder
# Description

[![License](https://img.shields.io/pypi/l/affinder.svg?color=green)](https://github.com/napari/affinder/raw/main/LICENSE)
[![PyPI](https://img.shields.io/pypi/v/affinder.svg?color=green)](https://pypi.org/project/affinder)
[![Python Version](https://img.shields.io/pypi/pyversions/affinder.svg?color=green)](https://python.org)
[![tests](https://github.com/jni/affinder/workflows/tests/badge.svg)](https://github.com/jni/affinder/actions)
[![codecov](https://codecov.io/gh/jni/affinder/branch/main/graph/badge.svg)](https://codecov.io/gh/jni/affinder)
This GUI plugin allows you to quickly find the affine matrix mapping
one image to another using manual correspondence points annotation.

Quickly find the affine matrix mapping one image to another using manual correspondence points annotation
More simply, this plugin allows you to select corresponding points
on an image, and a second image you wish to transform. It computes
the requisite transformation matrix using Affine Transform, Euclidean Transform,
or Similarity Transform, and performs this transformation on the
moving image, aligning it to the reference image.

----------------------------------
https://user-images.githubusercontent.com/17995243/120086403-f1d0b300-c121-11eb-8000-a44a2ac54339.mp4

This [napari] plugin was generated with [Cookiecutter] using with [@napari]'s [cookiecutter-napari-plugin] template.

<!--
Don't miss the full getting started guide to set up your new package:
https://github.com/napari/cookiecutter-napari-plugin#getting-started
# Who is This For?

and review the napari docs for plugin developers:
https://napari.org/docs/plugins/index.html
-->
This is a simple plugin which can be used on any 2D images, provided
they can be loaded as layers into napari. The images need not be the same
file format and this plugin also works with labels layers.

## Installation
No prior understanding of the transformation methods is required, as
they perform in the background based on the reference points selected.

You can install `affinder` via [pip]:
# How to Guide

pip install affinder
You will need a combination of two or more 2D image and/or labels layers
loaded into napari. Once you have installed affinder, you can find it in
the dock widgets menu.

## Contributing
![Affinder widget in the Plugins->Add Dock Widget menu](https://i.imgur.com/w7MCXQy.png)

Contributions are very welcome. Tests can be run with [tox], please ensure
the coverage at least stays the same before you submit a pull request.
The first two dropdown boxes will be populated with the layers currently
loaded into napari. Select a layer to use as reference, and another to
transform.

## License
![Dropdowns allow you to select the reference and moving layers](https://i.imgur.com/Tdbm1sX.png)

Distributed under the terms of the [BSD-3] license,
"affinder" is free and open source software
Next, you can select the transformation model to use (affine is selected by default
and is the least rigid transformation of those available). See [below](#transformation-models) for a
description of the different models.

## Issues
Finally, you can optionally select a path to a text file for saving out the
resulting transformation matrix.

If you encounter any problems, please [file an issue] along with a detailed description.
When you click Start, affinder will add two points layers to napari.
The plugin will also bring your reference image in focus, and its associated points
layer. You can then start adding reference points by clicking on your image.

[napari]: https://github.com/napari/napari
[Cookiecutter]: https://github.com/audreyr/cookiecutter
[@napari]: https://github.com/napari
[MIT]: http://opensource.org/licenses/MIT
[BSD-3]: http://opensource.org/licenses/BSD-3-Clause
[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt
[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt
[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt
[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin
[file an issue]: https://github.com/jni/affinder/issues
[napari]: https://github.com/napari/napari
[tox]: https://tox.readthedocs.io/en/latest/
[pip]: https://pypi.org/project/pip/
[PyPI]: https://pypi.org/
![Adding reference points to layer](https://i.imgur.com/WPzNtyy.png)

Once three points are added, affinder will switch focus to the moving image,
and you should then proceed to select the corresponding three points.

![Adding corresponding points to newly focused layer](https://i.imgur.com/JVZCvmp.png)

affinder will immediately transform the moving image to align the points you've
selected when you add your third corresponding point to your moving image.

![The moving image is transformed once three points are added](https://i.imgur.com/NTne9fj.png)

From there, you can continue iteratively adding points until you
are happy with the alignment. Affinder will switch focus between
reference and moving image with each point.

Click Finish to exit affinder.

## Transformation Models

There are three transformation models available for use with affinder.
They are listed here in order of increasing rigidity in the types of
transforms they will allow. The eponymous Affine Transform is the
least rigid and is the default choice.

- [**Affine Transform**](https://en.wikipedia.org/wiki/Affine_transformation):
the least rigid transformation, it preserves
lines and parallelism, but not necessarily distance and angles. Translation,
scaling, similarity, reflection, rotation and shearing are all valid
affine transformations.

- [**Similarity Transform**](https://en.wikipedia.org/wiki/Similarity_(geometry)):
this is a "shape preserving" transformation, producing objects which are
geometrically similar. Translation, rotation, reflection and uniform scaling are
valid similarity transforms. Shearing is not.

- [**Euclidean Transform**](https://en.wikipedia.org/wiki/Rigid_transformation):
Also known as a rigid transformation, this transform preserves the Euclidean
distance between each pair of points on the image. This includes rotation,
translation and reflection but not scaling or shearing.

# Getting Help

If you find a bug with affinder, or would like support with using it, please raise an
issue on the [GitHub repository](https://github.com/jni/affinder).

# How to Cite

Many plugins may be used in the course of published (or publishable) research, as well as
during conference talks and other public facing events. If you'd like to be cited in
a particular format, or have a DOI you'd like used, you should provide that information here.
6 changes: 3 additions & 3 deletions docs/replace_description_text.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
for ../.napari/DESCRIPTION.md
find URLs ending in .mp4 and replace URL wrapped in HTML video tag
for README.md
find URLs ending in .mp4 and replace it with URL wrapped in HTML video tag.
"""

replacement_url = '<video width="640" height="480" controls>\n\
Expand All @@ -9,7 +9,7 @@
</video>\n'
new_text = ""

with open(".napari/DESCRIPTION.md", 'r+') as desc_file:
with open("README.md", 'r+') as desc_file:
for line in desc_file:
if line.strip().startswith("https://user-images.githubusercontent") and line.strip().endswith(".mp4"):
line = replacement_url.format(line.strip())
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Using affinder
==============

.. include:: ../.napari/DESCRIPTION.md
.. include:: ../README.md
:parser: myst_parser.sphinx_
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ napari.manifest =
[options.extras_require]
testing =
coverage
pydantic<2
pytest
pytest-cov
pytest-qt
scikit-image[data]
napari[pyqt5]
napari[pyqt5]!=0.4.18
pygments!=2.16
zarr
docs =
furo
myst-parser
Expand Down
3 changes: 3 additions & 0 deletions src/affinder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
__version__ = "unknown"

from .affinder import start_affinder
from .copy_tf import copy_affine
from .load_tf import load_affine
from .apply_tf import apply_affine
56 changes: 55 additions & 1 deletion src/affinder/_tests/test_affinder.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from affinder import start_affinder
from affinder import start_affinder, copy_affine, apply_affine, load_affine
from affinder.affinder import AffineTransformChoices
from skimage import data, transform
import numpy as np
Expand All @@ -7,6 +7,7 @@
import napari
import pytest
from copy import copy
from scipy import ndimage as ndi

nuclei3D_pts = np.array([[30., 68.47649186, 67.08770344],
[30., 85.14195298, 51.81103074],
Expand Down Expand Up @@ -221,3 +222,56 @@ def test_ensure_different_layers(make_napari_viewer):
assert widget.reference.value != widget.moving.value
widget.reference.value = widget.moving.value
assert widget.reference.value != widget.moving.value


def test_copy_affine():
layer0 = napari.layers.Image(np.random.random((5, 5)))
layer1 = napari.layers.Image(np.random.random((5, 5)))
layer0.affine = np.array([[0.9, 0.1, 5], [0.4, 0.2, 9], [0, 0, 1]])

widget = copy_affine()
widget(layer0, layer1)
np.testing.assert_allclose(layer0.affine, layer1.affine)


def test_apply_affine():
ref_im = np.random.random((5, 5))
mov_im = ndi.zoom(ref_im, 2, order=0)

ref_layer = napari.layers.Image(ref_im)
mov_layer = napari.layers.Image(mov_im)
mov_layer.affine = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]])

widget = apply_affine()
res_layer = widget(ref_layer, mov_layer)

np.testing.assert_allclose(res_layer[0], ref_im)


def test_apply_affine_nonimage():
ref_im = np.random.random((5, 5))
mov_pts = np.random.random((5, 2))

ref_layer = napari.layers.Image(ref_im)
mov_layer = napari.layers.Points(mov_pts)
mov_layer.affine = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]])

widget = apply_affine()
with pytest.raises(NotImplementedError):
widget(ref_layer, mov_layer)


def test_load_affine(tmp_path):
affile = tmp_path / 'test_affine.txt'
affine = np.array([[2, 0, 5], [0, 2, 5], [0, 0, 1]])
np.savetxt(affile, affine, delimiter=',')

layer = napari.layers.Image(np.random.random((5, 5)))

widget = load_affine()
widget(layer, affile)

np.testing.assert_allclose(
layer.affine, affine
)

7 changes: 5 additions & 2 deletions src/affinder/affinder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Optional
import napari
from napari.layers import Image, Labels, Shapes, Points, Vectors
Expand Down Expand Up @@ -30,8 +31,10 @@ def reset_view(viewer: 'napari.Viewer', layer: 'napari.layers.Layer'):
size = extent[1] - extent[0]
center = extent[0] + size/2
viewer.camera.center = center
viewer.camera.zoom = np.min(viewer._canvas_size
) / np.max(size) ##deprecation
with warnings.catch_warnings():
warnings.simplefilter('ignore')
canvas_size = viewer._canvas_size
viewer.camera.zoom = np.min(canvas_size) / np.max(size)


@tz.curry
Expand Down
Loading

0 comments on commit 58987df

Please sign in to comment.