cellmap_flow.portable.fill

Labelling a box of an annotation volume: the decisions behind the Finetune tab’s Seed, All Background and Split Objects and an accepted AI annotation (finetune/session/fill.py says what each is for), on arrays alone, so the server makes them over MinIO’s zarr and the browser over its paint store’s chunks (portable/fill_calls.py for the Label it tools, portable/ai_annotate_calls.py for an accept).

  • box_voxels: the annotation voxels a box in world nm covers.

  • seed_labels: the ids a box gets from a segmentation: the id the user painted on an object kept, fresh ones for the rest (fresh_ids).

  • relabel_objects: the box’s foreground relabelled by connected component.

  • paint, rewrite and restore: which voxels a write changes and an undo puts back, with the counts the routes answer.

Nothing here reads or writes a volume: the callers read the box, hand it in and write back what comes out (finetune/session/fill.py through zarr over MinIO, web/src/backend/routes/view-labels.ts and web/src/backend/ai-annotate-job.ts through the paint store’s web/src/ai-annotate/fill.ts).

Functions

volume_corner_nm(dataset_offset_nm, output_voxel_size) → numpy.ndarray

The world position of an annotation volume's voxel-0 lower corner, in nm.

box_voxels(volume, offset_nm, shape_nm, volume_shape)

(lo, hi): the annotation voxels whose centres lie in a box, clipped to the volume.

fresh_ids(existing, count, count_up)

count ids for new objects in a box that holds existing.

counts_up(instance_target, dtype) → bool

Whether new objects' ids count up from the box's largest (fresh_ids):

seed_labels(objects, existing[, count_up])

Labels for a box from a segmentation: an id per object inside, 1 outside.

connected_components(mask[, connectivity, per_slice])

An id per connected object of mask (its nonzero voxels), 1..n, 0 elsewhere.

relabel_objects(existing[, count_up, connectivity, ...])

The box's foreground (labels 2 and up) relabelled by connected component.

paint(existing, labels[, overwrite])

The box after labels are written over existing, and what that wrote.

rewrite(existing, labels)

The box after labels are written over its foreground voxels, and how many changed.

restore(current, before, after)

The box put back to before where it still holds after: an undo.

Module Contents

cellmap_flow.portable.fill.volume_corner_nm(dataset_offset_nm, output_voxel_size) → numpy.ndarray

The world position of an annotation volume’s voxel-0 lower corner, in nm.

dataset_offset_nm is voxel 0’s centre, the OME translation, as finetune.session.volume.volume_corner_nm reads it (its docstring says why), so the corner is half a voxel before it; voxel 0’s corner is at minus half a voxel when the volume has no offset.

cellmap_flow.portable.fill.box_voxels(volume, offset_nm, shape_nm, volume_shape)

(lo, hi): the annotation voxels whose centres lie in a box, clipped to the volume.

offset_nm and shape_nm are the box’s lower corner and size in world nm, as a good region records them; volume is the volume’s record, for its grid (output_voxel_size, dataset_offset_nm). None when no voxel of the volume is in the box.

cellmap_flow.portable.fill.fresh_ids(existing, count, count_up)

count ids for new objects in a box that holds existing.

count_up: from one past the box’s largest label, so an affinity target’s objects stay apart from every object nearby (ids elsewhere in the volume are not looked at: affinities only compare nearby voxels). Else the lowest ids the box does not use, so a uint8 volume’s 254 ids are never run through: a repeat in another box is only a repeated colour, which a binary or distance target does not see. Raises ValueError when the dtype has no room.

cellmap_flow.portable.fill.counts_up(instance_target, dtype) → bool

Whether new objects’ ids count up from the box’s largest (fresh_ids): for a model trained on instances (instance_target: affinities, or Cellpose’s flows) whose volume holds more than 8-bit ids. A uint8 volume reuses the ids its box leaves free, whatever the model, or its 254 would soon run out.

cellmap_flow.portable.fill.seed_labels(objects, existing, count_up=False)

Labels for a box from a segmentation: an id per object inside, 1 outside.

objects is a segmenter’s output over the box (0 background, objects 1..n), existing what the box holds now. Each object gets an id of its own (fresh_ids; count_up for an instance target), so neighbouring objects are told apart, and a merge the model made shows as one colour to split; but an object the user has already painted part of takes the id painted there most, or the unpainted rest of it would be taught as a different object from its painted part. A binary or distance target reads every id as foreground, so the ids cost it nothing.

cellmap_flow.portable.fill.connected_components(mask, connectivity=1, per_slice=False)

An id per connected object of mask (its nonzero voxels), 1..n, 0 elsewhere.

scipy.ndimage.label with the structure of connectivity (1 face neighbours, 2 and edges, 3 every neighbour, at most the array’s dimensions), numbered as it numbers them: by the raster order of each object’s first voxel. 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. As post.segment.connected_components labels a seed’s objects, without its minimum size.

cellmap_flow.portable.fill.relabel_objects(existing, count_up=False, connectivity=1, per_slice=False)

The box’s foreground (labels 2 and up) relabelled by connected component.

Each component takes the id most of its voxels hold, so a stroke that joins two objects merges them into the id of the bigger. An id left on several components, an object a background wall cut in two, stays on the largest and the others get fresh ids (fresh_ids). Background (1) and unpainted (0) voxels are left alone. Returns (labels, counts): counts has objects, split (components given a fresh id) and merged (components that held more than one id).

A cut has to go through every slice the object spans: the brush paints one slice, and an object cut in one slice is still one object above and below it. split is 0 then, which the page says. With per_slice each z slice’s pieces are components on their own, so a wall in one slice splits that slice, and every slice of an object gets its own id. connectivity: as for connected_components.

cellmap_flow.portable.fill.paint(existing, labels, overwrite=False)

The box after labels are written over existing, and what that wrote.

A label of 0 leaves its voxel as it is. Without overwrite only unannotated voxels (0) are written, as for a seed: what the user painted stays. With it every voxel the labels cover is written, painted or not, for a result the user has looked at and prefers to what is there. Returns (after, (n_foreground, n_background, n_overwritten)): the box as it is then, of existing’s dtype, or None when nothing would change; how many voxels were written with foreground (2 and up) and with background, and how many of those held a label before.

cellmap_flow.portable.fill.rewrite(existing, labels)

The box after labels are written over its foreground voxels, and how many changed.

Only foreground voxels (2 and up) whose label changed are written, so a stroke painted meanwhile elsewhere in the box is kept (Split Objects relabels the box it read). Returns (after, n_changed), after None when none changed.

cellmap_flow.portable.fill.restore(current, before, after)

The box put back to before where it still holds after: an undo.

Only voxels the action changed and nobody has touched since go back, so a stroke painted in the box after the action is kept. Returns (restored, n): the box then, or None when nothing goes back, and how many voxels did.