Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for function widgets #1856

Merged
merged 22 commits into from
Dec 29, 2020
Merged

Conversation

sofroniewn
Copy link
Contributor

Description

Following on from #1816, particularly discussion here #1816 (comment) this PR adds the ability to provide functions that we turn into dock widgets using magic gui via an viewer.window.add_magic_function(function, magic, dock) API, where function is the function, magic is a dict that gets passed to the magicgui method and dock is a dict that gets passed to add_dock_widget method.

This PR adds two examples magic_image_arthimetic and magic_parameter_sweep based on respective examples of in the magicgui docs https://magicgui.readthedocs.io/en/latest/

I've made a couple issues on the magic gui around being able to use strings for types, see https://github.com/napari/magicgui/issues/46 and https://github.com/napari/magicgui/issues/47. I'm also curious about the ramifications of the API changes in https://github.com/napari/magicgui/pull/43, but we don't necessarily need to wait to release this until that is merged, though it might be nice.

Once we have this merged it will be much easier to continue with #1816 (or supercede it) as now a plugin will just be able to provide a list of tuples of inputs to add_magic_function.

I havn't added tests yet, but will do so. I think it would be nice to get some review and input on this first though - cc @tlambert03 @GenevieveBuckley @jni

Type of change

  • New feature (non-breaking change which adds functionality)

@sofroniewn sofroniewn added the feature New feature or request label Nov 12, 2020
@sofroniewn sofroniewn added this to the 0.4.1 milestone Nov 12, 2020
@codecov
Copy link

codecov bot commented Nov 12, 2020

Codecov Report

Merging #1856 (9eabb9c) into master (947bdbc) will increase coverage by 0.03%.
The diff coverage is 96.66%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1856      +/-   ##
==========================================
+ Coverage   79.78%   79.82%   +0.03%     
==========================================
  Files         387      388       +1     
  Lines       33215    33234      +19     
==========================================
+ Hits        26501    26529      +28     
+ Misses       6714     6705       -9     
Impacted Files Coverage Δ
napari/_tests/test_function_widgets.py 93.33% <93.33%> (ø)
napari/_qt/qt_main_window.py 80.05% <100.00%> (+0.28%) ⬆️
napari/utils/_magicgui.py 79.62% <100.00%> (+15.34%) ⬆️
napari/utils/_tests/test_magicgui.py 100.00% <100.00%> (ø)
napari/layers/base/base.py 93.02% <0.00%> (+0.21%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 947bdbc...9eabb9c. Read the comment docs.

Copy link
Contributor

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great to me @sofroniewn, exactly how I pictured it. I made a couple small implementation comments. but don't feel strongly on any of them

I've made a couple issues on the magic gui around being able to use strings for types, see napari/magicgui#46 and napari/magicgui#47.

totally agree. will do.

I'm also curious about the ramifications of the API changes in napari/magicgui#43,

The main change as it relates to this PR would be around the .Gui() syntax (which was always a bit weird). Briefly, the new magicgui decorator will return an instance of magicgui.FunctionGui, which itself is a magicgui widget (rather than simply adding a function attribute Gui that points to the widget). outside of napari, that object could be shown just with decorated_function.show() (or decorated_function.show(run=True) to spin up an event loop). To get at the actual QWidget, you would now use .native, much like how vispy does it. so, in this PR, you'd change it to

widget = magicgui(**magic)(function).native

but otherwise, it shouldn't change much here. To get a more complete idea about what is changing from the API perspective, have a look at how the "examples" are changing in https://github.com/napari/magicgui/pull/43/files

examples/magic_image_arithmetic.py Show resolved Hide resolved
examples/magic_parameter_sweep.py Show resolved Hide resolved
napari/_qt/qt_main_window.py Outdated Show resolved Hide resolved
napari/_qt/qt_main_window.py Outdated Show resolved Hide resolved
@tlambert03
Copy link
Contributor

oh... also, after https://github.com/napari/magicgui/pull/43/

viewer.layers.events.changed.connect(lambda x: widget.refresh_choices())
will become
viewer.layers.events.changed.connect(widget.reset_choices)

@sofroniewn
Copy link
Contributor Author

oh... also, after napari/magicgui#43

viewer.layers.events.changed.connect(lambda x: widget.refresh_choices())
will become
viewer.layers.events.changed.connect(widget.reset_choices)

will each widget always have a refresh_choices() or reset_choices on it? i.e. even if there is no layer argument passed? Does it do a global refresh of all enum / list etc type inputs?

@tlambert03
Copy link
Contributor

will each widget always have a refresh_choices() or reset_choices on it? i.e. even if there is no layer argument passed?

not all widgets, but all CategoricalWidgets ... (a widget will be categorical if it is an Enum or was provided with choices parameter.)

Does it do a global refresh of all enum / list etc type inputs?

it depends on how the categorical widget was created. In this case though, a parameter annotated as a napari Layer will have choices set to the get_layers function (in napari.utils._magicgui). So reset_choices will basically just call that function (which will grab layers in the layerlist)

@sofroniewn
Copy link
Contributor Author

not all widgets, but all CategoricalWidgets ... (a widget will be categorical if it is an Enum or was provided with choices parameter.)

Do I then need to add a check to see if the function/ attribute exists or is it always safe to try and connect in the way I'm doing?

@tlambert03
Copy link
Contributor

not all widgets, but all CategoricalWidgets ... (a widget will be categorical if it is an Enum or was provided with choices parameter.)

Do I then need to add a check to see if the function/ attribute exists or is it always safe to try and connect in the way I'm doing?

ohhh, right! good point :) yeah, I think we'll need to check. but actually, this concept probably belongs in napari.utils._magicgui more than it does here. in any case, let's just remember to revisit this after the new magicgui

@sofroniewn
Copy link
Contributor Author

ohhh, right! good point :) yeah, I think we'll need to check. but actually, this concept probably belongs in napari.utils._magicgui more than it does here. in any case, let's just remember to revisit this after the new magicgui

For now I'll check and then make an issue to follow-up

@tlambert03
Copy link
Contributor

For now I'll check and then make an issue to follow-up

until the magicgui API changes, all widgets will have a refresh_choices method
... and I believe it should be safe to call it even if none of the subwidgets have registered choices (it will just do nothing).

Copy link
Member

@jni jni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with almost everything @tlambert03 wrote. The broad strokes here though are super great, and I think @sofroniewn will know how to wrap this up, so I'm providing my approval pending the discussed changes.

@sofroniewn
Copy link
Contributor Author

I agree with almost everything @tlambert03 wrote. The broad strokes here though are super great, and I think @sofroniewn will know how to wrap this up, so I'm providing my approval pending the discussed changes.

Ok great sounds like we're pretty close. Out of curiosity @tlambert03 do you have guestimate on a timeline for how long it would take to finish https://github.com/napari/magicgui/pull/43? I just looked and we don't actually depend on magicgui yet - I need to add that to this PR - and if possible it might be nice to make our first dependency on a version after that PR has merged, but I don't think that's a requirement.

@tlambert03
Copy link
Contributor

do you have guestimate on a timeline for how long it would take to finish napari/magicgui#43?

the code is mostly done, (just need to add back labels). The main issues now are tests, and updated docs... and that's unfortunately why it's sat there for a while. but I can probably just live with imperfect test coverage for the first . probably a week or two of focused work? (question is when will the focused work come 😂)

@sofroniewn
Copy link
Contributor Author

the code is mostly done, (just need to add back labels). The main issues now are tests, and updated docs... and that's unfortunately why it's sat there for a while. but I can probably just live with imperfect test coverage for the first . probably a week or two of focused work? (question is when will the focused work come 😂)

ok sounds good, we can play it by ear!

@jni
Copy link
Member

jni commented Nov 17, 2020

Given https://github.com/napari/magicgui/pull/43 and how much that will change the API, I think we should probably hold off on this until 0.4.2, despite (or perhaps because of!) the massive community excitement about this. In short, I think many people would start using this immediately, and it would become a major backwards-compatibility headache. Let's take a couple of more weeks and get this right? (Or at least, a little more right. 😂)

@jni jni modified the milestones: 0.4.1, 0.4.2 Nov 17, 2020
@tlambert03
Copy link
Contributor

tlambert03 commented Nov 17, 2020

Let's take a couple of more weeks and get this right? (Or at least, a little more right. 😂)

yeah, I think that's a good idea. While the decorator syntax itself won't change too dramatically, the fact that accessing the widget with .Gui() will likely go away (provided everyone likes that?) is probably reason enough to wait. I promise to get on it soon... this week is really busy, but I will try make it a priority. (and wouldn't mind a couple extra reviews of napari/magicgui#43 ... even just casual perusals)

edit: jeez, I'm realizing now that even the original post there is a bit out of date. so if you want to maximize your review efforts, maybe hold off on that too for a week or so

@GenevieveBuckley
Copy link
Contributor

Let's take a couple of more weeks and get this right? (Or at least, a little more right. 😂)

Agreed

@sofroniewn sofroniewn modified the milestones: 0.4.2, 0.4.3 Nov 28, 2020
@tlambert03
Copy link
Contributor

tlambert03 commented Dec 26, 2020

I'm still having problems using strings instead of napari.layers.Image

ahh... this is because magicgui resolves the forward reference as needed, but doesn't actually replace the type annotation. It would be easy enough to fix on the magicgui side (see https://github.com/napari/magicgui/issues/65). Otherwise, we'd also need to resolve them in utils._magicgui, you could use the magicgui function for it like this:

def get_layers(...):
    if layer_type is None:
        gui, layer_type = gui.native, gui.annotation

    # resolve the ref
    if isinstance(layer_type, ForwardRef):
        from magicgui.type_map import _evaluate_forwardref
        layer_type = _evaluate_forwardref(layer_type)

@tlambert03
Copy link
Contributor

I think it's probably for the best to do this on the magicgui side, I made a PR: https://github.com/napari/magicgui/pull/66

Copy link
Contributor

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great @sofroniewn! The examples definitely make it look even easier now to get some gui goodness into napari. :)

I left some thoughts, but you can take them or leave them. Also, if you want, you can remove the comments from utils._magicgui.get_layers if we're going to depend on magicgui>=0.2.0

napari/_qt/qt_main_window.py Outdated Show resolved Hide resolved
napari/_qt/qt_main_window.py Outdated Show resolved Hide resolved
@tlambert03
Copy link
Contributor

also: if you want, I try to get napari/magicgui#63, napari/magicgui#64, and napari/magicgui#65 in and release 0.2.1 tomorrow and you can pin to that instead?

sofroniewn and others added 3 commits December 26, 2020 17:44
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
@sofroniewn
Copy link
Contributor Author

I've moved the auto-connection to the add_dock_widget which is nice as any magicgui added widget will now benefit from it. I did it by looking for reset_choices, let me know @tlambert03 if you have another way in mind (what I like about this is that it doesn't mean we have to import magicgui, but we could do different too, feel free to make a suggested change if you want.

If you want to make a commit to this PR that cleans up utils._magicgui now that we have > 0.2.0 that would be great too, might be safer that you do it rather than me!

also: if you want, I try to get napari/magicgui#63, napari/magicgui#64, and napari/magicgui#65 in and release 0.2.1 tomorrow and you can pin to that instead?

Sounds good, no rush

# Keep the dropdown menus in the widget in sync with the layer model
# if widget has a `reset_choices`, which is true for all magicgui
# widgets
self.qt_viewer.viewer.layers.events.connect(widget.reset_choices)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm forgetting at the moment... is layers.events the same as "any event" or did we need to do layers.events.inserted and layers.events.removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

layers.events will do all of them

sofroniewn and others added 2 commits December 26, 2020 18:36
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
@tlambert03
Copy link
Contributor

If you want to make a commit to this PR that cleans up utils._magicgui now that we have > 0.2.0 that would be great too, might be safer that you do it rather than me!

ok, I just cleaned up magicgui._utils.get_layers, and I extended your test a bit to make sure that viewer.layers does indeed stay in sync with the layer choices in the widget.

I figured out what was going wrong in #2044 and submitted fix in #2046.
On the magicgui side, I tweaked a few more things, so if you have time to peek at and approve napari/magicgui#67, napari/magicgui#68, napari/magicgui#69, I can release 0.2.1 and we can pin to that instead.

@sofroniewn
Copy link
Contributor Author

On the magicgui side, I tweaked a few more things, so if you have time to peek at and approve napari/magicgui#67, napari/magicgui#68, napari/magicgui#69, I can release 0.2.1 and we can pin to that instead.

I've approved all 3 PRs, they all look good to me. If you want to make a magicgui 0.2.1rc0 tomorrow I can give that a test before you make the actual release, and then once released I'll update this PR.

@tlambert03 tlambert03 mentioned this pull request Dec 29, 2020
1 task
@sofroniewn
Copy link
Contributor Author

Ok this has been updated to now depend on the latest magicgui 0.2.1 and fixed so all tests should be passing. I also added a comment about forward references and link to https://www.python.org/dev/peps/pep-0484/#forward-references in the examples. @tlambert03 can you look those over (occurs in both examples), feel free to directly edit PR. If you give this one final thumbs up I will then merge!! So exciting to get this in!!!

@tlambert03
Copy link
Contributor

@tlambert03 can you look those over (occurs in both examples)

good idea. I made a slight update to explain that ForwardRefs are mostly necessary if you don't want to import napari. all looks good to me i think! 🎉

@sofroniewn
Copy link
Contributor Author

Ok, great, will merge now!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants