cellmap_flow.post.segment

Instance segmenters: from what a model predicts to an id per object.

Plain functions on numpy arrays, shared by the postprocessors a served layer runs (post.postprocessors) and by the Finetune tab’s “Seed from Prediction” (dashboard/routes/finetune/view_labels.py), so a model’s objects come out the same in both. Which one fits follows from what the model outputs, not from its name:

  • a probability, or any map with a foreground threshold: connected_components of the thresholded mask. Touching objects stay one object.

  • affinities to the neighbours at given offsets: mutex_watershed. The model says where two voxels belong apart, so touching objects split.

  • a distance to the object boundary: distance_watershed, which cuts touching objects apart where the distance dips between their centres.

  • integer instance labels of the model’s own (Cellpose’s masks): relabel_instances.

Each returns uint64 labels, 0 for background and 1..n for the objects with no id skipped, so labels.max() is how many there are.

connectivity is scipy’s (ndimage.generate_binary_structure): which neighbours touch. 1, faces only (6 in 3D, 4 in a 2D slice), is the default everywhere: a one-voxel background wall painted with the brush cuts an object under it, where with 3 (all 26 neighbours) voxels across a diagonal wall still touch. 2 adds the edge neighbours (18 in 3D, 8 in 2D).

The segmentation libraries are imported inside the functions: the postprocessors import this module, and every chain is read through them, in processes that never segment anything (tests/utils/test_import_hygiene). connected_components, remove_small and the parsing of their options live in cellmap_flow/portable/label_it.py, which the browser runs too.

Functions

mutex_watershed(affinities, offsets[, bias, min_size])

Objects from affinities: (channels, z, y, x), one channel per offset, in [0, 1].

distance_watershed(distance[, threshold, markers, ...])

Objects from a distance map: higher deeper inside an object, threshold at its boundary.

relabel_instances(labels[, split_disconnected, ...])

A model's own integer instance labels, numbered 1..n (0 stays background).

Module Contents

cellmap_flow.post.segment.mutex_watershed(affinities, offsets, bias=0.0, min_size=0)

Objects from affinities: (channels, z, y, x), one channel per offset, in [0, 1].

mwatershed.agglom joins voxels along edges whose affinity is above bias and keeps them apart along those below it, strongest edges first. Every voxel ends up in some fragment, background included, so a fragment whose mean affinity (over the channels) is below bias is background: it is where the model sees no object. offsets: the voxel offset each channel compares, as the model was trained with; a list longer than the channels is cut to them. min_size: as for connected_components.

A channel whose offset is as long as the block along an axis, or longer, pairs no voxel in it (a 2D model’s z channel on the one slice it serves): it joins nothing and keeps nothing apart, so it is left out of both, the agglomeration (mwatershed panics on an offset longer than its axis) and the mean. In that mean its values, the model’s guess at a neighbour the block does not have, pulled a slice’s objects a third of the way to background, and a 4x4 square at 0.88 came out as none.

cellmap_flow.post.segment.distance_watershed(distance, threshold=0.5, markers='peaks', peak_depth=0.05, marker_threshold=0.9, connectivity=1, min_size=0)

Objects from a distance map: higher deeper inside an object, threshold at its boundary.

Foreground is distance > threshold. Each object grows from a marker, by a watershed of -distance inside the foreground, so two touching objects meet where the distance dips between their centres: the neck a threshold alone would leave joined. markers:

  • “peaks” (default): the distance’s maxima that stand at least peak_depth above the lowest point on any path to a higher one (h-maxima). A noisy plateau inside one object is one marker, and a neck shallower than peak_depth does not split.

  • “threshold”: the connected parts of distance > marker_threshold, the objects’ cores. For a model whose distance saturates inside objects, where the peaks are flat.

The defaults are for a probability-like map (0.5 at the boundary, as the cellmap distance models’ sigmoid gives, and Seed from Prediction makes of any output); for a distance in voxels, give the threshold and depth in voxels. A foreground part no marker reaches is an object of its own, so nothing the threshold calls foreground is lost.

cellmap_flow.post.segment.relabel_instances(labels, split_disconnected=False, connectivity=1, min_size=0)

A model’s own integer instance labels, numbered 1..n (0 stays background).

split_disconnected: each id’s connected parts become objects of their own. For labels whose ids are unique only within a block (a Cellpose server numbers each chunk from 1), read across several blocks: the same id in two blocks is two objects, and they do not touch.