First generation plugins (Deprecated)#

DEPRECATED

We introduced a new plugin engine (npe2) in December 2021.

Plugins targeting the first generation napari-plugin-engine (described on this page) will continue to work for at least the first half of 2022, but we recommend that new plugins use npe2 and existing plugins consider migrating soon. See the npe2 migration guide for details.

The content below describes the original napari-plugin-engine and exists for archival reference purposes during the deprecation period.

Overview#

napari supports plugin development through hooks: specific places in the napari codebase where functionality can be extended.

  1. Hook specifications: The available hooks are declared as “hook specifications”: function signatures that define the API (or “contract”) that a plugin developer must adhere to when writing their function that we promise to call somewhere in the napari codebase. See Step 1: Choose a hook specification to implement.

  2. Hook implementations: To make a plugin, plugin developers then write functions (”hook implementations”) and mark that function as meeting the requirements of a specific hook specification offered by napari. See Step 2: Write your hook implementation.

  3. Plugin discovery: Plugins that are installed in the same python environment as napari can make themselves known to napari. napari will then scan plugin modules for hook implementations that will be called at the appropriate time and place during the execution of napari. See Step 3: Make your plugin discoverable.

  4. Plugin sharing: When you are ready to share your plugin, tag your repo with napari-plugin, push a release to pypi, and announce it on Image.sc. Your plugin will then be available for users on the napari hub. See Step 4: Deploy your plugin.

Step 1: Choose a hook specification to implement#

The functionality of plugins, as currently designed and implemented in napari, is rather specific in scope: They are not just independent code blocks with their own GUIs that show up next to the main napari window. Rather, plugin developers must decide which of the current hook specifications defined by napari they would like to implement.

For a complete list of hook specifications that developers can implement, see the Hook Specification Reference.

A single plugin package may implement more than one hook specification, and each hook specification could have multiple hook implementations within a single package.

Let’s take the napari_get_reader() hook (our primary “reader plugin” hook) as an example. It is defined as:

   LayerData = Union[Tuple[Any], Tuple[Any, Dict], Tuple[Any, Dict, str]]
   ReaderFunction = Callable[[str], List[LayerData]]

   @napari_hook_specification(firstresult=True)
   def napari_get_reader(
       path: Union[str, List[str]]
   ) -> Optional[ReaderFunction]:
       ...

Note that it takes a str or a list of str and either returns None or a function. From the docstring of the hook specification, we see that the implementation should return None if the path is of an unrecognized format, otherwise it should return a ReaderFunction, which is a function that takes a str (the filepath to read) and returns a list of LayerData, where LayerData is any one of (data,), (data, meta), or (data, meta, layer_type).

That seems like a bit of a mouthful! But it’s a precise (though flexible) contract that you can follow, and know that napari will handle the rest.

Step 2: Write your hook implementation#

Once you have identified the hook specification that you want to implement, you have to create a hook implementation: a function that accepts the arguments specified by the hook specification signature and returns a value with the expected return type.

Here’s an example hook implementation for napari_get_reader() that enables napari to open a numpy binary file with a .npy extension (previously saved with numpy.save())

   import numpy as np
   from napari_plugin_engine import napari_hook_implementation

   def npy_file_reader(path):
      array = np.load(path)
      # return it as a list of LayerData tuples,
      # here with no optional metadata
      return [(array,)]

   # this line is explained below in "Decorating your function..."
   @napari_hook_implementation
   def napari_get_reader(path):
      # remember, path can be a list, so we check its type first...
      # (this example plugin doesn't handle lists)
      if isinstance(path, str) and path.endswith(".npy"):
         # If we recognize the format, we return the actual reader function
         return npy_file_reader
      # otherwise we return None.
      return None

Decorating your function with HookImplementationMarker#

In order to let napari know that one of your functions satisfies the API of one of the napari hook specifications, you must decorate your function with an instance of HookImplementationMarker, initialized with the name "napari". As a convenience, napari provides this decorator at napari_plugin_engine.napari_hook_implementation as shown in the example above.

However, it’s not required to import from or depend on napari at all when writing a plugin. You can import a napari_hook_implementation decorator directly from napari_plugin_engine (a very lightweight dependency that uses only standard lib python).

   from napari_plugin_engine import napari_hook_implementation
Matching hook implementations to specifications#

By default, napari matches your implementation to one of our hook specifications by looking at the name of your decorated function. So in the example above, because the hook implementation was literally named napari_get_reader, it gets interpreted as an implementation for the hook specification of the same name.

   @napari_hook_implementation
   def napari_get_reader(path: str):
      ...

However, you may also mark any function as satisfying a particular napari hook specification (regardless of the function’s name) by providing the name of the target hook specification to the specname argument in your implementation decorator:

   @napari_hook_implementation(specname="napari_get_reader")
   def whatever_name_you_want(path: str):
      ...

This allows you to specify multiple hook implementations of the same hook specification in the same module or class, without needing a separate entry point.

Step 3: Make your plugin discoverable#

Packages and modules installed in the same environment as napari may make themselves “discoverable” to napari using package metadata, as outlined in the Python Packaging Authority guide.

By providing an entry_points argument with the key napari.plugin to setup() in setup.py, plugins can register themselves for discovery.

For example if you have a package named mypackage with a submodule napari_plugin where you have decorated one or more napari hook implementations, then if you include in setup.py:

   # setup.py

   setup(
      ...
      entry_points={'napari.plugin': 'plugin-name = mypackage.napari_plugin'},
      ...
   )

… then napari will search the mypackage.napari_plugin module for functions decorated with the HookImplementationMarker("napari") decorator and register them under the plugin name "plugin-name".

A user would then be able to use napari, extended with your package’s functionality by simply installing your package along with napari:

   python -m pip install napari mypackage

Step 4: Deploy your plugin#

See testing and deploying your plugin. (This hasn’t changed significantly with the secod generation (npe2) plugin engine).

napari plugin template#

To quickly generate a new napari plugin project, you may wish to use the napari-plugin-template. This uses the copier command line utility, which will ask you a few questions about your project and get you started with a ready-to-go package layout where you can begin implementing your plugin.

Install copier and use the template as follows:

python -m pip install copier jinja2-time
python -m pip install npe2
copier copy --trust https://github.com/napari/napari-plugin-template new-plugin-name

See the readme for details


Hook Specification Reference#

All napari hook specifications for pluggable functionality are defined here.

A hook specification is a function signature (with documentation) that declares an API that plugin developers must adhere to when providing hook implementations. Hook implementations provided by plugins (and internally by napari) will then be invoked in various places throughout the code base.

When implementing a hook specification, pay particular attention to the number and types of the arguments in the specification signature, as well as the expected return type.

To allow for hook specifications to evolve over the lifetime of napari, hook implementations may accept fewer arguments than defined in the specification. (This allows for extending existing hook arguments without breaking existing implementations). However, implementations must not require more arguments than defined in the spec.

For more general background on the plugin hook calling mechanism, see the napari-plugin-manager documentation.

Note

Hook specifications are a feature borrowed from pluggy. In the pluggy documentation, hook specification marker instances are named hookspec by convention, and hook implementation marker instances are named hookimpl. The convention in napari is to name them more explicitly: napari_hook_specification and napari_hook_implementation, respectively.

IO hooks#

napari.plugins.hook_specifications.napari_provide_sample_data() dict[str, str | Path | Callable[[...], Iterable[tuple[Any] | tuple[Any, Mapping] | tuple[Any, Mapping, Literal['graph', 'image', 'labels', 'points', 'shapes', 'surface', 'tracks', 'vectors']]]] | SampleDict][source]#

Provide sample data.

Plugins may implement this hook to provide sample data for use in napari. Sample data is accessible in the File > Open Sample menu, or programmatically, with napari.Viewer.open_sample().

Plugins implementing this hook specification must return a dict, where each key is a sample_key (the string that will appear in the Open Sample menu), and the value is either a string, or a callable that returns an iterable of LayerData tuples, where each tuple is a 1-, 2-, or 3-tuple of (data,), (data, meta), or (data, meta, layer_type) (thus, an individual sample-loader may provide multiple layers). If the value is a string, it will be opened with napari.Viewer.open().

Examples

Here’s a minimal example of a plugin that provides three samples:

  1. random data from numpy

  2. a random image pulled from the internet

  3. random data from numpy, provided as a dict with the keys:
    • ‘display_name’: a string that will show in the menu (by default,

      the sample_key will be shown)

    • ‘data’: a string or callable, as in 1/2.

import numpy as np
from napari_plugin_engine import napari_hook_implementation

def _generate_random_data(shape=(512, 512)):
    data = np.random.rand(*shape)
    return [(data, {'name': 'random data'})]

@napari_hook_implementation
def napari_provide_sample_data():
    return {
        'random data': _generate_random_data,
        'random image': 'https://picsum.photos/1024',
        'sample_key': {
            'display_name': 'Some Random Data (512 x 512)'
            'data': _generate_random_data,
        }
    }
Returns:

A mapping of sample_key to data_loader

Return type:

Dict[ str, Union[str, Callable[…, Iterable[LayerData]]] ]

napari.plugins.hook_specifications.napari_get_reader(path: str | list[str]) Callable[[str | Path | Sequence[str | Path]], list[tuple[Any] | tuple[Any, Mapping] | tuple[Any, Mapping, Literal['graph', 'image', 'labels', 'points', 'shapes', 'surface', 'tracks', 'vectors']]]] | None[source]#

Return a function capable of loading path into napari, or None.

This is the primary “reader plugin” function. It accepts a path or list of paths, and returns a list of data to be added to the Viewer. The function may return [(None, )] to indicate that the file was read successfully, but did not contain any data.

The main place this hook is used is in Viewer.open(), via the read_data_with_plugins() function.

It will also be called on File -> Open... or when a user drops a file or folder onto the viewer. This function must execute quickly, and should return None if the filepath is of an unrecognized format for this reader plugin. If path is determined to be recognized format, this function should return a new function that accepts the same filepath (or list of paths), and returns a list of LayerData tuples, where each tuple is a 1-, 2-, or 3-tuple of (data,), (data, meta), or (data, meta, layer_type).

napari will then use each tuple in the returned list to generate a new layer in the viewer using the Viewer._add_layer_from_data() method. The first, (optional) second, and (optional) third items in each tuple in the returned layer_data list, therefore correspond to the data, meta, and layer_type arguments of the Viewer._add_layer_from_data() method, respectively.

Important

path may be either a str or a list of str. If a list, then each path in the list can be assumed to be one part of a larger multi-dimensional stack (for instance: a list of 2D image files that should be stacked along a third axis). Implementations should do their own checking for list or str, and handle each case as desired.

Parameters:

path (str or list of str) – Path to file, directory, or resource (like a URL), or a list of paths.

Returns:

A function that accepts the path, and returns a list of layer_data, where layer_data is one of (data,), (data, meta), or (data, meta, layer_type). If unable to read the path, must return None (not False!).

Return type:

Callable or None

napari.plugins.hook_specifications.napari_get_writer(path: str, layer_types: list[str]) Callable[[str, list[tuple[Any, Mapping, Literal['graph', 'image', 'labels', 'points', 'shapes', 'surface', 'tracks', 'vectors']]]], list[str]] | None[source]#

Return function capable of writing napari layer data to path.

This function will be called whenever the user attempts to save multiple layers (e.g. via File -> Save Layers, or save_layers()). This function must execute quickly, and should return None if path has an unrecognized extension for the reader plugin or the list of layer types are incompatible with what the plugin can write. If path is a recognized format, this function should return a function that accepts the same path, and a list of tuples containing the data for each layer being saved in the form of (Layer.data, Layer._get_state(), Layer._type_string). The writer function should return a list of strings (the actual filepath(s) that were written).

Important

It is up to plugins to inspect and obey any extension in path (and return None if it is an unsupported extension).

An example function signature for a WriterFunction that might be returned by this hook specification is as follows:

def writer_function(
    path: str, layer_data: List[Tuple[Any, Dict, str]]
) -> List[str]:
    ...
Parameters:
  • path (str) – Path to file, directory, or resource (like a URL). Any extensions in the path should be examined and obeyed. (i.e. if the plugin is incapable of returning a requested extension, it should return None).

  • layer_types (list of str) – List of layer types (e.g. “image”, “labels”) that will be provided to the writer function.

Returns:

A function that accepts the path, a list of layer_data (where layer_data is (data, meta, layer_type)). If unable to write to the path or write the layer_data, must return None (not False).

Return type:

Callable or None

Single Layers IO#

The following hook specifications will be called when a user saves a single layer in napari, and should save the layer to the requested format and return the save path if the data are successfully written. Otherwise, if nothing was saved, return None. They each accept a path. It is up to plugins to inspect and obey the extension of the path (and return False if it is an unsupported extension). The data argument will come from Layer.data, and a meta dict that will correspond to the layer’s _get_state() method.

napari.plugins.hook_specifications.napari_write_image(path: str, data: Any, meta: dict) str | None[source]#

Write image data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data (array or list of array) – Image data. Can be N dimensional. If meta[‘rgb’] is True then the data should be interpreted as RGB or RGBA. If meta[‘multiscale’] is True, then the data should be interpreted as a multiscale image.

  • meta (dict) – Image metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

napari.plugins.hook_specifications.napari_write_labels(path: str, data: Any, meta: dict) str | None[source]#

Write labels data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data (array or list of array) – Integer valued label data. Can be N dimensional. Every pixel contains an integer ID corresponding to the region it belongs to. The label 0 is rendered as transparent. If a list and arrays are decreasing in shape then the data is from a multiscale image.

  • meta (dict) – Labels metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

napari.plugins.hook_specifications.napari_write_points(path: str, data: Any, meta: dict) str | None[source]#

Write points data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data (array (N, D)) – Coordinates for N points in D dimensions.

  • meta (dict) – Points metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

napari.plugins.hook_specifications.napari_write_shapes(path: str, data: Any, meta: dict) str | None[source]#

Write shapes data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data (list) – List of shape data, where each element is an (N, D) array of the N vertices of a shape in D dimensions.

  • meta (dict) – Shapes metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

napari.plugins.hook_specifications.napari_write_surface(path: str, data: Any, meta: dict) str | None[source]#

Write surface data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data (3-tuple of array) – The first element of the tuple is an (N, D) array of vertices of mesh triangles. The second is an (M, 3) array of int of indices of the mesh triangles. The third element is the (K0, …, KL, N) array of values used to color vertices where the additional L dimensions are used to color the same mesh with different values.

  • meta (dict) – Surface metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

napari.plugins.hook_specifications.napari_write_vectors(path: str, data: Any, meta: dict) str | None[source]#

Write vectors data and metadata into a path.

It is the responsibility of the implementation to check any extension on path and return None if it is an unsupported extension. If path has no extension, implementations may append their preferred extension.

Parameters:
  • path (str) – Path to file, directory, or resource (like a URL).

  • data ((N, 2, D) array) – The start point and projections of N vectors in D dimensions.

  • meta (dict) – Vectors metadata.

Returns:

path – If data is successfully written, return the path that was written. Otherwise, if nothing was done, return None.

Return type:

str or None

Analysis hooks#

napari.plugins.hook_specifications.napari_experimental_provide_function() LambdaType | list[LambdaType][source]#

Provide function(s) that can be passed to magicgui.

This hook specification is marked as experimental as the API or how the returned value is handled may change here more frequently then the rest of the codebase.

Returns:

function(s) – Implementations should provide either a single function, or a list of functions. Note that this does not preclude specifying multiple separate implementations in the same module or class. The functions should have Python type annotations so that magicgui can generate a widget from them.

Return type:

FunctionType or list of FunctionType

Examples

>>> from napari.types import ImageData, LayerDataTuple
>>>
>>> def my_function(image : ImageData) -> LayerDataTuple:
>>>     # process the image
>>>     result = -image
>>>     # return it + some layer properties
>>>     return result, {'colormap':'turbo'}
>>>
>>> @napari_hook_implementation
>>> def napari_experimental_provide_function():
>>>     return my_function

GUI hooks#

napari.plugins.hook_specifications.napari_experimental_provide_theme() dict[str, dict[str, str | tuple | list]][source]#

Provide GUI with a set of colors used through napari. This hook allows you to provide additional color schemes so you can accomplish your desired styling.

Themes are provided as dict with several required fields and correctly formatted color values. Colors can be specified using color names (e.g. white), hex color (e.g. #ff5733), rgb color in 0-255 range (e.g. rgb(255, 0, 127) or as 3- or 4-element tuples or lists (e.g. (255, 0, 127). The Theme model will automatically handle the conversion.

See Theme for more detail of what are the required keys.

Returns:

themes – Sequence of dictionaries containing new color schemes to be used by napari. You can replace existing themes by using the same names.

Return type:

Dict[str, Dict[str, Union[str, Tuple, List]]

Examples

>>> def get_new_theme() -> Dict[str, Dict[str, Union[str, Tuple, List]]:
...     # specify theme(s) that should be added to napari
...     themes = {
...         "super_dark": {
...             "name": "super_dark",
...             "background": "rgb(12, 12, 12)",
...             "foreground": "rgb(65, 72, 81)",
...             "primary": "rgb(90, 98, 108)",
...             "secondary": "rgb(134, 142, 147)",
...             "highlight": "rgb(106, 115, 128)",
...             "text": "rgb(240, 241, 242)",
...             "icon": "rgb(209, 210, 212)",
...             "warning": "rgb(153, 18, 31)",
...             "current": "rgb(0, 122, 204)",
...             "syntax_style": "native",
...             "console": "rgb(0, 0, 0)",
...             "canvas": "black",
...         }
...     }
...     return themes
>>>
>>> @napari_hook_implementation
>>> def napari_experimental_provide_theme():
...     return get_new_theme()
napari.plugins.hook_specifications.napari_experimental_provide_dock_widget() AugmentedWidget | list[AugmentedWidget][source]#

Provide functions that return widgets to be docked in the viewer.

This hook specification is marked as experimental as the API or how the returned value is handled may change here more frequently then the rest of the codebase.

Returns:

result – A “callable” in this context is a class or function that, when called, returns an instance of either a QWidget or a FunctionGui.

Implementations of this hook specification must return a callable, or a tuple of (callable, dict), where the dict contains keyword arguments for napari.qt.Window.add_dock_widget(). (note, however, that shortcut= keyword is not yet supported).

Implementations may also return a list, in which each item must be a callable or (callable, dict) tuple. Note that this does not preclude specifying multiple separate implementations in the same module or class.

Return type:

callable or tuple or list of callables or list of tuples

Examples

An example with a QtWidget:

>>> from qtpy.QtWidgets import QWidget
>>> from napari_plugin_engine import napari_hook_implementation
>>>
>>> class MyWidget(QWidget):
...     def __init__(self, napari_viewer):
...         self.viewer = napari_viewer
...         super().__init__()
...
...         # initialize layout
...         layout = QGridLayout()
...
...         # add a button
...         btn = QPushButton('Click me!', self)
...         def trigger():
...             print("napari has", len(napari_viewer.layers), "layers")
...         btn.clicked.connect(trigger)
...         layout.addWidget(btn)
...
...         # activate layout
...         self.setLayout(layout)
>>>
>>> @napari_hook_implementation
>>> def napari_experimental_provide_dock_widget():
...     return MyWidget

An example using magicgui:

>>> from magicgui import magic_factory
>>> from napari_plugin_engine import napari_hook_implementation
>>>
>>> @magic_factory(auto_call=True, threshold={'max': 2 ** 16})
>>> def threshold(
...     data: 'napari.types.ImageData', threshold: int
... ) -> 'napari.types.LabelsData':
...     return (data > threshold).astype(int)
>>>
>>> @napari_hook_implementation
>>> def napari_experimental_provide_dock_widget():
...     return threshold

Help#

If you run into trouble creating your plugin, please don’t hesitate to reach out for help in the Image.sc Forum. Alternatively, if you find a bug or have a specific feature request for plugin support, please open an issue at our GitHub issue tracker.