segmentations
Build per-class segmentation overlays for a served scan, as masks or vectors.
Segmentations are already cached as raw model output in model_inferences; this
reads them and applies the same just-in-time preprocessing the consuming steps
use, then emits either or both of two forms of the one class set — the class
selection, the drawable-type check and name normalisation all live in
_instances_by_class, so the two forms agree on which classes were selected and
what each is called. Which classes reach each fragment can still differ: a class
whose raster loses every set pixel to downscaling ships vectors and no mask.
Masks are rasterised via the shared parse_mask_json and scaled to the
dimensions the frames were actually served at, as 1-bit palette PNGs with a
transparent background so a UI can colour and composite them itself rather than
receiving pre-overlaid pixels. to_masks_index names each one.
Vectors are the same classes' drawable instances in the model's own pixel space:
_vector_instance builds each VectorInstance from a raw instance dict, field
by allowlisted field, truncating coordinates exactly as parse_mask_json does
so a vector lands on the same pixels as the raster before it is scaled, and
to_vectors_index serves them. The shipped PNG is downscaled to the served dims
while vectors stay in mask space, which is what scale_x/scale_y are for: they
carry no raster, so a client that draws its own overlays pays no PNG encode and
scales them by the per-source factors the index reports.
Module
Functions
build_segmentations
def build_segmentations( *, cache: CacheProtocol, file_id: str, frames: list[FrameDims], class_filter: frozenset[str] | None = None, want_masks: bool = True, want_vectors: bool = False,) ‑> SegmentationPayload:Build the overlay payload for a scan's served frames.
Arguments
cache: The pod background cache holding the inference rows.file_id: The scan's raw file path, as stored on the inference rows. Must not be a resolved real path: the rows key on the path the ingest recorded.frames: Each served frame's native and served dimensions, in served order. Dimensions rather than pixels, so a warm frames cache layer can supply them without the scan being decoded again.class_filter: Optional set of normalised class names to restrict output to. None (the default) keeps every class any model produced. Which classes exist depends on the model that ran, so a name in the filter that no model produced simply yields no output for it — never an error.want_masks: Whether to rasterise and encode a PNG per class.want_vectors: Whether to carry each class's raw drawable instances.
Returns
A payload describing every frame, with empty sources where no
segmentation is available.
Raises
ValueError:framesdoes not carry the indices0..len(frames) - 1exactly once each, in order.
merge_index_fragments
def merge_index_fragments( *, masks_index: str | None, vectors_index: str | None,) ‑> str | None:Merge the overlay layers' index fragments into one served index.
Both fragments carry the same class metadata (name, label, colour,
probability) plus their own payload — the mask path from one,
instances from the other. Both layer keys carry the same frame identity,
inference fingerprint and class filter, so a mask fragment cannot pair with
a vector fragment from another inference or another selection. Where both
are present the masks fragment supplies the metadata.
The merged document's frames are the masks fragment's frames. Both fragments
are checked against the frames being served before they reach here, so both
list the same frames; a pair that has not been checked would lose whatever
frames the masks fragment omits. Frame order is not relied on either way —
every lookup here is keyed by frame["index"], never by position.
Arguments
masks_index: The masks layer's fragment, or None.vectors_index: The vectors layer's fragment, or None.
Returns
The merged segmentations.json document, or None when neither layer
contributed anything.
normalise_class_name
def normalise_class_name(name: str) ‑> str:Normalise a class name to the convention stored masks use.
Shared by _instances_by_class (a model's own className values) and the
caller-supplied class_filter, so a display-style filter name (e.g.
"RPE Atrophy") matches the stored, underscored form ("rpe_atrophy").
Arguments
name: A raw class name.
Returns
The lowercased name with every non-word character replaced by _.
Classes
ClassMask
class ClassMask( name: str, label: str, colour: tuple[int, int, int], probability: float | None, png: bytes | None, filename: str, instances: tuple[VectorInstance, ...],):One segmentation class's mask for one frame.
Arguments
name: Normalised class name (e.g. "rpe_atrophy").label: Human-readable label for display.colour: Advisory RGB colour for display.probability: Highest instance probability for this class on this frame, or None when the model reported none.png: The 1-bit palette PNG bytes, sized to the served frame, or None when masks were not requested or downscaling emptied the raster.filename: Path of the mask within the archive.instances: The class's raw drawable instances, in the model's own pixel space. Empty when vectors were not requested.
Variables
- static
colour : tuple[int, int, int]
- static
filename : str
- static
instances : tuple[VectorInstance, ...]
- static
label : str
- static
name : str
- static
png : bytes | None
- static
probability : float | None
FrameSegmentations
class FrameSegmentations( index: int, filename: str, width: int, height: int, source_width: int, source_height: int, sources: list[SourceSegmentations],):All segmentation output for a single served frame.
Arguments
index: Frame index, matchingframe_NNN.webp.filename: The frame's filename within the archive, so a consumer ofsegmentations.jsonneed not reconstruct it fromindex.width: Served frame width in pixels.height: Served frame height in pixels.source_width: The frame's native width.source_height: The frame's native height.sources: One entry per model that produced output for this frame; empty when nothing did.
Variables
- static
filename : str
- static
height : int
- static
index : int
- static
source_height : int
- static
source_width : int
- static
sources : list[SourceSegmentations]
- static
width : int
SegmentationPayload
class SegmentationPayload(frames: list[FrameSegmentations]):The complete overlay payload for one scan.
Arguments
frames: One entry per served frame, in frame order.
Variables
- static
frames : list[FrameSegmentations]
Methods
counts
def counts(self) ‑> tuple[int, int, int]:Return what this payload actually carries, for logging.
A payload is structurally complete whether or not any model produced
anything for the scan: a frame with no segmentation carries an empty
sources rather than being omitted, so len(frames) says nothing
about whether there is anything to draw. These three numbers do.
Returns The number of frames with at least one source, the number of class entries across every frame and source, and the number of vector instances across all of them.
mask_files
def mask_files(self) ‑> list[tuple[str, bytes]]:Return (archive path, PNG bytes) for every mask.
Returns One pair per class mask with a rendered PNG, in frame then source then class order.
to_masks_index
def to_masks_index(self) ‑> str:Return the masks layer's index fragment.
Held inside the masks cache layer as masks_index.json and merged into
the served segmentations.json by merge_index_fragments. Each class
entry names its mask's archive-relative path, matching mask_files. A
class whose png is None (masks were not requested, or downscaling
emptied the raster) carries no mask key and is omitted.
Returns
A JSON document whose frames[].sources[].classes[].mask values are
archive-relative paths.
to_vectors_index
def to_vectors_index(self) ‑> str:Return the vectors layer's index fragment.
Held inside the vectors cache layer and merged into the served
segmentations.json by merge_index_fragments. A class with no
instances (vectors were not requested) carries no instances key and
is omitted.
Returns
A JSON document whose frames[].sources[].classes[].instances
values are lists of VectorInstance.to_json().
SourceSegmentations
class SourceSegmentations( model_ref: str, model_version: int, kind: str, mask_width: int, mask_height: int, dims_match_source: bool, scale_x: float, scale_y: float, classes: list[ClassMask],):Every class one model produced for one frame.
Arguments
model_ref: The model that produced these masks.model_version: The model's version.kind: Which pipeline recognised the output ("pathology" or "retinal_layers").mask_width: Width of the model's own coordinate space.mask_height: Height of the model's own coordinate space.dims_match_source: Whether that space matches the frame's native dims. False means the masks were stretched to fit, which a client may want to treat with suspicion.scale_x: Factor to scale a vector instance's x coordinates by to reach the served frame's space (served width / mask width).scale_y: Factor to scale a vector instance's y coordinates by to reach the served frame's space (served height / mask height).classes: One entry per class present on this frame.
Variables
- static
classes : list[ClassMask]
- static
dims_match_source : bool
- static
kind : str
- static
mask_height : int
- static
mask_width : int
- static
model_ref : str
- static
model_version : int
- static
scale_x : float
- static
scale_y : float
VectorInstance
class VectorInstance( type: str, probability: float | None, points: tuple[int, ...] | None = None, cx: int | None = None, cy: int | None = None, rx: int | None = None, ry: int | None = None, angle: int | None = None,):One drawable segmentation instance, in the model's own pixel space.
Coordinates are integers produced by truncation, matching how
parse_mask_json rasterises them (np.int32 for points, int() for
ellipse fields). Truncating here rather than rounding keeps a vector
pixel-identical to the mask shipped beside it.
Arguments
type: The instance type: "polygon", "polyline" or "ellipse".probability: The model's confidence for this instance, or None when it reported none.points: Flat[x0, y0, x1, y1, …]for polygon and polyline.cx: Ellipse centre x.cy: Ellipse centre y.rx: Ellipse semi-axis x.ry: Ellipse semi-axis y.angle: Ellipse rotation in degrees.
Variables
- static
angle : int | None
- static
cx : int | None
- static
cy : int | None
- static
points : tuple[int, ...] | None
- static
probability : float | None
- static
rx : int | None
- static
ry : int | None
- static
type : str
Methods
to_json
def to_json(self) ‑> dict[str, typing.Any]:Return the served form, carrying only this type's own geometry.
Returns
A mapping with type, probability, and either points or the
ellipse fields. Fields that do not apply are omitted rather than
emitted as null.