cellmap_flow.portable.ai_annotate
AI-assisted annotation’s maths, which both modes run: which plane of the data an annotation reads and which voxels it writes, the image the model is sent, the mask in the image it sends back, and the labels that mask becomes.
The server runs it in its process (ai_annotate/geometry.py,
pipeline.py, mask_extraction.py, staging.py and the Vertex
backend call it, around their zarr reads and PNG files), the browser in
chunkmirage’s Pyodide worker (portable/ai_annotate_calls.py, around the
engine’s raw reads and the paint store). Nothing here reads data, calls a
model or sees a credential: what is sent and to whom stays with each mode’s
provider.
The plane. A click (or the view centre) picks a point in world nm and the
plane the user is looking at picks the depth axis. plan_plane turns
those into a PlanePlan: a square field of view around the point,
snapped to the annotation volume’s voxels and clipped to the volume (the
write box, one annotation voxel thick along the depth axis), and the raw
read that covers the same physical area at the model’s input voxel size (one
input voxel thick, at the point’s depth). So whatever the model paints on
the image lands on the annotation voxels under it. Annotation voxels are
placed as finetune.session.volume places them: the volume’s
dataset_offset_nm is voxel 0’s centre, and a voxel is in a box when
its centre is (fill.box_voxels). The raw is read on its own grid, from
the voxel that holds the first pixel’s centre (raw_box).
Axes are z, y, x throughout, as everywhere in cellmap-flow, except in the
image sent: that is laid out as the viewer’s panel shows the plane
(to_screen), so the model, and the user reviewing its answer, see what
was on screen. Which axis runs down it and which across, and whether across
runs backwards, depend on the viewer’s display dimensions (screen_axes):
cellmap-flow’s viewers show x, y, z, so the XY plane is drawn as in z, y, x
order, the XZ plane too, and the YZ plane transposed and mirrored.
The images are resampled by Pillow (resize, nearest, and the
model’s reply in model_mask), in both modes: the server’s installed
Pillow, and in the browser the one Pyodide ships, which chunkmirage’s worker
loads the first time a call imports it (scipy, for the mask’s objects,
likewise).
Attributes
Exceptions
A plane the point cannot have, outside the volume or the data: the |
Classes
One plane to annotate: what is read, what is sent and what is written. |
Functions
|
|
|
|
|
|
|
A plane's |
|
|
|
|
|
The plane through |
|
|
|
|
|
A 2D |
|
The plane sent, from the raw |
|
A 2D array as uint8 grey levels (from ask-to-mask's |
|
The pixels of the model's reply (rows, cols, 3 or 4, uint8) painted |
|
The mask in a model's reply, and the reply lined up with the plane: |
|
|
|
The objects of |
|
The labels for the write box: an id per object of |
|
What Accept writes into the write box: |
Module Contents
- cellmap_flow.portable.ai_annotate.AXIS_NAMES = ('z', 'y', 'x')
- cellmap_flow.portable.ai_annotate.MAX_IMAGE_SIDE = 1024
- cellmap_flow.portable.ai_annotate.PLANE_NAMES = ('XY', 'XZ', 'YZ')
- cellmap_flow.portable.ai_annotate.OVERLAY_ALPHA = 0.5
- exception cellmap_flow.portable.ai_annotate.Refused
A plane the point cannot have, outside the volume or the data: the message says so, for the user (the server raises it as an
AIAnnotateError("refused"), the browser’s calls answer it).
- cellmap_flow.portable.ai_annotate.snap_integral(values)
valuesas floats, with near-whole numbers made exactly whole (io.metadata.snap_integral): voxel arithmetic in nm leaves 511.99999999 where 512 is meant, and a floor or a round after it must not see the difference.
- cellmap_flow.portable.ai_annotate.display_names(display_dimensions=None) tuple
display_dimensionswhen they are z, y and x in some order, else z, y, x: the display dimensions of a viewer that sets none over cellmap-flow’s coordinate space, and of a plan saved before plans kept them.
- cellmap_flow.portable.ai_annotate.screen_axes(depth_axis, display_dimensions=None) tuple
(down, across, backwards): the data axes (0 z, 1 y, 2 x) the panel looking alongdepth_axisdraws down and across, and whether across runs backwards, in a viewer whose display dimensions aredisplay_dimensions, asdisplay_namesgives them (a plan’s), or None for z, y, x.
- cellmap_flow.portable.ai_annotate.screen_shape(shape, depth_axis, display_dimensions=None) tuple
A plane’s
(rows, cols)in z, y, x order, as the panel draws it (screen_axes).
- cellmap_flow.portable.ai_annotate.to_screen(plane, depth_axis, display_dimensions=None) numpy.ndarray
plane(rows and columns its two axes in z, y, x order) as the panel looking alongdepth_axisdraws it, in a viewer showingdisplay_dimensions(screen_axes).
- cellmap_flow.portable.ai_annotate.from_screen(image, depth_axis, display_dimensions=None) numpy.ndarray
to_screenundone: an image as the panel draws it, in z, y, x order.
- class cellmap_flow.portable.ai_annotate.PlanePlan
One plane to annotate: what is read, what is sent and what is written.
All three-element tuples are z, y, x.
raw_offset_nmandraw_shape_nmare the world box the raw is read over (lower corner and size): in plane, the write box’s extent; along the depth axis, one input voxel centred on the point.image_shapeis the (rows, cols) of the image sent, laid out as on screen (to_screen),resolution_nmits pixel size in nm (square pixels, which a model needs to see shapes as they are).write_loandwrite_hiare the annotation voxels written,[lo, hi), clipped to the volume and one voxel thick along the depth axis.display_dimensionsare the viewer’s (display_names), which say how its panel draws the plane (screen_axes); z, y, x in a plan saved before plans kept them.- depth_axis: int
- point_nm: tuple
- raw_offset_nm: tuple
- raw_shape_nm: tuple
- image_shape: tuple
- input_voxel_size: tuple
- write_lo: tuple
- write_hi: tuple
- resolution_nm: float
- display_dimensions: tuple = ('z', 'y', 'x')
- property plane_axes: tuple
The two in-plane axes, in z, y, x order.
- property plane_name: str
the data axes the plane shows.
- Type:
“XY”, “XZ” or “YZ”
- property write_shape: tuple
The write box’s in-plane shape in annotation voxels, its
plane_axesin z, y, x order.
- property click_px: tuple
(row, col)of the point in the image sent.
- to_screen(plane) numpy.ndarray
to_screenfor this plan’s plane.
- from_screen(image) numpy.ndarray
from_screenfor this plan’s plane.
- to_dict() dict
The plan as JSON-safe lists, for a staging’s meta.json and the browser.
- cellmap_flow.portable.ai_annotate.plan_plane(volume: dict, volume_shape, point_nm, depth_axis: int, crop_size_px: int, data_box_nm=None, display_dimensions=None) PlanePlan
The plane through
point_nm(world nm, z, y, x) acrossdepth_axis.volumeis the annotation volume’s record (output_voxel_size,input_voxel_size,dataset_offset_nm),volume_shapeitsannotation/s0shape. The field of view is square in nm,crop_size_pxannotation voxels along the finer in-plane axis (both, for isotropic voxels), centred on the point; its write box is the annotation voxels whose centres lie in it, clipped to the volume, in the annotation voxel that holds the point along the depth axis.data_box_nmis the raw data’s extent,(lo, hi)world nm z, y, x: the field is clipped to it too. The volume is the data rounded up to whole chunks of the model’s output, which for Cellpose at 64 nm is up to 33 um past the data’s edge, and there the plane is only the read’s zero padding: an image of it was labelled as background, and the cells beside it smeared into it.The raw is read over the clipped box’s extent, so the image and the box cover the same physical area. The image’s pixels are the finer in-plane input voxel size, square; when that makes a side longer than
MAX_IMAGE_SIDEthe pixels are made larger to fit. The image is laid out as the viewer’s panel draws the plane, for the viewer’sdisplay_dimensions(z, y, x when None).Raises
Refusedwhen the point is outside the volume or the data: a field clipped to an edge it does not reach is not what was aimed at. ValueError for a depth axis, crop size or point that is not one.
- cellmap_flow.portable.ai_annotate.raw_box(plan: PlanePlan, voxel_size, corner) tuple
(begin, count): the raw voxels to read forplan, on a raw grid ofvoxel_sizewhose voxel 0’s lower corner iscorner(nm, z, y, x).Each axis starts at the voxel that holds the first sample’s centre (half a voxel in from the box’s corner) and runs for as many voxels as fit in the box, at least one: so a box on the grid reads exactly its voxels, and the depth axis reads the voxel that holds the point.
- cellmap_flow.portable.ai_annotate.resize(plane, shape) numpy.ndarray
planeresampled toshape(rows, cols), float32; itself when it already is that.Shrinking (neither side grows) averages each pixel’s footprint (Pillow’s box filter), so fine membranes fade rather than alias; otherwise (the coarse axis of an anisotropic plane) it interpolates linearly (Pillow’s bilinear), on Pillow’s 32-bit float (“F”) image.
- cellmap_flow.portable.ai_annotate.nearest(array, shape) numpy.ndarray
A 2D
array(uint8 or int32, as Pillow holds an image of each) atshape(rows, cols) by nearest neighbour, pixel centre to pixel centre (Pillow’s NEAREST), in its own dtype; a copy when it already is that size.
- cellmap_flow.portable.ai_annotate.plane_image(block, plan: PlanePlan) numpy.ndarray
The plane sent, from the raw
blockread overraw_box’s voxels (z, y, x).Its one slice along the depth axis, laid out as on screen (
PlanePlan.to_screen) and resampled to the plan’s image (resize). The raw’s dtype is kept unless the plane had to be resized to the image’s grid, when it is float32.
- cellmap_flow.portable.ai_annotate.normalize_to_uint8(data: numpy.ndarray) numpy.ndarray
A 2D array as uint8 grey levels (from ask-to-mask’s
_normalize_to_uint8).uint8 passes through unchanged. Anything else is clipped to its 1st-99th percentile over the plane and stretched to 0-255, as raw EM is not always 8-bit and a few saturated pixels should not flatten the contrast.
- cellmap_flow.portable.ai_annotate.extract_mask(rgb, target_rgb, threshold: float = 200.0) numpy.ndarray
The pixels of the model’s reply (rows, cols, 3 or 4, uint8) painted
target_rgb, as a bool (rows, cols) mask (ask-to-mask’sextract_mask, its colour-difference mode).Each pixel scores how strongly it shows the target colour: its weakest channel among those the target has at full strength, minus its strongest deviation elsewhere – a channel the target has off (0) being on, or a channel the target has at part strength being away from that strength. For red (255, 0, 0) that is
R - max(G, B): red that dominates. A pixel counts when its score reachesthreshold.The part-strength term is what lets orange (255, 128, 0) or purple (128, 0, 255) be found at all: counting their half channel as “on” would cap the score at 128, under the threshold, and counting it as “off” would reject the colour itself.
- cellmap_flow.portable.ai_annotate.model_mask(rgb, sent_shape, image_shape, target_rgb)
The mask in a model’s reply, and the reply lined up with the plane:
(mask, aligned).rgbis the image the model sent back, decoded (rows, cols, 3 or 4, uint8),sent_shapethe (rows, cols) of the image it was sent (the plane padded to a square),image_shapethe plane’s. A reply at another size (or shape: stretching it then is what the review shows) is resized back to the sent size by Pillow’s bilinear filter on an RGB image (which averages when shrinking), then the padding is cut off the right and bottom, so what is left lines up with the plane; the mask is the pixels paintedtarget_rgbin that (extract_mask).alignedis (rows, cols, 3) uint8, the plane’s shape, for the review.
- cellmap_flow.portable.ai_annotate.overlay(rgb, mask, target_rgb, alpha: float = OVERLAY_ALPHA) numpy.ndarray
rgb(the image sent, rows, cols, 3 or 4) with the pixelsmaskmarks tintedtarget_rgb: the review’s third picture, what accepting writes, not the model’s own image.maskis the write box’s objects laid out as the image is (to_screen) and scaled to it by nearest neighbour; uint8 RGB back, rounded half to even.
- cellmap_flow.portable.ai_annotate.mask_to_write_shape(mask: numpy.ndarray, plan: PlanePlan) numpy.ndarray
The objects of
mask(the image’s shape, as on screen) on the write box’s in-plane voxels, in z, y, x order (PlanePlan.write_shape): int32, 0 for background and 1..n for the objects, numbered in the box’s raster order asscipy.ndimage.labelnumbers a mask.Objects are the mask’s 4-connected pieces (scipy’s default, so pieces touching only at a corner stay apart, as the model was asked to leave them), found at the image’s own resolution, where the model drew the lines between them: on voxels coarser than the pixels a line can fall between the pixels the voxels take, and the objects either side of it still keep ids of their own. Then nearest neighbour, pixel centre to voxel centre: each annotation voxel takes the object of the pixel at its centre, so the mask keeps hard edges and lands where it was drawn whatever the two sizes are. An object no voxel centre falls in is lost.
- cellmap_flow.portable.ai_annotate.labels_for_box(objects: numpy.ndarray, existing: numpy.ndarray, depth_axis: int, count_up: bool, beside=None) numpy.ndarray
The labels for the write box: an id per object of
objects, 1 elsewhere.objectsis the in-plane objects (mask_to_write_shape), 0 for background,existingthe one-voxel-thick box as the volume holds it. The objects are labelled byfill.seed_labels: an object the user has already painted part of keeps that id, the rest get fresh ones, counting up for an instance target’scount_up.besideis the planes on either side of the box along the depth axis, as the volume holds them (each shaped likeexisting, or None at the volume’s edge). Planes are annotated one at a time, so an object usually continues one in the plane next to it: where this plane is unpainted, the ids beside it count as painted here, and an object takes the id it overlaps most, rather than a fresh one that an instance target would read as a boundary between the two planes. Fresh ids skip those planes’ ids too, so a new object does not take the id of a different object right next to it, which an instance target would read as one object.
- cellmap_flow.portable.ai_annotate.accept_labels(objects, existing, depth_axis: int, instance_target: bool, overwrite: bool, beside=None) numpy.ndarray
What Accept writes into the write box:
labels_for_box’s labels, as a seed’s ids (counting up for an instance target on a volume of more than 8-bit ids,fill.counts_up; an object continuing one in a planebesidekeeps its id), forfill.paintto write.With
overwritethe model’s objects replace what is under them, but its background only fills unlabelled voxels: an object already painted that the model missed is kept, not erased to background (those voxels get 0, “leave as it is”).