Building a Third-Party Plugin
Overview
The vpt
plugin architecture allows users to run custom cell segmentation algorithms on their data. This plug-and-play
structure gives users the ability to have full customization over the segmentation model and parameters they wish to employ,
so long as it fits the plugin specification and existing workflow. The user can always choose to utilize the previously
supported segmentation techniques, Cellpose and Watershed.
Currently, Vizgen provides two pre-built plugins for use in segmentation:
vpt-plugin-cellpose
vpt-plugin-watershed
These packages, vpt-plugin-cellpose
and vpt-plugin-watershed
, use the Cellpose and Watershed techniques respectively.
Reiterating the Installation section, the packages can be installed individually or together using vpt[all]
.
- For Cellpose:
pip install vpt[cellpose]
- For Watershed:
pip install vpt[watershed]
- For all:
pip install vpt[all]
Naming and Structure
The segmentation family is defined in the Segmentation Task Definition section. With the plugin architecture,
packages should be named as such, vpt-plugin-<segmentation family>. Examples of this include the two aforementioned
packages available from Vizgen, vpt-plugin-cellpose
and vpt-plugin-watershed
. Similarly, modules
in the root folder of the plugin need to be of the form, vpt_plugin_<segmentation family>.
After VPT has found the appropriately named module, vpt_plugin_<segmentation family>, it will import a sub-module
named segment.py
which must exist within the vpt_plugin_<segmentation family> module. In this file is where a
user will import SegmentationBase
from vpt-core and run mask prediction.
Within the segment.py
module, there needs to exist a SegmentationMethod
class that will inherit the SegmentationBase
class
from vpt-core
. The SegmentationMethod
class will use the segmentation method of the users choice to run prediciton and generate
a segmentation mask. From the generated masks, the user needs to return geometries according to the specification of the
SegmentationResult
class in vpt-core
. The user may choose to customize this task or use vpt-core
function,
generate_polygons_from_mask()
, to complete the task. Below is an example of this from the Cellpose plugin.
from typing import Dict, Optional, List, Iterable, Union
import pandas as pd
from vpt_core.io.image import ImageSet
from vpt_core.segmentation.polygon_utils import generate_polygons_from_mask
from vpt_core.segmentation.seg_result import SegmentationResult
from vpt_core.segmentation.segmentation_base import SegmentationBase
from vpt_plugin_cellpose import predict, CellposeSegProperties, CellposeSegParameters
class SegmentationMethod(SegmentationBase):
@staticmethod
def run_segmentation(
segmentation_properties: Dict,
segmentation_parameters: Dict,
polygon_parameters: Dict,
result: List[str],
images: Optional[ImageSet] = None,
transcripts: Optional[pd.DataFrame] = None,
) -> Union[SegmentationResult, Iterable[SegmentationResult]]:
properties = CellposeSegProperties(**segmentation_properties)
parameters = CellposeSegParameters(**segmentation_parameters)
masks = predict.run(images, properties, parameters)
return generate_polygons_from_mask(masks, polygon_parameters)
The run_segmentation()
method within the SegmentationMethod
class runs the prediction specified in the predict module.
The run()
method and hence run_segmentation()
method takes as input images digested from the segmentation task definition.
The 'images' is an instance of the ImageSet
class in vpt-core
which contains information about the images as well as
methods to return the images contained within as a stack for when the model needs to be run on a numpy.ndarray
as in the
run()
method in the predict
module.
The run_segmentation()
method within the SegmentationMethod
class returns a SegmentationResult
object that contains cell
geometries. Upon this object’s creation in generate_polygons_from_mask()
, this object translates fields of information
about each cell and consolidates them into a geopandas
GeoDataFrame that is later saved and can be accessed to visualize
the predicted cell geometries.
Vignette
As an example, below is a snippet of the predict
module. The code block in the above Naming and Structure section
shows how this module is imported and used. This module contains the actual Cellpose model and its parameter control.
import warnings
import numpy as np
from cellpose import models
from vpt_core.io.image import ImageSet
from vpt_plugin_cellpose import CellposeSegProperties, CellposeSegParameters
def run(images: ImageSet, properties: CellposeSegProperties, parameters: CellposeSegParameters) -> np.ndarray:
warnings.filterwarnings("ignore", message=".*the `scipy.ndimage.filters` namespace is deprecated.*")
is_valid_channels = parameters.nuclear_channel and parameters.entity_fill_channel
image = (
images.as_stack([parameters.nuclear_channel, parameters.entity_fill_channel])
if is_valid_channels
else images.as_stack()
)
empty_z_levels = set()
for z_i, z_plane in enumerate(image):
for channel_i in range(z_plane.shape[-1]):
if z_plane[..., channel_i].std() < 0.1:
empty_z_levels.add(z_i)
if len(empty_z_levels) == image.shape[0]:
return np.zeros((image.shape[0],) + image.shape[1:-1])
if properties.custom_weights:
model = models.CellposeModel(gpu=False, pretrained_model=properties.custom_weights, net_avg=False)
else:
model = models.Cellpose(gpu=False, model_type=properties.model, net_avg=False)
to_segment_z = list(set(range(image.shape[0])).difference(empty_z_levels))
mask = model.eval(
image[to_segment_z, ...],
z_axis=0,
channel_axis=len(image.shape) - 1,
diameter=parameters.diameter,
flow_threshold=parameters.flow_threshold,
mask_threshold=parameters.mask_threshold,
resample=False,
min_size=parameters.minimum_mask_size,
tile=True,
do_3D=(properties.model_dimensions == "3D"),
)[0]
mask = mask.reshape((len(to_segment_z),) + image.shape[1:-1])
for i in empty_z_levels:
mask = np.insert(mask, i, np.zeros(image.shape[1:-1]), axis=0)
return mask