Skip to content

COCOObjectDetectionAnnotation

Represents one ground-truth annotation in the COCO annotations format: an image/category pair with a bounding box and an optional segmentation.

Parameters

FieldTypeDescription
idintAnnotation id.
image_idintSource image id.
category_idintCategory id.
bboxnp.ndarray | list[float] | list[int]Box in [x, y, w, h] format, shape (4,). Required.
image_heightintSource image height, > 0.
image_widthintSource image width, > 0.
segmentationdict[str, Any] | list[list[float] | list[int]] | NoneOptional segmentation: a polygon (one or more flattened [x1, y1, x2, y2, ...] coordinate lists), a COCO RLE dict (compressed counts as str/bytes, or uncompressed counts as list[int]), or None. Normalized to an encoded COCO RLE dict internally. See SegmentationFormat.

Raises

ExceptionCondition
ValueErrorbbox isn't shape (4,).
ValueErrorimage_height or image_width is <= 0.
TypeErrorsegmentation is neither None, a valid polygon list, nor an RLE dict with size/counts keys.
ValueErrorsegmentation is an already-encoded RLE dict whose size doesn't match (image_height, image_width).

Attributes

AttributeTypeDescription
idintAnnotation id.
image_idintSource image id.
category_idintCategory id.
bboxnp.ndarrayDefensive copy of the box, shape (4,), [x, y, w, h]. Always present.
image_heightintSource image height.
image_widthintSource image width.
segmentationdict[str, Any] | NoneDefensive copy of the canonical encoded COCO RLE dict, or None.

Methods

MethodDescription
COCOObjectDetectionAnnotation.coerce(value)Returns value unchanged if it's already a COCOObjectDetectionAnnotation; if it's a dict, constructs one via COCOObjectDetectionAnnotation(**value). Raises TypeError for any other input.
COCOObjectDetectionAnnotation.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 (source_type/target_type each accept a SegmentationFormat member or its string value). height/width are required only when source_type is "polygon". min_points/epsilon only affect conversion to "polygon". A stateless class method shared with COCOObjectDetectionAnnotations, COCOObjectDetectionResult, and COCOObjectDetectionResults. Raises ImportError if converting to "polygon" and OpenCV isn't installed.

get_segmentation is broken on this class

get_segmentation(idx, target_type) is also inherited from the same shared segmentation mixin, but it assumes a self._segmentations list -- the plural classes' internal storage. This class stores a single self._segmentation instead, so calling get_segmentation on a COCOObjectDetectionAnnotation raises AttributeError: 'COCOObjectDetectionAnnotation' object has no attribute '_segmentations'. Use the segmentation property together with convert_segmentation instead -- there's only one entry, so no index is needed.

Operators

OperationBehavior
a == bTrue only if b is also a COCOObjectDetectionAnnotation with equal id, image_id, category_id, bbox (element-wise), image_height, image_width, and segmentation (size and counts); NotImplemented if b isn't a COCOObjectDetectionAnnotation.
hash(ann)Not supported (__hash__ = None), despite the object being immutable after construction.

Serialization

to_pyarrow/from_pyarrow round-trip through a length-1 pa.StructArray:

StructArray length 1
├── id:                  int32
├── image_id:            int32
├── category_id:         int32
├── bbox:                fixed_size_list<float32>[4]   (always present, never null)
├── image_height:        int32
├── image_width:         int32
├── segmentation_height: int32    (0 when segmentation is None)
├── segmentation_width:  int32    (0 when segmentation is None)
└── segmentation_count:  string   (null when segmentation is None)

Unlike COCOObjectDetectionResult, the bbox field has no null branch since it's always required here. As with the other three COCO classes, the RLE dict is flattened into segmentation_height/segmentation_width/segmentation_count rather than stored as a nested struct.

Visualization

datatypes.visualize(ann, entity_path=...) logs one rr.Boxes2D at {entity_path}/boxes (the box is always present) with class_ids=[category_id] and labels=[str(id)]. When segmentation is present, additionally decodes the RLE and logs an rr.SegmentationImage at {entity_path}/masks/mask0, with a two-class rr.AnnotationContext (transparent background, foreground labeled "cat={category_id}" -- no score suffix, since annotations have no score field).

Example

COCOObjectDetectionAnnotation has no standalone example file -- it's demonstrated as the element produced by indexing a COCOObjectDetectionAnnotations batch (single = annotations[index]) in that class's example, embedded verbatim below.

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()