Examples¶
Everything here is a snippet you can copy: no dependency beyond NumPy and Pillow, no
pycocotools, and no code of ours is needed to read what the API returns. Class names and the
mask palette always come from the dataset's own description, GET /datasets/{dataset}, rather
than from a figure or a blog post — fetch it once per session and keep it.
Reading a semantic mask (gwfss)¶
A gwfss row's annotation is an image: mask_url points at a three-channel PNG where each
pixel's color names its class. The dataset's own description carries the palette, so the mapping is
read rather than hard-coded — label_masks.py keeps that palette only as a
convenience constant, and takes the description's classes as its argument.
import numpy as np
from PIL import Image
from label_masks import GWFSS_CLASSES, label_masks
description = requests.get(f"{API}/datasets/gwfss", headers=KEY).json()
palette = {c["name"]: c["color"] for c in description["classes"]} # names come from here
mask = np.asarray(Image.open(mask_url))
for name, boolean in label_masks(mask, description["classes"]).items():
print(name, boolean.sum())
label_masks asserts that no color outside the palette appears, so a mask carrying a class the
dataset never declared fails loudly instead of being silently mislabeled. Background is one of the
classes; a head mask is masks["head"], never masks[1].
Decoding an object's RLE (gwhs)¶
An instance dataset carries geometry, not a mask image. The detail response for one row holds each object's box, category, area, attributes, and its run-length mask:
{"idx": 0, "category": "spikelet_-_side1", "bbox": [231.0, 923.0, 126.0, 162.0],
"area": 12074.0, "attributes": ["Green-Spikelet"],
"rle": {"size": [2509, 592], "counts": "Zofa05S^2;E;E;F8K4L5K4L4M3L5L2O1N2O1N3M2M3…"}}
rle.size is [height, width] and rle.counts is the annotation's compressed string, verbatim.
Decoding is the twenty lines in rle_decode.py — and its
JavaScript and Kotlin ports:
from rle_decode import rle_decode
height, width = annotation["rle"]["size"]
flat = rle_decode(annotation["rle"]["counts"], (height, width))
mask = np.array(flat).reshape(height, width, order="F") # COCO is column-major
Decode a row's objects, stack them, and mask.any(axis=0) gives the row's union; flat.sum() equals
the area the response reports for that object, which is the cheapest check that a decoder is right.
The API never decodes anything: it returns the string the annotation was published with, unmodified.