Skip to content

Commit

Permalink
Tweak magicgui content
Browse files Browse the repository at this point in the history
  • Loading branch information
psobolewskiPhD committed Apr 26, 2024
1 parent d869916 commit 90446b2
Showing 1 changed file with 98 additions and 15 deletions.
113 changes: 98 additions & 15 deletions napari-workshops/notebooks/spot_detection_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,29 @@ high_passed_spots = gaussian_high_pass(spots, 2)
viewer.add_image(high_passed_spots, colormap="I Blue", blending="minimum")
```

```{code-cell} ipython3
nbscreenshot(viewer)
```

Now lets modify the function slightly, by providing type annotations and a docstring, to
leverage napari `magicgui` integration.

````{tip}
A brief note about type hints:
Type hints are not enforced at runtime, **but** they can still raise `NameError` exceptions if not defined or imported.
To avoid that, we are putting the napari types in quotes to make them "forward references", because we have
not imported them. Alternatively, we could have imported them. A third option is to import:
```python
from __future__ import annotations
```
This would permit us to drop the quotes from the type hints.
For more information, see the official Python documentation for:
[type hints in Python](https://peps.python.org/pep-0484/), [forward references](https://peps.python.org/pep-0484/#forward-references),and [annotations](https://peps.python.org/pep-0563/).
````

```{code-cell} ipython3
from magicgui import magicgui
Expand Down Expand Up @@ -97,42 +117,78 @@ def gaussian_high_pass(image: "napari.types.ImageData", sigma: float = 2)->"napa
```

We have our `magicgui` decorated function and we've annotated it with the napari types.
Now it is both a compound widget and a callable function. Let's add the widget to the
viewer.
Now, the object `gaussian_high_pass` is both a (compound) widget and a callable function.
Let's add it to the viewer.

```{code-cell} ipython3
high_pass_widget
viewer.window.add_dock_widget(gaussian_high_pass)
```

Hit the `Run` button and you will see that a new layer is added with the results of our
function! If you press it again, the data for that layer will be updated in place. So
```{code-cell} ipython3
nbscreenshot(viewer)
```

Notice that because we told `magicgui` that our function will use not just any numpy array, but
specifically `ImageData`—the data of an Image layer—and that it will also return that, `magicgui`
generated UI widgets for selecting an Image layer--if you add another layer type, it won't show
up in the dropdown!
Press the `Run` button and you will see that a new Image layer is added with the results of our
function—again thanks to autogeneration from `magicgui`.

```{code-cell} ipython3
# we'll call the widget to simulate clicking `Run`
gaussian_high_pass(viewer.layers['spots'].data)
```

Note that we are just returning `ImageData`, so there is no information passed about colormaps, blending, etc. If we want to specify that, we would need to annotate as [`LayerDataTuple`](https://napari.org/stable/guides/magicgui.html#returning-napari-types-layerdatatuple).
For now you will need to manually or programmatically set any colormap/blending settings. (Let's also hide the previous filtering output.)

```{code-cell} ipython3
viewer.layers[-1].blending = "minimum"
viewer.layers[-1].colormap = "I Blue"
viewer.layers['high_passed_spots'].visible = False
```

```{code-cell} ipython3
nbscreenshot(viewer)
```

However, if you press `Run` again, the data for that layer will be updated in place, so
you can change the `sigma` value and see the updated result.

```{tip}
Hover over the labels `image` and `sigma` -- the names of the parameters we passed to the function.
You should see tooltips with the docstring information! How cool is that?
```

Our function is also the widget, so we can get the value of the current setting:
Our `gaussian_high_pass` object *is the widget*, so we can easily get the value of the current setting:

```{code-cell} ipython3
gaussian_high_pass.sigma.value
```

Let's also check that the function is still working as expected -- type annotations are not
used by Python at runtime, so nothing should have changed.
At the same time, `gaussian_high_pass` remains a callable function. Let's call it normally, to check
that the function is still working as expected. Remember, type hints are not enforced by Python at runtime,
so nothing should have changed.

```{code-cell} ipython3
test_output = gaussian_high_pass(spots, 2)
test_output.shape
```

This means that if you have a script or module you can import the function and use it as normally
or use it as a widget in napari.
*or* use it as a widget in napari.

Let's make the the widget more dynamic, by giving `magicgui` some extra information.
Let's make the the widget more dynamic and user-friendly, by giving `magicgui` some extra information.
Let's ask for a slider for the `sigma` parameter and lets have the function be auto-called
when the slider is dragged.
when the slider is changed.

But first, lets remove the previous widget.

```{code-cell} ipython3
viewer.window.remove_dock_widget("all")
```

```{code-cell} ipython3
@magicgui(auto_call=True,
Expand Down Expand Up @@ -164,6 +220,10 @@ def gaussian_high_pass(image: "napari.types.ImageData", sigma: float = 2)->"napa
viewer.window.add_dock_widget(gaussian_high_pass)
```

```{code-cell} ipython3
nbscreenshot(viewer)
```

Now you can play with the slider until you get the effect you want in the GUI and then return the value:

```{code-cell} ipython3
Expand All @@ -173,11 +233,16 @@ gaussian_high_pass.sigma.value
Or you can *set the value*:

```{code-cell} ipython3
gaussian_high_pass.sigma.value = 5
gaussian_high_pass.sigma.value = 3
```

```{code-cell} ipython3
nbscreenshot(viewer)
```

Finally, lets make a widget for the whole workflow as a function. Now we are starting with
image layer data, but then we want a Points layer with points. So we will return a LayerDataTuple.
image layer (data), but then we want a Points layer with points. We could again return just the
layer data using `napari.types.PointsData`. But lets get a nicer Points layer instead, so we will return a LayerDataTuple.
If `detect_spots()` returns a `LayerDataTuple`, napari will add a *new layer* to
the viewer using the data in the `LayerDataTuple`. For more information on
the `LayerDataTuple` type, please see
Expand All @@ -187,15 +252,22 @@ If `detect_spots()` returns a `LayerDataTuple`, napari will add a *new layer* to
coordinates)
- `layer_metadata`: the display options for the layer stored as a
dictionary. Some options to consider: `symbol`, `size`, `face_color`
- `layer_type`: the name of the layer type as a string—in this case `'Points'`
- `layer_type`: the name of the layer type as a string—in this case `'Points'`
Also let's change the `image` argument type hint to `ImageLayer` so that we can access more
properties if we'd like or be able to more easily set the value programmatically.

```{code-cell} ipython3
# again lets remove the previous widget
viewer.window.remove_dock_widget("all")
```

```{code-cell} ipython3
import numpy as np
from skimage.feature import blob_log
@magicgui
def detect_spots(
image: "napari.types.ImageData",
image: "napari.layers.Image",
high_pass_sigma: float = 2,
spot_threshold: float = 0.01,
blob_sigma: float = 2
Expand Down Expand Up @@ -228,7 +300,7 @@ def detect_spots(
"""
# filter the image
filtered_spots = gaussian_high_pass(image, high_pass_sigma)
filtered_spots = gaussian_high_pass(image.data, high_pass_sigma)
# detect the spots on the filtered image
blobs_log = blob_log(
Expand All @@ -250,3 +322,14 @@ def detect_spots(
```{code-cell} ipython3
viewer.window.add_dock_widget(detect_spots)
```

```{code-cell} ipython3
# let's call the widget/function to simulate pressing run
detect_spots(viewer.layers['spots'])
```

```{code-cell} ipython3
# lets set the dropdown value for the screenshot
detect_spots.image.value = viewer.layers['spots']
nbscreenshot(viewer)
```

0 comments on commit 90446b2

Please sign in to comment.