Skip to content

Contours

Represents a batch of contours, each an independent ordered list of 2D polygon vertices.

Parameters

FieldTypeDescription
pointslist[list[list[int]]] | list[np.ndarray]Length-N list of (K_i, 2) point arrays, one variable-length contour per annotation; each element is converted independently to a contiguous int64 array.

Raises

ExceptionCondition
ValueErrorpoints is not a list (note: ValueError, not TypeError, even though this is a container-type mismatch — verified empirically), or any element, after conversion to int64, isn't 2-D or its second axis isn't length 2

Conversion of each element to int64 isn't wrapped in a try/except; a value NumPy can't cast raises whatever error NumPy itself produces.

Attributes

AttributeTypeDescription
pointslist[np.ndarray]Length-N list of defensive copies, one int64 (K_i, 2) array per contour.

Methods

MethodDescription
Contours.coerce(value)Returns value unchanged if it's already a Contours; wraps a list of point arrays into one otherwise. Raises TypeError for anything else.

Operators

OperationBehavior
contours == otherTrue only if other is a Contours with the same number of contours and elementwise-equal coordinates for every contour. NotImplemented (so False) for any other type.
len(contours)The number of contours, N.
contours[i]An int returns a single Contour for that row (supports negative indices; raises IndexError out of range). A slice or boolean np.ndarray mask returns a new Contours sub-batch (raises ValueError for a wrongly-sized mask). Raises TypeError for any other index type.
hash(contours)Not supported, despite being immutable after construction.
repr(contours)Contours(num_contours=2, total_points=5), or Contours(num_contours=0) when empty — verified empirically.

Visualization

datatypes.visualize(contours, entity_path=...) logs every contour as a closed rerun 2D line strip (rr.LineStrips2D(strips)), each with its first vertex repeated at the end to close the loop. Contours has no registered label handler — passing label= to visualize() is silently ignored (no error, no label rendered).

Example

python
"""Demonstrates the Telekinesis Contours datatype."""

import time

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

from telekinesis import datatypes

def contours_example():
    """Demonstrate creation, inspection, visualization, indexing, empty contours, and serialization."""

    # ======================= Create ============================================
    contour_1 = np.array([[118, 84], [134, 79], [150, 86], [139, 115]], dtype=np.int64)
    contour_2 = np.array([[210, 145], [238, 140], [276, 160], [245, 190]], dtype=np.int64)
    contour_3 = np.array([[322, 212], [335, 211], [332, 225], [320, 218]], dtype=np.int64)
    contours = datatypes.Contours(points=[contour_1, contour_2, contour_3])
    logger.info(f"Original Contours: {contours}")

    # ======================= Inspect ===========================================
    points = contours.points
    num_contours = len(contours)

    logger.info(f"Points: {points}")
    logger.info(f"First contour points: {points[0]}")
    logger.info(f"Number of contours: {num_contours}")

    # ======================= Visualize =========================================
    rr.init("contours_example", spawn=True)
    datatypes.visualize(contours, entity_path="/Contours")

    # ======================= Index =============================================
    first_contour = contours[0]
    sub_batch = contours[1:]

    logger.info(f"First contour: {first_contour}")
    logger.info(f"Sub-batch of contours [1:]: {sub_batch}")

    # ======================= Empty Contours ====================================
    empty_points = np.empty((0, 2), dtype=np.int64)
    empty_contours = datatypes.Contours(points=[empty_points])
    logger.info(f"Empty Contours: {empty_contours}")

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


if __name__ == "__main__":
    contours_example()