Skip to content

Extending Napari

If you are contributing to and extending AFMSlicer you will likely want to include the changes and new features in the Napari. There is extensive documentation on developing Napari plugins which leverage the magicgui package to facilitate creation of "widgets".

A worked example is included for illustrative purposes and should help with adding or extending options. You can always refer to the source code for more details when extending.

3D Viewer Widget

Napari leverages magicgui to make it easier to construct "widgets" and there are a number of examples in the documentation. Here is a simplified version that I actually understand.

We leverage the @magicfactory() decorator and for each argument of the function that is being decorated (bar the image argument which Napari seems to sort out on its own) we define a dictionary with a bare minimum of a label key. The value of this label key should be a string and is the text that is displayed against the option. If there are not other key/value pairs then this includes a simple text box which is appropriate for a boolean parameter. If a numerical value is required to be input then you can include key the keys min, max and step which define the range and the increments the slider adjusts in. An illustrative example is shown below...

"""View images in three dimensions."""

from typing import TYPE_CHECKING

import napari.types
from magicgui import magic_factory
from napari import current_viewer  # pylint: disable=no-name-in-module
from napari.layers import Image
from napari_topostats.utils import afm2stack

if TYPE_CHECKING:
    import napari


@magic_factory(
    by_slices={"label": "Whether to stack by slices."},
    numslices={"label": "Number of slices.", "min": 20, "max": 2000, "step": 1},
    resolution={"label": "Resolution.", "min": 1.0, "max": 100.0, "step": 0.1},
)
def view_3d(
    image: Image,
    by_slices: bool = True,
    numslices: int = 255,
    resolution: float = 1.0,
) -> napari.types.LayerDataTuple:
    """
    View image in three dimensions.

    Parameters
    ----------
    image : Image
        Image to be viewed in three dimensions.
    by_slices : bool
        Whether to stack by slices (default ``True``). If ``False`` then ``resolution`` is used.
    numslices : int
        Number of slices to create.
    resolution : float
        The resolution/distance between each slice, by default 1.0.

    Returns
    -------
    napari.types.LayerDataTuple:
        Modified image in three-dimensions.
    """
    three_dimensions = afm2stack(
        image=image.data,
        by_slices=by_slices,
        numslices=numslices,
        resolution=resolution,
    )
    viewer = current_viewer()
    viewer.dims.ndisplay = 3
    return (
        three_dimensions,
        {"name": f"{image.name}_3D"},
        "image",
    )

Here the plugin component for viewing the image in 3D has three "widgets", a tick box for whether to stack slices which is stored in by_slices, the number of slices (numslices) which can be between 20 and 2000 in steps of 1 and the resolution which can be between 1.0 and 100.0 in steps of 0.1. These map directly to the arguments of the view_3d() function which is a thin wrapper to afm2stack(). In this instance the current viewer is obtained and the number of display dimensions is increased to 3 (as this renders a 2D image in 3D). Finally a LayerDataTuple is returned.

To add this to the menus in the Napari GUI you add the following to napari.yaml.

contributions:
  commands:
    - id: napari-afmslicer.view_3d_widget
      python_name: napari_afmslicer.view_3d_widget:view_3d
      title: AFMSlicer 3D Viewer
  widgets:
    - command: napari-afmslicer.view_3d_widget
      display_name: AFMSlicer 3D Viewer
  menus:
    napari/layers/filters:
      - submenu: filtering_submenu
    filtering_submenu:
      - command: napari-afmslicer.view_3d_widget

  submenus:
    - id: filtering_submenu
      label: Filtering