Skip to content

COCOObjectDetectionResult

Represents one prediction in the COCO "results" format: a per-image, per-category detection with a confidence score.

Stale class docstring in source

This class's docstring in source (and its embedded usage example) describes (N,)-shaped array attributes -- that text is stale, left over from before COCOObjectDetectionResult/COCOObjectDetectionResults were split into separate singular/plural classes. The Parameters/Attributes documented below reflect the actual (scalar) constructor and properties, not that docstring.

Parameters

FieldTypeDescription
image_idintSource image id.
category_idintCategory id.
image_heightintSource image height, > 0.
image_widthintSource image width, > 0.
scorefloatDetection confidence score, >= 0. Stored at float32 precision so a serialization round-trip can't silently change the value.
bboxnp.ndarray | list[float] | list[int] | NoneOptional box in [x, y, w, h] format, shape (4,). At least one of bbox/segmentation must be provided.
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
ValueErrorBoth bbox and segmentation are None.
ValueErrorbbox is provided but 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
image_idintSource image id.
category_idintCategory id.
image_heightintSource image height.
image_widthintSource image width.
scorefloatDetection confidence score.
bboxnp.ndarray | NoneDefensive copy of the box, shape (4,), [x, y, w, h], or None.
segmentationdict[str, Any] | NoneDefensive copy of the canonical encoded COCO RLE dict, or None.

Type hints in source are misleading here

The image_id/category_id/image_height/image_width/score properties are type-hinted as returning np.ndarray, and segmentation as list[dict | None] | None -- both leftover from the plural class this one was adapted from. At runtime they return plain Python int/float/dict/None as documented in the table above; the constructor's _validate casts every scalar field with int(...)/float(np.float32(...)).

Methods

MethodDescription
COCOObjectDetectionResult.coerce(value)Returns value unchanged if it's already a COCOObjectDetectionResult; if it's a dict, constructs one via COCOObjectDetectionResult(**value). Raises TypeError for any other input.
COCOObjectDetectionResult.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" (RLE and mask are self-describing). min_points/epsilon only affect conversion to "polygon" (drop contours with fewer than min_points vertices; simplify with cv2.approxPolyDP when epsilon > 0). A stateless class method shared with COCOObjectDetectionResults, COCOObjectDetectionAnnotation, and COCOObjectDetectionAnnotations. Raises ImportError if converting to "polygon" and OpenCV isn't installed; raises TypeError/ValueError for invalid formats.

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 COCOObjectDetectionResult raises AttributeError: 'COCOObjectDetectionResult' 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 COCOObjectDetectionResult with equal image_id, category_id, image_height, image_width, score, bbox (element-wise), and segmentation (size and counts); NotImplemented if b isn't a COCOObjectDetectionResult.
hash(result)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
├── image_id:            int32
├── category_id:         int32
├── bbox:                fixed_size_list<float32>[4]   (null when bbox is None)
├── image_height:        int32
├── image_width:         int32
├── score:               float32
├── segmentation_height: int32    (0 when segmentation is None)
├── segmentation_width:  int32    (0 when segmentation is None)
└── segmentation_count:  string   (null when segmentation is None)

The RLE dict isn't stored as a nested struct: its size is split into segmentation_height/segmentation_width and its counts becomes the flat segmentation_count string field. A null segmentation_count is the sole marker that this result has no segmentation -- there's no separate validity field.

Visualization

datatypes.visualize(result, entity_path=...): when bbox is present, logs a single rr.Boxes2D at {entity_path}/boxes with class_ids=[category_id] and labels=[f"{score:.2f}"]. 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} ({score:.2f})").

Example

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

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