Skip to content

COCOObjectDetectionAnnotations

Represents a batch of ground-truth annotations in the COCO annotations format.

Parameters

FieldTypeDescription
idslist[int] | np.ndarray(N,) annotation ids.
image_idslist[int] | np.ndarray(N,) source image ids.
category_idslist[int] | np.ndarray(N,) category ids.
bboxeslist[list[float]] | np.ndarray(N, 4) boxes in [x, y, w, h] format. Required (unlike COCOObjectDetectionResults, there's no bbox-vs-segmentation "at least one" rule).
image_heightslist[int] | np.ndarray(N,) source image heights, each > 0.
image_widthslist[int] | np.ndarray(N,) source image widths, each > 0.
segmentationslist[Any] | np.ndarray | NoneOptional length-N list, each entry a polygon, an RLE dict, or None. Normalized to encoded COCO RLE. See SegmentationFormat.

Raises

ExceptionCondition
ValueErrorbboxes isn't shape (N, 4) (N = bboxes.shape[0]).
ValueErrorAny of ids/image_ids/category_ids/image_heights/image_widths/segmentations doesn't have length N.
ValueErrorimage_heights/image_widths contains a value <= 0.
TypeErrorAn entry in segmentations is neither None, a valid polygon list, nor an RLE dict with size/counts keys.
ValueErrorAn entry in segmentations is an already-encoded RLE dict whose size doesn't match that row's (image_height, image_width).

Attributes

AttributeTypeDescription
idsnp.ndarrayDefensive copy, shape (N,) int32, annotation ids.
image_idsnp.ndarrayDefensive copy, shape (N,) int32, source image ids.
category_idsnp.ndarrayDefensive copy, shape (N,) int32, category ids.
bboxesnp.ndarrayDefensive copy, shape (N, 4) float32, [x, y, w, h]. Always present.
image_heightsnp.ndarrayDefensive copy, shape (N,) int32, source image heights.
image_widthsnp.ndarrayDefensive copy, shape (N,) int32, source image widths.
segmentationslist[dict[str, Any] | None] | NoneDefensive copy: a canonical encoded COCO RLE dict or None per entry, or None if no segmentations were provided.
masksnp.ndarrayDecodes every segmentation into one (N, H, W) bool array (one plane per annotation; entries without a segmentation are all-False). Assumes every annotation shares the same (H, W) -- taken from row 0's image_height/image_width. Returns shape (0, 0, 0) when the batch is empty.

Methods

MethodDescription
COCOObjectDetectionAnnotations.coerce(value)Returns value unchanged if it's already a COCOObjectDetectionAnnotations; if it's a dict, constructs one via COCOObjectDetectionAnnotations(**value). Raises TypeError for any other input.
COCOObjectDetectionAnnotations.from_binary_masks(ids, image_ids, category_ids, bboxes, masks)Alternate constructor: builds a batch from a non-empty list of N binary (H, W) masks (heights/widths may differ per mask). Each mask's own shape supplies image_heights/image_widths, and each mask is encoded to COCO RLE to populate segmentations. Not available on COCOObjectDetectionResults. Raises ValueError if masks is empty, or if any array's length doesn't match N = len(masks).
anns.get_segmentation(idx, target_type)Returns entry idx's segmentation converted to target_type (a SegmentationFormat member or its string value), or None if there are no segmentations or entry idx has none.
COCOObjectDetectionAnnotations.convert_segmentation(segmentation, source_type, target_type, *, height=None, width=None, min_points=3, epsilon=0.0)Converts a segmentation between "rle", "polygon", and "mask" representations. height/width are required only when source_type is "polygon". min_points/epsilon only affect conversion to "polygon". A stateless class method shared with COCOObjectDetectionAnnotation, COCOObjectDetectionResult, and COCOObjectDetectionResults. Raises ImportError if converting to "polygon" and OpenCV isn't installed.

Operators

OperationBehavior
len(anns)Number of annotations N in the batch.
anns[i]An int returns a single COCOObjectDetectionAnnotation. A slice or boolean np.ndarray mask returns a new COCOObjectDetectionAnnotations sub-batch. Raises IndexError for an out-of-range int, ValueError for a boolean mask of the wrong length, TypeError for any other index type.
a == bTrue only if b is also a COCOObjectDetectionAnnotations with equal ids, image_ids, category_ids, bboxes, image_heights, image_widths, and segmentations; NotImplemented if b isn't a COCOObjectDetectionAnnotations.
hash(anns)Not supported (__hash__ = None), despite the object being immutable after construction.

Serialization

to_pyarrow/from_pyarrow round-trip through a length-1 pa.StructArray whose fields are each a length-1 pa.ListArray wrapping the full length-N batch:

StructArray length 1
├── ids:                  list<int32>                        (inner length N)
├── image_ids:            list<int32>                        (inner length N)
├── category_ids:         list<int32>                        (inner length N)
├── bboxes:               list<fixed_size_list<float32>[4]>  (inner length N)
├── image_heights:        list<int32>                        (inner length N)
├── image_widths:         list<int32>                        (inner length N)
├── segmentation_heights: list<int32>                        (inner length N, 0 per row without a segmentation)
├── segmentation_widths:  list<int32>                        (inner length N, 0 per row without a segmentation)
├── segmentation_valid:   list<bool>                         (inner length N; True iff that row has a mask)
└── segmentation_counts:  list<string>                       (inner length N, null per row without a segmentation)

Unlike COCOObjectDetectionResults, which relies solely on a null segmentation_counts entry as the per-row validity marker, this class also carries an explicit segmentation_valid boolean column.

Visualization

datatypes.visualize(anns, entity_path=...) logs one batched rr.Boxes2D at {entity_path}/boxes (boxes are always present) with class_ids=category_ids and labels=[str(id) for id in ids] (annotation ids, not category labels or scores -- there's no score field). For every non-None entry in segmentations, additionally decodes the RLE and logs an rr.SegmentationImage at {entity_path}/masks/mask{i}, each with its own two-class rr.AnnotationContext (transparent background, foreground labeled "cat={category_id}").

Example

python
"""Demonstrates the Telekinesis COCOObjectDetectionAnnotations datatype."""

import time

import numpy as np
from loguru import logger
import rerun as rr

from telekinesis import datatypes

def object_detection_annotations_example():
    """Demonstrate creation, access, indexing, segmentation format conversion, construction from masks, and serialization."""

    # ======================= Create ============================================
    H, W = 720, 1280
    annotations = datatypes.COCOObjectDetectionAnnotations(
        ids=np.array([0, 1], dtype=np.int32),
        image_ids=np.array([7, 7], dtype=np.int32),
        category_ids=np.array([1, 2], dtype=np.int32),
        bboxes=np.array([[0, 0, 10, 10], [20, 20, 5, 5]], dtype=np.float32),
        image_heights=np.array([H, H], dtype=np.int32),
        image_widths=np.array([W, W], dtype=np.int32),
        segmentations=[
            [[0, 2, 10, 0, 10, 10, 0, 10]],
            [[22, 20, 25, 20, 25, 25, 20, 25]],
        ],
    )
    logger.info(f"Original COCOObjectDetectionAnnotations: {annotations}")

    # ======================= Inspect ===========================================
    logger.info(f"Number of annotations in batch: {len(annotations)}")
    logger.info(
        f"ids={annotations.ids}, "
        f"image_ids={annotations.image_ids}, "
        f"category_ids={annotations.category_ids}, "
        f"image_heights={annotations.image_heights}, "
        f"image_widths={annotations.image_widths}"
    )
    logger.info(f"bboxes={annotations.bboxes}")
    logger.info(f"segmentations={annotations.segmentations}")

    # ======================= Visualize =========================================
    rr.init("object_detection_example", spawn=True)
    datatypes.visualize(annotations, entity_path="/COCOObjectDetectionAnnotations")

    # ======================= Index =============================================
    index = 0
    single = annotations[index]
    logger.info(f"Single ObjectDetectionAnnotation at index {index}: {single}")
    logger.info(
        f"id={single.id}, "
        f"image_id={single.image_id}, "
        f"category_id={single.category_id}, "
        f"bbox={single.bbox}"
    )
    logger.info(f"segmentation={single.segmentation}")
    datatypes.visualize(single, entity_path="/SingleObjectDetectionAnnotation")

    # ======================= Convert ===========================================
    as_rle = annotations.segmentations[0]
    as_polygon = annotations.get_segmentation(0, "polygon")
    as_mask = annotations.get_segmentation(0, "mask")
    logger.info(f"Segmentation 0 as RLE: {as_rle}")
    logger.info(f"Segmentation 0 as polygon: {as_polygon}")
    logger.info(f"Segmentation 0 as mask: shape={as_mask.shape}, dtype={as_mask.dtype}")

    mask_back_to_rle = datatypes.COCOObjectDetectionAnnotations.convert_segmentation(
        as_mask, "mask", "rle"
    )
    logger.info(f"Mask converted back to RLE: {mask_back_to_rle}")

    # ======================= From Masks ========================================
    masks = [
        annotations.get_segmentation(i, datatypes.SegmentationFormat.MASK)
        for i in range(len(annotations))
    ]
    from_masks = datatypes.COCOObjectDetectionAnnotations.from_binary_masks(
        ids=annotations.ids,
        image_ids=annotations.image_ids,
        category_ids=annotations.category_ids,
        bboxes=annotations.bboxes,
        masks=masks,
    )
    logger.info(f"Built from binary masks: {from_masks}")

    # ======================= Serialize / Deserialize ===========================
    start = time.perf_counter()
    serialized = datatypes.serialize(annotations)
    serialization_ms = (time.perf_counter() - start) * 1000

    start = time.perf_counter()
    deserialized = datatypes.deserialize(serialized)["param_0"]
    deserialization_ms = (time.perf_counter() - start) * 1000

    logger.info(f"Deserialized COCOObjectDetectionAnnotations: {deserialized}")
    logger.info(f"Round-trip successful: {annotations == deserialized}")
    logger.info(f"Serialization time: {serialization_ms:.3f} ms")
    logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")


if __name__ == "__main__":
    object_detection_annotations_example()