Skip to content

PlantSeg plugin

See Cells from a membrane stain.

patchworks.plugins.plantseg.plantseg_fn(model: str | None = 'generic_confocal_3D_unet', *, model_id: str | None = None, config_path: str | None = None, weights_path: str | None = None, segmentation: str = 'gasp', beta: float = 0.6, post_minsize: int = 100, ws_threshold: float = 0.5, ws_sigma_seeds: float = 2.0, ws_min_size: int = 50, ws_stacked: bool = False, nuclei_sigma: Any = 1.0, nuclei_threshold: float | None = None, nuclei_min_size: int = 50, foreground: str | float | None = None, foreground_sigma: Any = 2.0, max_radius_um: float | None = None, boundary_channel: int = 0, rescale: bool = True, patch: tuple[int, ...] | None = None, device: str = 'cuda', n_threads: int | None = None, seeds: str = 'channel', voxel_size: dict[str, float] | None = None) -> Callable[[np.ndarray], np.ndarray]

Return a PlantSeg segmentation for tile_process.

Parameters:

Name Type Description Default
model str | None

PlantSeg zoo model (available_models()), e.g. "generic_confocal_3D_unet" or "generic_light_sheet_3D_unet".

'generic_confocal_3D_unet'
model_id str | None

Or a BioImage.IO model zoo id instead of model.

None
config_path str | None

Or your own trained U-Net (PlantSeg's training config and weights).

None
weights_path str | None

Or your own trained U-Net (PlantSeg's training config and weights).

None
segmentation str

One of :data:SEGMENTATIONS. "gasp" (default), "mutex_ws" and "multicut" agglomerate watershed supervoxels; "dt_watershed" stops at the supervoxels; "lifted_multicut" and "nuclei_watershed" also use the nuclear channel.

'gasp'
beta float

Agglomeration bias: lower merges more (under-segments), higher splits more. PlantSeg's GUI default is 0.6.

0.6
post_minsize int

Cells smaller than this many voxels are merged into a neighbour.

100
ws_threshold float

The supervoxel watershed (PlantSeg's dt_watershed); ws_stacked runs it plane by plane, for a coarse z.

0.5
ws_sigma_seeds float

The supervoxel watershed (PlantSeg's dt_watershed); ws_stacked runs it plane by plane, for a coarse z.

0.5
ws_min_size float

The supervoxel watershed (PlantSeg's dt_watershed); ws_stacked runs it plane by plane, for a coarse z.

0.5
ws_stacked float

The supervoxel watershed (PlantSeg's dt_watershed); ws_stacked runs it plane by plane, for a coarse z.

0.5
nuclei_sigma Any

How nuclei are found in the nuclear channel, see :func:patchworks.plugins.watershed.nuclei_seeds.

1.0
nuclei_threshold Any

How nuclei are found in the nuclear channel, see :func:patchworks.plugins.watershed.nuclei_seeds.

1.0
nuclei_min_size Any

How nuclei are found in the nuclear channel, see :func:patchworks.plugins.watershed.nuclei_seeds.

1.0
foreground str | float | None

Background masking, see :func:patchworks.plugins.watershed.foreground_mask: a boundary U-Net puts cells everywhere, empty space included.

None
foreground_sigma str | float | None

Background masking, see :func:patchworks.plugins.watershed.foreground_mask: a boundary U-Net puts cells everywhere, empty space included.

None
max_radius_um str | float | None

Background masking, see :func:patchworks.plugins.watershed.foreground_mask: a boundary U-Net puts cells everywhere, empty space included.

None
boundary_channel int

Output channel holding the boundaries (0 for the boundary models).

0
rescale bool

Resample each tile to the model's training voxel size before predicting (and the prediction back), from voxel_size. The single biggest factor in a pretrained U-Net's quality.

True
patch tuple[int, ...] | None

U-Net patch shape; None lets PlantSeg size it to the GPU.

None
device str

"cuda" or "cpu".

'cuda'
n_threads int | None

Threads for the watershed and agglomeration.

None
seeds str

For the modes using nuclei: "channel" (default) finds them in the tile's nuclear channel (nuclei_* options); "labels" takes them as given, one object per seed -- the workflow's seed_labels, e.g. Cellpose's nuclei, which sets this itself.

'channel'
voxel_size dict[str, float] | None

{"z": .., "y": .., "x": ..} in micrometres; the workflow passes the image's own calibration.

None

Returns:

Type Description
Callable[[ndarray], ndarray]

Picklable labeller: ([2,] [z,] y, x) -> ([z,] y, x).

Source code in src/patchworks/plugins/plantseg.py
def plantseg_fn(
    model: str | None = "generic_confocal_3D_unet",
    *,
    model_id: str | None = None,
    config_path: str | None = None,
    weights_path: str | None = None,
    segmentation: str = "gasp",
    beta: float = 0.6,
    post_minsize: int = 100,
    ws_threshold: float = 0.5,
    ws_sigma_seeds: float = 2.0,
    ws_min_size: int = 50,
    ws_stacked: bool = False,
    nuclei_sigma: Any = 1.0,
    nuclei_threshold: float | None = None,
    nuclei_min_size: int = 50,
    foreground: str | float | None = None,
    foreground_sigma: Any = 2.0,
    max_radius_um: float | None = None,
    boundary_channel: int = 0,
    rescale: bool = True,
    patch: tuple[int, ...] | None = None,
    device: str = "cuda",
    n_threads: int | None = None,
    seeds: str = "channel",
    voxel_size: dict[str, float] | None = None,
) -> Callable[[np.ndarray], np.ndarray]:
    """Return a PlantSeg segmentation for ``tile_process``.

    Parameters
    ----------
    model :
        PlantSeg zoo model (``available_models()``), e.g.
        ``"generic_confocal_3D_unet"`` or ``"generic_light_sheet_3D_unet"``.
    model_id :
        Or a BioImage.IO model zoo id instead of *model*.
    config_path, weights_path :
        Or your own trained U-Net (PlantSeg's training config and weights).
    segmentation :
        One of :data:`SEGMENTATIONS`. ``"gasp"`` (default), ``"mutex_ws"``
        and ``"multicut"`` agglomerate watershed supervoxels;
        ``"dt_watershed"`` stops at the supervoxels; ``"lifted_multicut"``
        and ``"nuclei_watershed"`` also use the nuclear channel.
    beta :
        Agglomeration bias: lower merges more (under-segments), higher
        splits more. PlantSeg's GUI default is 0.6.
    post_minsize :
        Cells smaller than this many voxels are merged into a neighbour.
    ws_threshold, ws_sigma_seeds, ws_min_size, ws_stacked :
        The supervoxel watershed (PlantSeg's ``dt_watershed``); *ws_stacked*
        runs it plane by plane, for a coarse z.
    nuclei_sigma, nuclei_threshold, nuclei_min_size :
        How nuclei are found in the nuclear channel, see
        :func:`patchworks.plugins.watershed.nuclei_seeds`.
    foreground, foreground_sigma, max_radius_um :
        Background masking, see
        :func:`patchworks.plugins.watershed.foreground_mask`: a boundary
        U-Net puts cells everywhere, empty space included.
    boundary_channel :
        Output channel holding the boundaries (0 for the boundary models).
    rescale :
        Resample each tile to the model's training voxel size before
        predicting (and the prediction back), from *voxel_size*. The single
        biggest factor in a pretrained U-Net's quality.
    patch :
        U-Net patch shape; ``None`` lets PlantSeg size it to the GPU.
    device :
        ``"cuda"`` or ``"cpu"``.
    n_threads :
        Threads for the watershed and agglomeration.
    seeds :
        For the modes using nuclei: ``"channel"`` (default) finds them in
        the tile's nuclear channel (``nuclei_*`` options); ``"labels"``
        takes them as given, one object per seed -- the workflow's
        ``seed_labels``, e.g. Cellpose's nuclei, which sets this itself.
    voxel_size :
        ``{"z": .., "y": .., "x": ..}`` in micrometres; the workflow passes
        the image's own calibration.

    Returns
    -------
    Callable[[ndarray], ndarray]
        Picklable labeller: ``([2,] [z,] y, x) -> ([z,] y, x)``.
    """
    if segmentation not in SEGMENTATIONS:
        raise ValueError(
            f"segmentation must be one of {SEGMENTATIONS}, got {segmentation!r}"
        )
    if model is None and model_id is None and config_path is None:
        raise ValueError("give a zoo model, a model_id or a config_path")
    if seeds not in SEED_MODES:
        raise ValueError(f"seeds must be one of {SEED_MODES}, got {seeds!r}")
    if seeds == "labels" and segmentation not in NEEDS_NUCLEI:
        raise ValueError(
            f'seeds: "labels" (seed_labels) is only used by segmentation '
            f'{NEEDS_NUCLEI}; "{segmentation}" would ignore them'
        )
    if max_radius_um is not None and not voxel_size:
        raise ValueError(
            "max_radius_um needs voxel_size (the image calibration)"
        )
    if max_radius_um is not None and segmentation not in NEEDS_NUCLEI:
        raise ValueError(
            "max_radius_um measures from the nuclei: use it with "
            f"segmentation {NEEDS_NUCLEI}"
        )
    _require_plantseg()
    factors = None
    if rescale and model and not (model_id or config_path):
        resolution = model_resolution(model)
        if voxel_size and resolution:
            factors = rescale_factors(voxel_size, resolution, len(resolution))
            if factors:
                logger.info(
                    "PlantSeg: resampling tiles by %s (z, y, x) to the "
                    "training voxel size of %s (%s um)",
                    factors,
                    model,
                    resolution,
                )
    cfg = dict(
        model=model,
        model_id=model_id,
        config_path=config_path,
        weights_path=weights_path,
        segmentation=segmentation,
        beta=beta,
        post_minsize=post_minsize,
        ws_threshold=ws_threshold,
        ws_sigma_seeds=ws_sigma_seeds,
        ws_min_size=ws_min_size,
        ws_stacked=ws_stacked,
        nuclei_sigma=nuclei_sigma,
        nuclei_threshold=nuclei_threshold,
        nuclei_min_size=nuclei_min_size,
        foreground=foreground,
        foreground_sigma=foreground_sigma,
        max_radius_um=max_radius_um,
        boundary_channel=boundary_channel,
        rescale=factors,
        patch=tuple(patch) if patch else None,
        device=device,
        n_threads=n_threads,
        seeds=seeds,
        voxel_size=voxel_size,
    )
    return partial(_run, cfg=cfg)

patchworks.plugins.plantseg.fetch_model(model: str = 'generic_confocal_3D_unet') -> None

Download a zoo model into $PLANTSEG_HOME now, not on a GPU node.

Compute nodes often have no internet access; the segment jobs then load the copy fetched here.

Source code in src/patchworks/plugins/plantseg.py
def fetch_model(model: str = "generic_confocal_3D_unet") -> None:
    """Download a zoo model into ``$PLANTSEG_HOME`` now, not on a GPU node.

    Compute nodes often have no internet access; the segment jobs then load
    the copy fetched here.
    """
    _require_plantseg()
    from plantseg.core.zoo import model_zoo

    model_zoo.get_model_by_name(model)

patchworks.plugins.plantseg.available_models() -> list[str]

Names in PlantSeg's model zoo (empty without PlantSeg).

Source code in src/patchworks/plugins/plantseg.py
def available_models() -> list[str]:
    """Names in PlantSeg's model zoo (empty without PlantSeg)."""
    try:
        from plantseg.core.zoo import model_zoo
    except ImportError:
        return []
    return list(model_zoo.get_model_names())

patchworks.plugins.plantseg.rescale_factors(voxel_size: dict[str, float] | None, resolution: tuple[float, ...] | None, ndim: int) -> tuple[float, ...] | None

Zoom per axis taking the image to the model's training resolution.

None when either is unknown or every factor is within 10 % of 1.

Examples:

>>> rescale_factors({"z": 0.47, "y": 0.3, "x": 0.3}, (0.235, 0.15, 0.15), 3)
(2.0, 2.0, 2.0)
>>> rescale_factors({"z": 0.24, "y": 0.15, "x": 0.15}, (0.235, 0.15, 0.15), 3)
Source code in src/patchworks/plugins/plantseg.py
def rescale_factors(
    voxel_size: dict[str, float] | None,
    resolution: tuple[float, ...] | None,
    ndim: int,
) -> tuple[float, ...] | None:
    """Zoom per axis taking the image to the model's training resolution.

    ``None`` when either is unknown or every factor is within 10 % of 1.

    Examples
    --------
    >>> rescale_factors({"z": 0.47, "y": 0.3, "x": 0.3}, (0.235, 0.15, 0.15), 3)
    (2.0, 2.0, 2.0)
    >>> rescale_factors({"z": 0.24, "y": 0.15, "x": 0.15}, (0.235, 0.15, 0.15), 3)
    """
    if not voxel_size or not resolution:
        return None
    axes = "zyx"[-ndim:]
    res = tuple(resolution)[-ndim:]
    factors = []
    for axis, r in zip(axes, res):
        v = voxel_size.get(axis)
        factors.append(float(v) / float(r) if v and r else 1.0)
    if all(abs(f - 1.0) <= _RESCALE_TOLERANCE for f in factors):
        return None
    return tuple(round(f, 6) for f in factors)