COCOObjectDetectionAnnotation
Represents one ground-truth annotation in the COCO annotations format: an image/category pair with a bounding box and an optional segmentation.
Parameters
| Field | Type | Description |
|---|---|---|
id | int | Annotation id. |
image_id | int | Source image id. |
category_id | int | Category id. |
bbox | np.ndarray | list[float] | list[int] | Box in [x, y, w, h] format, shape (4,). Required. |
image_height | int | Source image height, > 0. |
image_width | int | Source image width, > 0. |
segmentation | dict[str, Any] | list[list[float] | list[int]] | None | Optional 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
| Exception | Condition |
|---|---|
ValueError | bbox isn't shape (4,). |
ValueError | image_height or image_width is <= 0. |
TypeError | segmentation is neither None, a valid polygon list, nor an RLE dict with size/counts keys. |
ValueError | segmentation is an already-encoded RLE dict whose size doesn't match (image_height, image_width). |
Attributes
| Attribute | Type | Description |
|---|---|---|
id | int | Annotation id. |
image_id | int | Source image id. |
category_id | int | Category id. |
bbox | np.ndarray | Defensive copy of the box, shape (4,), [x, y, w, h]. Always present. |
image_height | int | Source image height. |
image_width | int | Source image width. |
segmentation | dict[str, Any] | None | Defensive copy of the canonical encoded COCO RLE dict, or None. |
Methods
| Method | Description |
|---|---|
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
| Operation | Behavior |
|---|---|
a == b | True 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.
"""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()
