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

how to use annotation with create_widget #68

Merged
merged 14 commits into from
Apr 15, 2024
42 changes: 42 additions & 0 deletions docs/howtos/extending/magicgui.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ The following napari types may be used as *parameter* type annotations in
{attr}`napari.types.ImageData` or {attr}`napari.types.LabelsData`
- {class}`napari.Viewer`

```{note}
If you're interested in getting synchronized information from the napari viewer into widgets without `@magicgui`, see the [`create_widget` example](#magicgui-widgets-create_widget) below.
```
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is necessary. I think we automatically update for all magicgui registered napari types; layer, layer subclasses and layer type data (e.g., napari.types.ImageData).
I am not sure if there would be any cases where you would want an input that is not in the above list.

If there is, we could mention that for non registered types you need to manually synchronize, see QWidget example for more...

cc @DragaDoncila

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have made the note more precise. I added it, because I was aware that one can easily create these synchronized dropdown lists of the available layers with @magicgui. But then, they were part of a bigger widget, and I was not able to figure out to create only these widgets which is possible with create_widget.


The consequence of each type annotation is described below:

#### Annotating as a `Layer` subclass
Expand Down Expand Up @@ -783,3 +787,41 @@ viewer.window.add_dock_widget(my_widg)

As above to turn this into a [plugin widget contribution](widgets-contribution-guide),
simply provide the class definition and add to the plugin manifest.

#### `magicgui.widgets.create_widget`

You might want to add a layer selection as [shown above](#parameter-annotations) into your highly customizable `QtWidgets.QWidget` that is always synchronized with the available {attr}`napari.types.ImageData` layers in your viewer. For this you can use the {func}`create_widget <magicgui.widgets.create_widget>` function as described in the following example.

To synchronize the information between the napari viewer (i.e. the available layers) the function `reset_choices` of the dropdown widget needs to be connected to the `inserted`, `removed` and `reordered` events of the `viewer.layers` as is done in the `__init__` function below:

```python
import napari
from magicgui.widgets import create_widget
from qtpy.QtWidgets import QWidget, QHBoxLayout

from napari.types import ImageData


class ExampleLayerListWidget(QWidget):
def __init__(self, viewer: "napari.viewer.Viewer"):
super().__init__()
self.viewer = viewer

# create new widget with create_widget and type annotation
self.layer_select = create_widget(annotation=ImageData)
layers_events = self.viewer.layers.events
layers_events.inserted.connect(self.layer_select.reset_choices)
layers_events.removed.connect(self.layer_select.reset_choices)
layers_events.reordered.connect(self.layer_select.reset_choices)
Comment on lines +799 to +802
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
layers_events = self.viewer.layers.events
layers_events.inserted.connect(self.layer_select.reset_choices)
layers_events.removed.connect(self.layer_select.reset_choices)
layers_events.reordered.connect(self.layer_select.reset_choices)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe, we can explain that part of napari magic, too, and show both possibilities with a marker that one of them is potentially deprecated?

Copy link
Contributor

Choose a reason for hiding this comment

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

We are working on nested Layers (Layer Groups). It may change a list of events. So then people will need to update their plugins if they do not depend on napari calling reset_choices.

Copy link
Contributor

@DragaDoncila DragaDoncila Apr 11, 2024

Choose a reason for hiding this comment

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

So then people will need to update their plugins if they do not depend on napari calling reset_choices.

@Czaki I'm not happy to enshrine this behaviour in our docs right now (see original comment here). It's very brittle and in my opinion, quite an overreach that could lead to surprising effects on widgets totally unrelated to the layerlist. I would strongly prefer to keep the example as it currently is, because it involves no magic and no reliance on napari internals. If we one day change the layerlist events then yes, people may have to update their plugins, but I have no issue with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could we explain the shortcut via reset_choices in the docs and also mention that there are plans that both viewer.layers.events and reset_choices might change in future, so that developers should check the corresponding source code once there plugins stop working as expected.

reset_choices https://github.com/napari/napari/blob/50b314f7ad2e7cd4ef21b5705b3c6b363c0857ed/napari/_qt/qt_main_window.py#L1093 shows all the relevant layers.events that would need to be connected individually.

viewer.layers.events currently is a instance subclassing https://github.com/napari/napari/blob/50b314f7ad2e7cd4ef21b5705b3c6b363c0857ed/napari/utils/events/containers/_selectable_list.py#L12

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the offer @gatonielif we decide to take you up on it or parts of it, I think expanding on the reset_choices magic should come in a separate PR. For now I think we are still internally deciding whether we want it in the docs, as you can see 😅, so I think this should go in as-is.

Regarding the future of the event connections: in my opinion, if we start worrying in the docs about all the things we are planning to deprecate, we will never document anything. 😂 I think in general the docs should be written with regard to the current public API, unless we really want to discourage use of a specific API. Which is not the case here.


self.setLayout(QHBoxLayout())
# add it to the layout
self.layout().addWidget(self.layer_select.native)

# Create a `viewer`
viewer = napari.Viewer()
# Instantiate your widget
my_widg = ExampleLayerListWidget(viewer)
# Add widget to `viewer`
viewer.window.add_dock_widget(my_widg)
```
Loading