GithubHelp home page GithubHelp logo

jni / affinder Goto Github PK

View Code? Open in Web Editor NEW
16.0 16.0 13.0 330 KB

Quickly find the affine matrix mapping one image to another using manual correspondence points annotation

Home Page: https://jni.github.io/affinder/

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%

affinder's People

Contributors

andreasmarnold avatar chili-chiu avatar dragadoncila avatar genevievebuckley avatar jamesyan-git avatar jni avatar thanushipeiris avatar tlambert03 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

affinder's Issues

Type error: missing a required argument 'layers'

Just pulled the latest master branches of napari, magicgui and affinder and getting this Error when clicking the start button:

image

(napari) hilsenst@itservices-XPS-15-9500:~/Github/napari$ affinder 
ERROR:root:Unhandled exception:
Traceback (most recent call last):
  File "/home/hilsenst/Github/magicgui/magicgui/widgets/_bases/value_widget.py", line 44, in <lambda>
    lambda *x: self.changed(value=x[0] if x else None)
  File "/home/hilsenst/Github/magicgui/magicgui/events.py", line 603, in __call__
    self._invoke_callback(cb, event)
  File "/home/hilsenst/Github/magicgui/magicgui/events.py", line 625, in _invoke_callback
    cb_event=(cb, event),
  File "/home/hilsenst/Github/magicgui/magicgui/events.py", line 619, in _invoke_callback
    cb(event)
  File "/home/hilsenst/Github/magicgui/magicgui/widgets/_function_gui.py", line 175, in _disable_button_and_call
    self.__call__()
  File "/home/hilsenst/Github/magicgui/magicgui/widgets/_function_gui.py", line 249, in __call__
    value = self._function(*bound.args, **bound.kwargs)
  File "/home/hilsenst/Github/affinder/affinder/affinder.py", line 147, in start_affinder
    close_affinder()
  File "/home/hilsenst/Github/magicgui/magicgui/widgets/_function_gui.py", line 241, in __call__
    raise TypeError(msg) from None
TypeError: missing a required argument: 'layers' in call to 'close_affinder(layers, callback)'.
To avoid this error, you can bind a value or callback to the parameter:

    close_affinder.layers.bind(value)

Or use the 'bind' option in the magicgui decorator:

    @magicgui(layers={'bind': value})
    def close_affinder(layers, callback): ...

ERROR:root:Unhandled exception:

Add ability to automatically refine alignment once approximate alignment is found

Many image alignment methods fail because they rely on global optimisation of a non-convex metric. With affinder, we can let the user quickly provide a close-enough estimate of initial alignment, after which automatic alignment methods have a better chance of being accurate.

Therefore, it would be nice to add automatic registration methods to affinder that users can activate after an initial alignment.

Extend affinder to 3D

Hello @jni
I recently installed Napari in the context of the NEUBIAS Pasteur course (30/05-02/06/2023).
We got a very nice introduction and tutorial on creating our own Napari plugins by Robert Haase and Kevin Yamauchi.
In my case, I am really interested in using Napari for a project about 3D microscopy image registration.
I discovered your plugin "affinder" and like it a lot. However, as far as I understand, so far it is only available for 2D, while I would definitely be interested in a 3D version.
Thus, I have the following questions:

  • Do you know if there is another equivalent plugin that works in 3D?
  • Is there any plan to extend affinder to the 3D? By you or other contributors?
  • Do you know if someone is by chance already working on it?
    In case of NO to all three questions above, I might consider to work on it with another colleague of mine.
    Do you have any guidelines and recommandations ?

Thank you very much by advance for your reply.

Best regards,

Closing the widget should close affinder

If a user accidentally closes the affinder widget, the callbacks on the layers will stick around and it will be hard to remove them. It would be good if closing the widget was equivalent to stopping affinder. I don't know yet whether there is an event one can catch when the widget is closed, but there were some discussions about this either in the napari repo or in the Zulip.

Limit "Select File" button for output to .txt files

It turns out that (unexperienced) users tend to mistake the "Select File" button for a way to load images. This easily leads to inadvertently overwriting image files with the affine matrix.

Could solve or at least mitigate this issue by renaming the button, limiting the selection to .txt files or both.

why not make the description the readme?

The description page @DragaDoncila made here is lovely. why not just make it the readme? There's nothing in there that isn't generally applicable to the plugin, and it would make for a much nicer presentation on github, pypi, etc...

multi-time-point multi-channel issues

Hi @jni,

just a brief placeholder for something that I noticed today.

When using a dataset with channel order (c,y,x) and 3 channels, napari loads this as if it was a time-series or Z-stack.
After clicking 3 points with affinder I noticed that it didn't switch to the other layer. After clicking a 4th point it crashed.
Looking at the error message I interpret the behaviour as affinder thinking it needs to align volumes or some other dimension misinterpretation.

As some users may want to register a single frame to a time series, or several multi-channel time-series on top of each other
the interpretation of the dimension becomes important. I heard there is a guardian of dimensions keeping an eye on such matters.

Can try and create a more detailed report in the coming days, but I guess as affinder is still WIP maybe this is already on your radar.

Saving control points

If not done already:

Not only save the transform file, but also the control points specified by the user, for later (re-)processing (e.g., using a different transformation model).

Need ability to undo point adding and effect of callbacks

During a call with @andreasmarnold, we discussed what else we needed in affinder before publishing. The big issue he identified is that it is very hard to recover from misclicks: if you inadvertently add an incorrect point (or for example, put down the first three points in the wrong order in the first layer switch), the transform is very distorted and it becomes very hard to recover, even if deleting the points.

This could be fixed by an affinder-level history, where each point added and resulting transform is recorded. When hitting undo, we would switch back to the previous state. Even for long click sequences this would be a small amount of data so not too onerous to keep track of.

Transformation matrix with skimage.transform.warp

Thank you for this great plugin!

I would like to reuse the saved transformation matrix from Affinder but after using it with skimage.transform.warp I get slightly different results. Could you help me figure out what I'm missing?

Napari 0.4.15
Affinder 0.2.2
skimage 0.19.2

Automatically set zoom to fit layer when switching layers

When switching layers before the affine transform has been estimated, the zoom/position shift can be quite jarring, even making finding the layer difficult. The behaviour should instead be as follows:

  • upon starting: set the zoom to fit the reference layer
  • when enough points (ndim+1) have been selected in the reference: set the zoom to fit the moving layer
  • when switching back to the reference layer, alternate between:
    • set the zoom to fit the reference layer
    • don't change the zoom: the next point should be in approximately the same position in the moving layer

make napari plugin

Currently affinder is a standalone function. It should also provide a napari dock widget hook implementation so that napari users can install it.

Remember settings

... although I'm not sure if there is a functional settings framework in place for napari already?

apply_affine does not correctly transform images for scales<1

When trying to transform images that have a scale <1, the resulting transformed image is wrong.
Is it possible that this is a result of defining the affine in relation to the reference image in apply_tf (line 66)?

Here's an example to quickly reproduce the problem. However, it will be necessary to use the plugin.

import napari
from skimage import transform
from skimage.data import astronaut

# Create reference image
reference_image = astronaut()
# Create moving image by resizing reference image and rotating it by 90 degrees
moving_image = transform.rotate(reference_image, 90, resize=True)
moving_image = transform.resize(moving_image, (1024, 1024), order=2)

# open viewer and add images with arbitrary "nonsense" scale < 1
viewer = napari.Viewer()
viewer.add_image(reference_image, name='ref', scale=(0.16, 0.16))
viewer.add_image(moving_image, name='mov', scale=(0.02, 0.02))

# Use affinder to find affine transformation and then apply it to the moving image

# The transformed image contains nothing of the original image but is the right shape
print(f"shape of transformed image: {viewer.layers[-1].data.shape}")
print(f"scale of transformed image: {viewer.layers[-1].scale}")

Show status

Add a status label, such as "Select control points in layer X"

add n-dimensional support

scikit-image/scikit-image#5188 allows estimation of n-dimensional affine transforms. We can either vendor that or wait for the next skimage release, but either way, the affinder code should be n-dimensional. This means we need ndim+1 points, rather than the currently-hardcoded "3", to estimate the initial transform.

Allow alignment of mixed dimensionality layers

Sometimes we want to align a projection to its 3D volume, or a 2D shape with measurements that change over time, e.g. the segmentation of a neuron aligned with a calcium imaging time series. What to do is different in each case (treat the 2D image as a very thin 3D slice, or treat it as "limited" to the two right-aligned dimensions), and this could be controlled by an option switch.

Add documentation URL to repo description

This is different to #67 (but it is related).

@jni you can add a URL to the right hand sidebar description on your repo page. Can you add the documentation url there? https://jni.github.io/affinder/

You have to look for the little gear icon in the right hand sidebar, next to the "About" repo info. You click it to edit the repo description and url.
Screen Shot 2023-04-20 at 10 04 21 am

Then there's a spot you can add the URL

Screen Shot 2023-04-20 at 10 04 38 am

Add ability to use RANSAC for robust registration with many points

Currently, affinder uses least squares to estimate the transformation between all user selected keypoints. However, it might not always be the best choice, as some user selected points may be more accurately placed than others (due e.g. to the sharpness of image features in different parts of the image). Additionally, users may want to use pre-computed points (see #11, for example), which could come from automated methods). Using RANSAC to match points will increase the reliability of found alignments.

Task: Allow aligning non-image layers

Shapes layers have feelings affines too!

Specifically, I worked with someone who had a 2D+t image layer and a 2D shapes layer (see also #41), and it was not possible to use affinder to align them.

Since all napari layers natively support affine transforms, we should be able to align any layer with any other using affinder.

Saving functionality

Currently there's no built-in way to save the found affine transforms. We should find a way to set saving options within the plugin.

wheel building broken?

in testing out npe2 convert, I realized that python setup.py bdist_wheel doesn't seem to be working here. It's not including the affinder module (indeed, the wheel on pypi lacks the actual module) https://pypi.org/project/affinder/#files

I think you need to add packages = find: to your [options] (see for example)

[options]
packages = find:
...

fortunately, this is in the cookiecutter! ๐Ÿ˜…

Add UI to pre-load existing transforms

Now that we can save transforms, it might be useful to use them as a new starting point, or simply to use affinder to view the transformed data after a reboot.

refresh available layers in dropdown

If a user starts affinder and then adds a layer, that layer is not updated in the dropdown list of available layers. It works when the affinder window is restarted but ideally the layers would refresh automatically.

Crosshairs

This is really a napari issue, but I find it really hard to place the markers pixel-perfectly accurate due to the points layer cursor.
I could zoom in a lot more, but that makes the overall workflow more cumbersome as I need to constantly zoom in and zoom out again as to quickly navigate without losing the overall overview.

I think with a one line-thine translucent cross hair that is fully transparent at the centre pixel one can place points more accurately.
The same is actually true for the points themselves, they are rather large.

Again, I realize these are all napari issues but none of these have bothered me other than in this particular context of affinder.

Starting affinder without layers causes Napari to crash

When opening the "Start affinder" widgets without any active layers in the viewer:

import napari


viewer = napari.Viewer()

qtwidget, widget = viewer.window.add_plugin_dock_widget(
        'affinder', 'Start affinder'
        )

napari.run()

I get this error:

Traceback (most recent call last):
  File "C:\Users\arnoldam\custom_code\affinder\examples\error-example.py", line 8, in <module>
    qtwidget, widget = viewer.window.add_plugin_dock_widget(
  File "C:\Users\arnoldam\miniconda\envs\napari-main\lib\site-packages\napari\_qt\qt_main_window.py", line 811, in add_plugin_dock_widget
    wdg = _instantiate_dock_widget(
  File "C:\Users\arnoldam\miniconda\envs\napari-main\lib\site-packages\napari\_qt\qt_main_window.py", line 1465, in _instantiate_dock_widget
    return wdg_cls(**kwargs)
  File "C:\Users\arnoldam\miniconda\envs\napari-main\lib\site-packages\magicgui\type_map\_magicgui.py", line 487, in __call__
    self._widget_init(widget)
  File "C:\Users\arnoldam\custom_code\affinder\src\affinder\affinder.py", line 120, in _on_affinder_main_init
    _update_unique_choices(widget.moving, widget.reference.current_choice)
  File "C:\Users\arnoldam\custom_code\affinder\src\affinder\affinder.py", line 104, in _update_unique_choices
    index = choice_names.index(choice_name)
ValueError: '' is not in list

Process finished with exit code 1

Don't use a magicgui widget as a global store for attributes

Somehow the code evolved to use a magicgui function not for a gui but for its ability to have bound variables, here:

# make a bindable function to shut things down
@magicgui
def close_affinder(layers, callback):
for layer in layers:
layer.events.data.disconnect(callback)
layer.mode = 'pan_zoom'

and here:

close_affinder.layers.bind(points_layers)
close_affinder.callback.bind(callback)

This is generally a gross abuse of magicgui and the Python programming language. ๐Ÿ˜‚ Since the main widget is stateful, the correct thing to do is to make a class subclassing FunctionGui.

allow chaining of transforms

Currently, affinder only works if both layers start out with the identity matrix as their .affine. We should instead make sure affine transforms can be composed ad-infinitum.

Widget idea: table showing current point coordinates and residuals for each point

(Related: #21)

During a pair session today @andreasmarnold came up with what I think is a really good idea: during alignment, have a table widget showing all the points added so far in each layer, together with the associated residuals. Then, one can go back and remove high-residual points from the table, which may have been added less precisely than others. This is easier than manually going through points, especially because the conditions on the "move layers" callback (which alternately bring the reference and moving images and points to the front) are not very sophisticated, and can get weirdly out of sync if you start removing points in the "wrong" order. By removing points in corresponding pairs through the table, we circumvent this issue altogether.

The magicgui table widget might be usable here. It might be a matter of:

  • Adding a(n initially empty) table as a return value from the start_affinder function
  • Add a callback to add rows to the table as points get added
  • Add a callback for when row values get deleted to actually delete the whole row and the corresponding points in the points layers

The last bit is a bit tricky because by default, selecting a row in a magicgui table and pressing backspace clears the row values, rather than deleting the row. We'll have to look at the internal representation of the table to see how easy it is to delete a row "in place". I also expect that people will expect that editing the values in the table actually edits the point coordinates, but let's call that a stretch goal for now. ๐Ÿ˜‚

CC @tlambert03 in case you have some comments about that proposed implementation / the Table widget API. ๐Ÿ™

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.