cellmap_flow.portable.label_it

Label it’s segmentation of a prediction, written once for both modes.

What the Finetune tab’s Seed from Prediction makes of a model’s output over the box on screen: the request’s options (seed_options), the channels a seed reads (seed_channels; affinity_offsets for a model known by its channel names alone), which served voxel lies under each annotation voxel (placement, gather), the output as a probability (probability) and the objects of the components method (components: threshold, connected components, smallest size, per slice). The server calls these from dashboard/routes/finetune/view_labels.py around its read of the model server’s array; the browser from web/src/backend/routes/view-labels.ts around the engine’s readOutput, through label_it_calls.py.

The box every tool covers is here too when the page asks for the field of view (field_of_view_nm): what the viewer’s panel shows, at the slice on screen, rather than a box around its centre.

The mutex watershed is the one method not here: mwatershed is Rust, with no Pyodide wheel, so each mode runs its own on what affinities gives (post.segment.mutex_watershed on the server, web/src/infer/affinity.ts in the browser, parity-tested against it). The distance watershed and a model’s own instances are the server’s alone (scikit-image, Cellpose); no model the browser runs outputs either.

scipy is imported inside the functions that need it, never at the top: the browser’s Python starts with numpy alone, and chunkmirage’s worker loads a package the first time a call fails to import it, then calls again, so the first seed pays for scipy and opening a dataset does not.

Attributes

SEED_METHODS

UNIT

SIGNED_UNIT

UNBOUNDED

CONNECTIVITIES

FIELD_OF_VIEW_MAX_VOXELS

NEUROGLANCER_TOP_ROW_PX

Functions

classify_output_range(lo, hi) → Optional[str]

Bucket an observed (min, max) output range into an activation class;

as_connectivity(value[, ndim]) → int

value as scipy's connectivity for an ndim-D array: 1 up to ndim.

as_bool(value) → bool

A form's "true"/"false" as a bool; anything else by Python's truth.

seed_options(data) → dict

A seed request's options, from its JSON body, with the defaults the

affinity_offsets(channel_names)

The offsets of a model known by its channel names alone, or None

seed_channels(output_type, offsets[, select_channel])

The channels a seed reads, as a slice, or None for every one.

placement(lo, hi, voxel_size, dataset_offset_nm, ...)

Which voxel of a served array each annotation voxel of [lo, hi)

read_box(indices)

(lo, hi): the box of the served array the indices span, the one to read for gather.

gather(region, indices)

The prediction on the annotation grid: each annotation voxel the

probability(prediction[, output_class, channels])

The prediction as a probability, (channels, z, y, x), 0.5 at the model's decision boundary.

foreground_mask(prediction[, output_class, channels, ...])

Where the model says foreground: its probability, averaged over channels, over threshold.

affinities(prediction, output_class, offsets)

The mutex watershed's input: the probability of an affinity model's

remove_small(labels[, min_size])

labels with objects of fewer than min_size voxels made background.

connected_components(mask[, connectivity, min_size, ...])

An id per connected object of mask (its nonzero voxels): uint64,

components(prediction, output_class, channels[, ...])

The objects of the components method: the foreground_mask of

panel_px(viewer_px, state)

(across, down): the size in screen pixels of the panel the field

field_of_view_nm(viewer_px, state, pixel_nm, ...[, ...]) → list

The size in nm (z, y, x) of the box the viewer's panel shows: the

Module Contents

cellmap_flow.portable.label_it.SEED_METHODS = ('components', 'mutex_watershed', 'distance_watershed', 'instances')
cellmap_flow.portable.label_it.UNIT = 'unit'
cellmap_flow.portable.label_it.SIGNED_UNIT = 'signed_unit'
cellmap_flow.portable.label_it.UNBOUNDED = 'unbounded'
cellmap_flow.portable.label_it.CONNECTIVITIES
cellmap_flow.portable.label_it.classify_output_range(lo: float, hi: float) → str | None

Bucket an observed (min, max) output range into an activation class; None for a flat one, max - min within the tolerance.

A flat output is no evidence: jrc_mus-livers_16nm_to_8nm_peroxisome gave -0.9595 everywhere on the server’s noise, read as a tanh’s [-1, 1], when its network ends in a Conv (logits); a sigmoid saturated everywhere would be as flat at 1. Whatever the values, a range they do not span says nothing of what the output can be.

cellmap_flow.portable.label_it.as_connectivity(value, ndim=3) → int

value as scipy’s connectivity for an ndim-D array: 1 up to ndim.

Forms send strings, so “2” is 2. A value past ndim (3 for one 2D slice) is ndim: every neighbour, which is what it asked for. Raises ValueError for anything that is not 1, 2 or 3, None among them.

cellmap_flow.portable.label_it.as_bool(value) → bool

A form’s “true”/”false” as a bool; anything else by Python’s truth.

cellmap_flow.portable.label_it.seed_options(data) → dict

A seed request’s options, from its JSON body, with the defaults the routes give an absent one: method (“components”), threshold (a probability, 0.5, the model’s own boundary), min_size (0), connectivity (1, faces) and per_slice (false).

Raises ValueError, which both modes answer 400, for a threshold outside 0 to 1, a negative or non-integer size, a method not in SEED_METHODS and a connectivity that is not 1, 2 or 3. A connectivity given as null is refused with the rest, not read as absent: the key is there.

cellmap_flow.portable.label_it.affinity_offsets(channel_names)

The offsets of a model known by its channel names alone, or None when they name no affinity channel.

The server’s rule for a model whose script says none (dashboard/routes/finetune/common.autodetect_output_type): a model with a channel named *_aff* is an affinity model, and its first affinity channels, up to three, compare each voxel with the next one along z, y and x in turn, whatever their names say after _aff.

cellmap_flow.portable.label_it.seed_channels(output_type, offsets, select_channel=None)

The channels a seed reads, as a slice, or None for every one.

As the trainer targets them: the selected channel; else an affinity model’s offset channels, the first len(offsets) (the rest, LSDs say, are masked out of its loss); else every channel, which a binary target is broadcast over. offsets is a list of the model’s offsets. A slice past the model’s channels reads those there are (probability).

cellmap_flow.portable.label_it.placement(lo, hi, voxel_size, dataset_offset_nm, translation, declared, shape, drawn=None)

Which voxel of a served array each annotation voxel of [lo, hi) reads: the one its centre lies in, so a seed from a model served at another voxel size (any served model can seed, not only the one the volume is for) lies under that model’s layer.

The volume’s grid is its voxel_size from dataset_offset_nm, the centre of its voxel 0 (its OME translation, as finetune/session/volume.volume_corner_nm reads it; None is 0). The served array’s is its OME translation (voxel 0’s centre too) and declared voxel size, over shape voxels; drawn is the voxel size Neuroglancer draws it at when that is another (viewer.layers.prediction_voxel_override), which keeps the served offset in voxels, so the array’s corner moves with it.

Returns (indices, lo, hi): per axis, the served index of each annotation voxel the array covers, and the box shrunk to those voxels; None when it covers none along some axis.

cellmap_flow.portable.label_it.read_box(indices)

(lo, hi): the box of the served array the indices span, the one to read for gather.

cellmap_flow.portable.label_it.gather(region, indices)

The prediction on the annotation grid: each annotation voxel the served voxel placement gave it. region is (channels, z, y, x), the served box read_box gives; with the two grids equal, all of it in order.

cellmap_flow.portable.label_it.probability(prediction, output_class=None, channels=None)

The prediction as a probability, (channels, z, y, x), 0.5 at the model’s decision boundary.

The boundary depends on the output’s activation: 0.5 on [0, 1] output (a sigmoid already applied; the cellmap distance models end in one, and 0.5 is their object boundary), 0 on tanh’s [-1, 1], and 0 on unbounded output, logits or a signed distance alike. Each is turned into a probability, so channels (a slice: the affinity channels, say) can be averaged and one threshold means the same for every model. A slice running past the model’s channels reads those there are; one starting past them is refused. output_class is the server’s (its network’s last op, or its probe’s: inference/runner.py), or read off the values when it has none, and taken as logits when they are flat (classify_output_range). Read off the values is a guess: logits that lie within [0, 1] read as probabilities. It is kept because the values are real predictions (the ranking’s under the paint, a seed’s in its box), nearer the truth than the probe’s noise, and a model whose graph says its activation never comes to it.

cellmap_flow.portable.label_it.foreground_mask(prediction, output_class=None, channels=None, threshold=0.5)

Where the model says foreground: its probability, averaged over channels, over threshold.

threshold is in probability whatever the activation: 0.5 is the model’s own boundary. Strictly over: a voxel at it is background.

cellmap_flow.portable.label_it.affinities(prediction, output_class, offsets)

The mutex watershed’s input: the probability of an affinity model’s offset channels, the first len(offsets) (as seed_channels takes them; fewer when the model has fewer).

cellmap_flow.portable.label_it.remove_small(labels, min_size=0)

labels with objects of fewer than min_size voxels made background.

The rest are numbered 1..n again, in the order they had. labels must already be numbered without large gaps (any segmenter’s output): the sizes are counted per id.

cellmap_flow.portable.label_it.connected_components(mask, connectivity=1, min_size=0, per_slice=False)

An id per connected object of mask (its nonzero voxels): uint64, 0 for background and 1..n with no id skipped, numbered as scipy.ndimage.label numbers them.

per_slice: each z slice of a (z, y, x) mask is labelled on its own, in 2D, its ids following the slice before’s. For data whose objects are 2D (what Cellpose sees), or annotations painted slice by slice: an object touching itself across slices is still one per slice. min_size: objects of fewer voxels are background (specks a threshold let through); per slice, each slice’s piece is counted alone.

cellmap_flow.portable.label_it.components(prediction, output_class, channels, threshold=0.5, min_size=0, connectivity=1, per_slice=False)

The objects of the components method: the foreground_mask of the prediction over channels, an id per connected object (connected_components).

cellmap_flow.portable.label_it.FIELD_OF_VIEW_MAX_VOXELS = 4194304
cellmap_flow.portable.label_it.NEUROGLANCER_TOP_ROW_PX = 27
cellmap_flow.portable.label_it.panel_px(viewer_px, state)

(across, down): the size in screen pixels of the panel the field of view is taken from, in a viewer frame viewer_px (width, height) pixels big showing state (the viewer’s state, its JSON); None in a layout with no slice panel (“3d”) or one not in _PANEL_SHARE (a row of viewers).

The page measures its frame, not the panel: in the server’s mode the frame holds neuroglancer’s own page, of another origin, whose panels the page cannot read. So the panel is the frame less neuroglancer’s top row (NEUROGLANCER_TOP_ROW_PX) and the side panels the state shows (_SIDE_PANELS), and then its share of what is left (_PANEL_SHARE).

cellmap_flow.portable.label_it.field_of_view_nm(viewer_px, state, pixel_nm, depth_axis, display_dimensions, voxel_size, depth_nm=None) → list

The size in nm (z, y, x) of the box the viewer’s panel shows: the box every Label it tool covers, centred where the viewer looks, when the page asks for the field of view.

In the plane, what the panel shows at its zoom: its size in screen pixels (panel_px of viewer_px, the page’s viewer frame, and state, the viewer’s state as JSON) times pixel_nm, the nm one screen pixel spans (viewer.raw.view_of’s, the browser’s viewPixelNm): its width along the data axis it draws across, its height along the one it draws down (ai_annotate.screen_axes of depth_axis in a viewer showing display_dimensions). Along depth_axis, the axis the panel looks along by the AI annotation’s plane rule, it is depth_nm (the Box field’s depth along it, when it gives one), else one annotation voxel of voxel_size (nm, z, y, x): the slice on screen.

Raises ValueError, which both modes answer 400: without the frame’s size or the viewer’s zoom, in a layout with no slice panel or side panels that leave it none, and for a view of more than FIELD_OF_VIEW_MAX_VOXELS annotation voxels, saying how far to zoom in.