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, intensity_stats: 'tuple[float, 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 (z, y, x). None: the zoo's recommendation for model (80, 160, 160 for the generic ones), no larger than the tile; it shrinks by itself on a GPU out-of-memory, down to PlantSeg's 64-pixel minimum in y and x.

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
intensity_stats 'tuple[float, float] | None'

(mean, std) of the membrane channel over the whole image, used to standardize every tile. PlantSeg otherwise standardizes each input with its own mean and std, so every tile gets its own contrast and neighbouring tiles disagree at the seams. The workflow measures it (:func:patchworks._intensity.intensity_stats).

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,
    intensity_stats: "tuple[float, 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 (z, y, x). ``None``: the zoo's recommendation for
        *model* (80, 160, 160 for the generic ones), no larger than the
        tile; it shrinks by itself on a GPU out-of-memory, down to
        PlantSeg's 64-pixel minimum in y and x.
    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.
    intensity_stats :
        ``(mean, std)`` of the membrane channel over the whole image, used
        to standardize every tile. PlantSeg otherwise standardizes each input
        with its own mean and std, so every tile gets its own contrast and
        neighbouring tiles disagree at the seams. The workflow measures it
        (:func:`patchworks._intensity.intensity_stats`).

    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,
        intensity_stats=(
            tuple(float(v) for v in intensity_stats)
            if intensity_stats
            else None
        ),
    )
    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)