Part 4: Creating a napari plugin
Overview
In addition to being a viewer accessible from Python, napari supports plugins
to add functionality to the viewer and to bundle custom functions into a nice
user interface.
In this tutorial, we will make a napari analysis plugin for the watershed
segmentation we implemented in the previous section.
The steps in making a napari plugin are as follows:
choose which manifest contribution(s) your plugin requires
create a repository using the napari cookiecutter template
implement your contributions
share your plugin with the community
In the following sections, we will work through steps (1) - (3). For step (4)
you can refer to the in depth plugin
tutorial,
or the instructions on
napari.org.
Choosing a contribution
A contribution is a list item in napari.yaml
(the manifest file) that napari
uses for each specific type of plugin. Each contribution conforms to a function
signature, i.e. the function linked to the contribution defines what napari
provides to the plugin (e.g., data and parameters) and what the plugin returns
to napari. napari is then able to use the functions pointed to in napari.yaml
to carry out the plugin tasks. The current categories of contributions are
described below. Please see the contribution
reference and
contribution guide for more
details. Many plugins declare multiple contributions to provide all of the
desired functionality.
reader: allows loading of specified data formats into napari layers
writer: this allows layer data to be written to disk in specified formats
sample data: allows developers to provide users with sample data with their plugin.
widget: allows custom Qt widgets (GUIs) to be added to napari, either from a magic_factory
widget, a plain function, or a subclass of QWidget
theme: allows customization of the entire napari viewer appearance e.g. light theme or dark theme
In this tutorial, we will create a widget to run the watershed segmentation
from previous part of this workshop.
Using the cookiecutter template to create your plugin directory
To make creating plugins easier, we provide a template that automatically
builds most of the infrastructure for your plugin, so you can focus on
implementing the details unique to your plugin. The template is provided using
a command line utility called
cookiecutter
. In the
following steps, you will build your plugin directory using the cookiecutter
template.
In this next step, we will use cookiecutter
to create a directory for our
plugin from the template. cookiecutter
will ask a series of questions that
that will customize the directory for your plugin. Once completed, a new
directory will be created in your current directory. It will come
pre-initialised with a git repository.
cookiecutter https://github.com/napari/cookiecutter-napari-plugin
You will be asked for some information to customize the setup of your plugin.
Each prompt gives the default value in square brackets ([]
). The questions
are explained below. Enter your answer after the prompt and press enter to
continue.
full_name [Napari Developer]
: enter your name here. Names entered here will
be listed as the authors of the plugin in the package metadata.
email [yourname@example.com]
: this email will be listed as the contact
information in the package metadata
github_username_or_organization [githubuser]
: if you have a github
username, you can enter it here. It will be used to generate a GitHub URL for
you after you enter a plugin name.
plugin_name [napari-foobar]
: enter the name you would like your plugin to
be called. spaces are not allowed and are often replaced with -
(e.g.,
napari-watershed
). This will be your PyPI package name (if you choose
to deploy your plugin).
Select github_repository_url:
this is used for plugin metadata and is not
required now. If you don’t plan to upload it to your github, select 2.
module_name [napari_foobar]
: this is the name of the module containing your
plugin code. Typically, this is the plugin name with the -
replaced with
_
(e.g., napari_watershed
).
display_name [napari FooBar]
: this name will show up in the napari viewer
and on the napari hub if you release your plugin. You should pick something
human readable and ideally indicative of what your plugin does. There are no
restrictions on the symbols in this name.
short_description [A simple plugin to use with napari]
: give a one sentence
description of your plugin. This will go into the readme.
include_reader_plugin [y]
: answer y
(for yes) if you would like a reader
contribution. We do not need a reader contribution for this tutorial, so
answer n
for no.
include_writer_plugin [y]
: answer y
(for yes) if you would like a writer
contribution. We do not need a writer contribution for this tutorial, so
answer n
for no.
include_sample_data_plugin [y]
: answer y
(for yes) if you would like a
sample data contribution. We do not need a sample data contribution for this
tutorial, so answer n
for no.
include_dock_widget_plugin [y]
: answer y
(for yes) if you would like a
widget contribution. We are implementing a widget contribution for this
tutorial, so answer y
for yes.
use_git_tags_for_versioning [n]
: we will not be covering setting plugin
versions in this tutorial, so enter n
for no.
install_precommit [n]
: we will not be covering precommit in this tutorial,
so enter n
for no.
Select license
: select the license you would like to use for your plugin.
The license sets the rules for how others can build upon and re-use your
plugin code. For more information on typical open source licenses,
choosealicense.com is a good primer. The
default choice is BSD-3,
which is common in the Scientific Python ecosystem.
After completing all of the questions, a directory will be created containing
your new napari plugin. You will be given instructions on how to upload the
initialized git repository to GitHub. By default, we will not be covering this
aspect in the tutorial, but please feel free to ask the teaching team if you
would like to give it a try. Your new plugin directory (assuming you called the
plugin napari-watershed
and the module napari_watershed
) will be
organized as follows (with some irrelevant files/folders omitted)
napari-watershed
├── .github
│ └── workflows
│ └── test_and_deploy.yml
├── LICENSE
├── MANIFEST.in
├── .napari
│ └── DESCRIPTION.md
├── pyproject.toml
├── README.md
├── setup.cfg
├── src
│ └── napari_watershed
│ ├── __init__.py
│ ├── napari.yaml
│ ├── _tests
│ │ ├── __init__.py
│ │ └── test_widget.py
│ └── _widget.py
└── tox.ini
See below for explanations about some of the most notable files, but do not
hesitate to reach out to the teaching team if you have questions about any of
the other files.
.github/workflows/test_and_deploy.yml
: this is a github
actions workflow that will
automatically run the tests and upload your plugin to pypi (thus making it
available through the built-in napari plugin browser. Please ask the teaching
team if you would like to learn how to set up your github repository to
support the workflow.
pyproject.toml
and setup.cfg
: these files allow your plugin to be built
as a package and installed by pip. the cookiecutter template has set
everything up in these files, so you are good to go!
the src/
folder contains all the python code for your plugin.
src/napari_watershed/_widget.py
: This file contains example
implementations for different widget contributions. This is where you will
add your points_watershed()
function.
the src/napari_watershed/napari.yaml
file declares commands and
contributions for each example widget in the _widget.py
file. Look at these
carefully and match up which command & contribution belong to what python
code in the _widget.py
file.
You have now set up the directory for your new plugin! You can explore the
directory and files with the file browser. In the next step, you will complete
your plugin by adding your points_watershed()
function to the _widget.py
file.
Implementing a function GUI
In this step, we will implement our points_watershed()
function as a plugin
contribution. First, we will add our function to the plugin
package. Then, we will add the type annotations to the function to so that
napari can infer the correct GUI elements to add to our plugin.
We want to edit the file src/<module_name>/_widget.py
. The file contains some
example code for using a qtpy
widget, a magicgui.magic_factory
widget, or even just a plain function
with type annotations. We don’t need to customize our widget today so we will
use only the plain function and can delete the rest. We can also rename the
function to points_watershed
.
We also need to edit napari.yaml
to remove the other widgets and rename our
function. Find the command
ID that corresponds to the function widget:
- command: napari-watershed.make_func_widget
autogenerate: true
display_name: Example Function Widget
Rename that to whatever you called your function, make sure to leave
autogenerate: true
, and update the display name. Napari will then take our
function and generate a widget for it automatically!
Now, you need to:
write a function points_watershed
, that takes in a NumPy array of
coordinates, a numpy array containing edge data, and a numpy array containing
a mask, and produces a segmentation. The annotations should be
napari.types.PointsData
, napari.types.ImageData
, and
napari.types.LabelsData
. The output annotation should be
napari.types.LayerDataTuple
— see the documentation for
this.
To write this function you can reuse code from the corresponding steps in the
segmentation chapter.
make sure that you put in all the required imports at the top of the file.
install your plugin into your environment, with python -m pip install -e .
at the root of your repository.
Testing/Installing your plugin
Go back to the previous lesson and, if it’s still running, restart the kernel.
Run it until you have your seed points, and then in the napari Viewer, click
the “Plugin” menu — you should see your plugin function appear. Click on it,
select the inputs, and check that the watershed result is produced and added to
the viewer!
Congratulations! You have made your first napari plugin!
Explore the other files generated by cookiecutter
Cookiecutter does a lot of work for us, but there is no magic — it’s good to
understand what it’s doing. One important thing is telling napari that it’s
made a plugin. It does this through Python entry
points,
which are annotated in the setup.cfg
file as follows:
[options.entry_points]
napari.manifest =
napari-watershed = napari_watershed:napari.yaml
Bonus exercises
If you have time, you can add more functions to your plugin:
edge filtering of the nuclei image to get the edges image
finding the centers of the nuclei using skimage.measure.peak_local_max
finding the mask using skimage.filters.threshold_li
and
scipy.ndimage.binary_fill_holes
You can then run the whole analysis entirely within napari! This can be a good
way to share any scientific Python code with collaborators who may not know how
to use Python.
Some more ideas:
add sample data to your plugin. To do so, you would need to implement the sample data contribution
add some tests to the _tests/test_widget.py
file.
upload your plugin to github
start your own plugin
consult with the teaching team about integrating napari into your workflow