Creating a napari plugin
Contents
Creating a napari plugin#
Overview#
In this tutorial, we will make a napari analysis plugin from the detect_spots()
function we wrote in the first part of this practical session. The primary steps in making a napari plugin are as follows:
choose which hook specification(s) your plugin requires
create your repository using the napari cookiecutter template
implement the hookspec(s)
share your plugin with the community
In the following sections, we will work through steps (1) - (3) and we discussed the process for (4) during the lecture.
Choosing a hook specification#
A hook specification or “hookspec” is a definition for a function that napari uses for a specific type of plugin. In other words, the hook specification defines what napari provides to napari (e.g., data and parameters) and what the plugin returns to napari. napari is then able to use the functions meeting the hook specification to carry out the plugin tasks. The current categories of napari hook specifications are described below. Please see the hook specification reference for more details. Many plugins will implement multiple hookspecs to provide all of the required 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.
dock widget: allows custom Qt widgets (GUIs) to be added to napari
function: the developer provides a function and napari automatically generates the GUI to both set the inputs parameters for the function and run the function.
In this tutorial, we will create a spot detection plugin by implementing the function hookspec with the spot detection function (detect_spots()
) we created in the first part of this practical session.
Implementing a function hookspec#
In this step, we will implement our detect_spots()
function in the function hookspec. First, we will add our spot detection 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.
To edit your plugin source code, open an integrated development environment (VSCode is a good, free option) or text editor.
In VSCode, open the directory you created with
cookiecutter
in the section above.From the “File” menu, select “Open…”
Navigate to and select the directory you created with
cookiecutter
(~/Documents/napari-spot-detector
if you called your pluginnapari-spot-detector)
.
You should now see your plugin directory in the “Explorer” pane in the left hand side of the window. You can double click on folders to expand them and files to open them in the editor.
Open the
<module_name>/_function.py
file using VSCode by double clicking on it in the “Explorer” pane.You will see that it has already been populated with a few code blocks by cookiecutter.
At the top, you see the imports. You can leave unchanged for now.
from typing import TYPE_CHECKING from enum import Enum import numpy as np from napari_plugin_engine import napari_hook_implementation if TYPE_CHECKING: import napari
Next, you see the function hookspec. We will modify this to return our
detect_spots
function.
# This is the actual plugin function, where we export our function # (The functions themselves are defined below) @napari_hook_implementation def napari_experimental_provide_function(): # we can return a single function # or a tuple of (function, magicgui_options) # or a list of multiple functions with or without options, as shown here: return [threshold, image_arithmetic]
Finally, you see two example functions. We will replace these with our
detect_spots()
function.
Delete the example functions. You can delete everything below the comment
# 1. First example,...
.Copy the
gaussian_high_pass()
anddetect_spots()
functions from your notebook from the first part of the tutorial and paste it where the example functions were (the ones you deleted in the previous step).Next, we need to modify
detect_spots()
to return the necessary layer data so that napari can create a new Points layer with our detected spots. Ifdetect_spots()
returns aLayerDataTuple
, napari will add a new layer to the viewer using the data in theLayerDataTuple
. For more information on theLayerDataTuple
type, please see the lecture slides or the docs.The layer data tuple should be:
(layer_data, layer_options, layer_type)
layer_data
: the data to be displayed in the new layer (i.e., the points coordinates)layer_options
: the display options for the layer stored as a dictionary. Some options to consider:symbol
,size
layer_type
: the name of the layer type as a string (i.e.,'Points'
)
Add type annotations to the function parameters (inputs). Napari (via magicgui) will infer the required GUI elements from the type annotations. We have to add annotations to both the parameters (i.e., inputs to the function) and the
Annotate the Return type as
"napari.types.LayerDataTuple"
.Add the required imports for the
scipy.ndimage
module andscikit-image
blob_log()
function to the top of the file.from scipy import ndimage as ndi
from skimage.feature import blob_log
_function.py solution#
See below for an example implementation of the _function.py file.
"""
This module is an example of a barebones function plugin for napari
It implements the ``napari_experimental_provide_function`` hook specification.
see: https://napari.org/docs/dev/plugins/hook_specifications.html
"""
from typing import TYPE_CHECKING
import numpy as np
from napari_plugin_engine import napari_hook_implementation
from scipy import ndimage as ndi
from skimage.feature import blob_log
if TYPE_CHECKING:
import napari
# This is the actual plugin function, where we export our function
# (The functions themselves are defined below)
@napari_hook_implementation
def napari_experimental_provide_function():
# we can return a single function
# or a tuple of (function, magicgui_options)
# or a list of multiple functions with or without options, as shown here:
return detect_spots
def gaussian_high_pass(image: np.ndarray, sigma: float = 2):
"""Apply a gaussian high pass filter to an image.
Parameters
----------
image : np.ndarray
The image to be filtered.
sigma : float
The sigma (width) of the gaussian filter to be applied.
The default value is 2.
Returns
-------
high_passed_im : np.ndarray
The image with the high pass filter applied
"""
low_pass = ndi.gaussian_filter(image, sigma)
high_passed_im = image - low_pass
return high_passed_im
def detect_spots(
image: "napari.types.ImageData",
high_pass_sigma: float = 2,
spot_threshold: float = 0.01,
blob_sigma: float = 2
) -> "napari.types.LayerDataTuple":
"""Apply a gaussian high pass filter to an image.
Parameters
----------
image : napari.types.ImageData
The image in which to detect the spots.
high_pass_sigma : float
The sigma (width) of the gaussian filter to be applied.
The default value is 2.
spot_threshold : float
The threshold to be passed to the blob detector.
The default value is 0.01.
blob_sigma: float
The expected sigma (width) of the spots. This parameter
is passed to the "max_sigma" parameter of the blob
detector.
Returns
-------
layer_data : napari.types.LayerDataTuple
The layer data tuple to create a points layer
with the spot coordinates.
"""
# filter the image
filtered_spots = gaussian_high_pass(image, high_pass_sigma)
# detect the spots
blobs_log = blob_log(
filtered_spots,
max_sigma=blob_sigma,
num_sigma=1,
threshold=spot_threshold
)
points_coords = blobs_log[:, 0:2]
sizes = 3 * blobs_log[:, 2]
layer_data = (
points_coords,
{
"face_color": "magenta",
"size": sizes
},
"Points"
)
return layer_data
Testing/Installing your plugin#
To test and use our plugin, we need to install it in our python environment. First, return to your terminal and verify you have the napari-tutorial
environment activated. Then, navigate to the directory that you created with the cookiecutter. For example, if you named your plugin napari-spot-detector
, you would enter the following into your terminal.
cd ~/Documents/napari-spot-detector
Then, we install the plugin with pip. pip is the package installer for python (see the documentation for more information). We will use the -e
option to install in “editable” mode. This means that when we in make a change to our source code, it will be update the installed package the next time it is imported.
pip install -e .
To confirm if your installation completed successfully, you can launch napari from the command line.
napari
Once napari is open, you can open your plugin from the “Plugin” menu. You can test your plugin by locating the spots image from the tutorial notebooks folder (napari_plugin_intro_workshop
) we downloaded at the beginning of this tutorial in the File browser (<path to notebook folder>/data/spots_cropped.tif
), dragging the image into the napari viewer, and try running the plugin.
Congratulations! You have made your first napari plugin!
Bonus exercises#
In case you have finished all of the exercises with some time to spare, we have provided some ideas for ways that you can extend the plugin. Please feel free to give them a go and ask the teaching team if you have any questions.
add sample data to your plugin. To do so, you would need to implement the sample data hookspec
add an option to your
detect_spots()
function plugin to return the filtered image in addition to the points layer.add some tests to the
_tests/test_function.py
file.upload your plugin to github
start your own plugin
consult with the teaching team about integrating napari into your workflow