Source code for olo.vision.types

"""Value types for camera geometry: pixels, boxes, intrinsics and observations."""

from dataclasses import dataclass
from datetime import datetime
from typing import Protocol

from olo.spatial import Point, Transform, Vector


class FrameLookup(Protocol):
    """Protocol for namespace-scoped frame lookup handles."""

    def lookup(
        self,
        target_frame: str,
        source_frame: str,
        *,
        at: datetime | None = None,
        timeout: float = 5.0,
    ) -> Transform: ...


[docs] @dataclass(frozen=True, slots=True) class Pixel: """Pixel coordinate, zero-based from the top-left of the image.""" x: float y: float
[docs] @dataclass(frozen=True, slots=True) class BoundingBox: """Half-open image bounding box with XYXY pixel coordinates.""" minimum: Pixel maximum: Pixel def __post_init__(self) -> None: if self.maximum.x <= self.minimum.x or self.maximum.y <= self.minimum.y: raise ValueError("BoundingBox maximum must be greater than minimum")
[docs] @classmethod def from_xyxy(cls, x_min: float, y_min: float, x_max: float, y_max: float) -> "BoundingBox": """Create a half-open bounding box from ``x_min, y_min, x_max, y_max``.""" return cls(minimum=Pixel(x_min, y_min), maximum=Pixel(x_max, y_max))
[docs] @classmethod def from_xywh(cls, x: float, y: float, width: float, height: float) -> "BoundingBox": """Create a half-open bounding box from ``x, y, width, height``.""" if width <= 0 or height <= 0: raise ValueError("BoundingBox width and height must be positive") return cls.from_xyxy(x, y, x + width, y + height)
@property def width(self) -> float: """Return the box width in pixels.""" return self.maximum.x - self.minimum.x @property def height(self) -> float: """Return the box height in pixels.""" return self.maximum.y - self.minimum.y @property def center(self) -> Pixel: """Return the box centre pixel.""" return Pixel(x=(self.minimum.x + self.maximum.x) / 2.0, y=(self.minimum.y + self.maximum.y) / 2.0)
[docs] def to_xyxy(self) -> tuple[float, float, float, float]: """Return ``x_min, y_min, x_max, y_max``.""" return (self.minimum.x, self.minimum.y, self.maximum.x, self.maximum.y)
[docs] def to_xywh(self) -> tuple[float, float, float, float]: """Return ``x, y, width, height``.""" return (self.minimum.x, self.minimum.y, self.width, self.height)
[docs] @dataclass(frozen=True, slots=True) class CameraIntrinsics: """Pinhole camera calibration parsed from sensor_msgs/CameraInfo.""" width: int height: int fx: float fy: float cx: float cy: float frame_id: str = "" stamp: datetime | None = None distortion_model: str = "" distortion: tuple[float, ...] = ()
[docs] @dataclass(frozen=True, slots=True) class PointObservation: """Stamped 3D point observation in the camera optical frame.""" point: Point frame_id: str pixel: Pixel depth_metres: float stamp: datetime | None = None
[docs] @dataclass(frozen=True, slots=True) class AlignmentError: """Image-space alignment error.""" x_px: float y_px: float distance_px: float converged: bool
[docs] @dataclass(frozen=True, slots=True) class PositionError: """Cartesian position error.""" vector: Vector distance: float converged: bool frame_id: str = ""
__all__ = [ "AlignmentError", "BoundingBox", "CameraIntrinsics", "FrameLookup", "Pixel", "PointObservation", "PositionError", ]