Skip to main content

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: frames does not carry the indices 0..len(frames) - 1 exactly 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 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, matching frame_NNN.webp.
  • filename: The frame's filename within the archive, so a consumer of segmentations.json need not reconstruct it from index.
  • 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 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

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 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.