Skip to content

COCOObjectDetectionResults

Represents a batch of predictions in the COCO "results" format: per-image, per-category detections with confidence scores.

Parameters

FieldTypeDescription
image_idsnp.ndarray | list[int](N,) source image ids.
category_idsnp.ndarray | list[int](N,) category ids.
image_heightsnp.ndarray | list[int](N,) source image heights, each > 0.
image_widthsnp.ndarray | list[int](N,) source image widths, each > 0.
scoresnp.ndarray | list[float](N,) detection scores, each >= 0.
bboxesnp.ndarray | list[list[float]] | NoneOptional (N, 4) boxes in [x, y, w, h] format. At least one of bboxes/segmentations must be given.
segmentationslist[dict[str, Any] | list | None] | NoneOptional length-N list, each entry a polygon, an RLE dict, or None. Normalized to encoded COCO RLE. See SegmentationFormat.

Raises

ExceptionCondition
ValueErrorBoth bboxes and segmentations are None.
ValueErrorbboxes isn't shape (N, 4), or any of image_ids/category_ids/image_heights/image_widths/scores/segmentations doesn't have length N (N = len(image_ids)).
ValueErrorimage_heights/image_widths contains a value <= 0.
ValueErrorscores contains a negative value.
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
image_idsnp.ndarrayDefensive copy, shape (N,) int32, source image ids.
category_idsnp.ndarrayDefensive copy, shape (N,) int32, category ids.
image_heightsnp.ndarrayDefensive copy, shape (N,) int32, source image heights.
image_widthsnp.ndarrayDefensive copy, shape (N,) int32, source image widths.
scoresnp.ndarray | NoneDefensive copy, shape (N,) float32, detection scores. None only for an empty (N=0) batch -- the constructor requires scores, but an empty array is normalized to None internally.
bboxesnp.ndarray | NoneDefensive copy, shape (N, 4) float32, [x, y, w, h], or None if no boxes were provided.
segmentationslist[dict[str, Any] | None] | NoneDefensive copy: a canonical encoded COCO RLE dict or None per entry, or None if no segmentations were provided.

Methods

MethodDescription
COCOObjectDetectionResults.coerce(value)Returns value unchanged if it's already a COCOObjectDetectionResults; if it's a dict, constructs one via COCOObjectDetectionResults(**value). Raises TypeError for any other input.
results.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. Entries are always stored as canonical RLE, so only the target format needs to be specified.
COCOObjectDetectionResults.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 COCOObjectDetectionResult, COCOObjectDetectionAnnotation, and COCOObjectDetectionAnnotations. Raises ImportError if converting to "polygon" and OpenCV isn't installed.

Operators

OperationBehavior
len(results)Number of results N in the batch.
results[i]An int returns a single COCOObjectDetectionResult. A slice or boolean np.ndarray mask returns a new COCOObjectDetectionResults 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 COCOObjectDetectionResults with equal image_ids, category_ids, bboxes, image_heights, image_widths, scores, and segmentations; NotImplemented if b isn't a COCOObjectDetectionResults.
hash(results)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
├── image_ids:            list<int32>                        (inner length N)
├── category_ids:         list<int32>                        (inner length N)
├── bboxes:               list<fixed_size_list<float32>[4]>  (inner length N; all-null when bboxes is None)
├── image_heights:        list<int32>                        (inner length N)
├── image_widths:         list<int32>                        (inner length N)
├── scores:               list<float32>                      (inner length N; empty when scores is None)
├── 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_counts:  list<string>                       (inner length N, null per row without a segmentation)

As with the singular COCOObjectDetectionResult, each row's RLE size/counts are split into flat segmentation_heights/segmentation_widths/segmentation_counts columns rather than a nested struct, and a null entry in segmentation_counts is the sole per-row validity marker.

Visualization

datatypes.visualize(results, entity_path=...): when bboxes is present, logs one batched rr.Boxes2D at {entity_path}/boxes with class_ids=category_ids and labels "cat={category_id}", each suffixed with " ({score:.2f})" when scores is present. 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 the same "cat={category_id}" + score suffix).

Example

python
"""Demonstrates the Telekinesis COCOObjectDetectionResults datatype."""

import time

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

from telekinesis import datatypes

def coco_object_detection_results_example():
    """Demonstrate creation, indexing, segmentation conversion, visualization, and serialization."""

    # ======================= Case 1: Bounding Boxes Only =======================
    H, W = 720, 1280
    bbox_results = datatypes.COCOObjectDetectionResults(
        image_ids=np.array([7, 7], dtype=np.int32),
        category_ids=np.array([1, 2], dtype=np.int32),
        image_heights=np.array([H, H], dtype=np.int32),
        image_widths=np.array([W, W], dtype=np.int32),
        scores=np.array([0.95, 0.82], dtype=np.float32),
        bboxes=np.array(
            [[0, 0, 10, 10], [20, 20, 5, 5]],
            dtype=np.float32,
        ),
    )
    logger.info(f"Original COCOObjectDetectionResults: {bbox_results}")

    # ======================= Inspect ===========================================
    image_ids = bbox_results.image_ids
    category_ids = bbox_results.category_ids
    image_heights = bbox_results.image_heights
    image_widths = bbox_results.image_widths
    scores = bbox_results.scores
    bboxes = bbox_results.bboxes
    segmentations = bbox_results.segmentations

    logger.info(f"Number of results in batch: {len(bboxes)}")
    logger.info(
        f"image_ids={image_ids}, "
        f"category_ids={category_ids}, "
        f"image_heights={image_heights}, "
        f"image_widths={image_widths}, "
        f"scores={scores}"
    )
    logger.info(f"Bboxes: {bboxes}")
    logger.info(f"Segmentations: {segmentations}")

    # ======================= Index =============================================
    index = 0
    detection_at_index = bbox_results[index]
    image_id = detection_at_index.image_id
    category_id = detection_at_index.category_id
    bbox = detection_at_index.bbox
    score = detection_at_index.score

    logger.info(
        f"ObjectDetectionResult at index {index}: "
        f"image_id={image_id}, "
        f"category_id={category_id}, "
        f"bbox={bbox}, "
        f"score={score}"
    )

    # ======================= Visualize =========================================
    rr.init("coco_object_detection_results_example", spawn=True)
    datatypes.visualize(bbox_results, entity_path="/COCOObjectDetectionResults")

    first_result = bbox_results[0]
    datatypes.visualize(first_result, entity_path="/COCOObjectDetectionResults/FirstResult")

    # ======================= Case 2: Bbox + Segmentation =======================
    bbox_segmentation_results = datatypes.COCOObjectDetectionResults(
        image_ids=np.array([7, 7], dtype=np.int32),
        category_ids=np.array([1, 2], dtype=np.int32),
        image_heights=np.array([H, H], dtype=np.int32),
        image_widths=np.array([W, W], dtype=np.int32),
        scores=np.array([0.95, 0.82], dtype=np.float32),
        bboxes=np.array([[0, 0, 10, 10], [20, 20, 5, 5]], dtype=np.float32),
        segmentations=[
            [[0, 2, 10, 0, 10, 10, 0, 10]],
            [[22, 20, 25, 20, 25, 25, 20, 25]],
        ],
    )
    datatypes.visualize(
        bbox_segmentation_results, entity_path="/COCOObjectDetectionResultsWithSegmentation"
    )

    # ======================= Case 3: Segmentation Only =========================
    segmentation_only_results = datatypes.COCOObjectDetectionResults(
        image_ids=np.array([7, 7], dtype=np.int32),
        category_ids=np.array([1, 2], dtype=np.int32),
        image_heights=np.array([H, H], dtype=np.int32),
        image_widths=np.array([W, W], dtype=np.int32),
        scores=np.array([0.95, 0.82], dtype=np.float32),
        segmentations=[
            [[0, 2, 10, 0, 10, 10, 0, 10]],
            [[22, 20, 25, 20, 25, 25, 20, 25]],
        ],
    )
    datatypes.visualize(
        segmentation_only_results, entity_path="/COCOObjectDetectionResultsWithOnlySegmentation"
    )

    # ======================= Convert Segmentation ==============================
    as_rle = bbox_segmentation_results.segmentations[0]
    as_polygon = bbox_segmentation_results.get_segmentation(0, "polygon")
    as_mask = bbox_segmentation_results.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.COCOObjectDetectionResults.convert_segmentation(
        as_mask, "mask", "rle"
    )
    polygon_to_mask = datatypes.COCOObjectDetectionResults.convert_segmentation(
        as_polygon,
        datatypes.SegmentationFormat.POLYGON,
        datatypes.SegmentationFormat.MASK,
        height=H,
        width=W,
    )
    logger.info(f"Mask converted back to RLE: {mask_back_to_rle}")
    logger.info(
        f"Polygon converted to mask: shape={polygon_to_mask.shape}, dtype={polygon_to_mask.dtype}"
    )

    # ======================= Serialize / Deserialize ===========================
    start = time.perf_counter()
    serialized = datatypes.serialize(bbox_results)
    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 COCOObjectDetectionResults: {deserialized}")
    logger.info(f"Round-trip successful: {deserialized == bbox_results}")
    logger.info(f"Serialization time: {serialization_ms:.3f} ms")
    logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")


if __name__ == "__main__":
    coco_object_detection_results_example()