Skip to content

evalio.types

Classes:

  • Duration

    Duration class for representing a positive or negative delta time.

  • Experiment

    An experiment is a single run of a pipeline on a dataset.

  • ExperimentStatus

    Status of the experiment.

  • FailedMetadataParse

    Exception raised when metadata parsing fails.

  • GroundTruth

    Metadata for ground truth trajectories.

  • ImuMeasurement

    ImuMeasurement is a simple structure for storing an IMU measurement.

  • ImuParams

    ImuParams is a structure for storing the parameters of an IMU

  • LidarMeasurement

    LidarMeasurement is a structure for storing a point cloud measurement, with a timestamp and a vector of points.

  • LidarParams

    LidarParams is a structure for storing the parameters of a lidar sensor.

  • Metadata

    Base class for metadata associated with a trajectory.

  • Point

    Point is the general point structure in evalio, with common point cloud attributes included.

  • SE3

    SE3 class for representing a 3D rigid body transformation using a quaternion and a translation vector.

  • SO3

    SO3 class for representing a 3D rotation using a quaternion.

  • Stamp

    Stamp class for representing an absolute point in time.

  • Trajectory

    A trajectory of poses with associated timestamps and metadata.

Attributes:

  • Param

    A parameter value for a pipeline, can be a bool, int, float, or str.

Param module-attribute

Param = bool | int | float | str

A parameter value for a pipeline, can be a bool, int, float, or str.

Duration

Duration class for representing a positive or negative delta time.

Uses int64 as the underlying data storage for nanoseconds.

Methods:

  • __add__

    Add two Durations

  • __eq__

    Check for equality

  • __gt__

    Compare two Durations

  • __lt__

    Compare two Durations

  • __ne__

    Check for inequality

  • __sub__

    Compute the difference between two Durations

  • from_nsec

    Create a Duration from nanoseconds

  • from_sec

    Create a Duration from seconds

  • to_nsec

    Convert to nanoseconds

  • to_sec

    Convert to seconds

Attributes:

  • nsec (int) –

    Underlying nanoseconds representation

Source code in python/evalio/_cpp/types.pyi
class Duration:
    """
    Duration class for representing a positive or negative delta time. 

    Uses int64 as the underlying data storage for nanoseconds.
    """

    @staticmethod
    def from_sec(sec: float) -> Duration:
        """Create a Duration from seconds"""

    @staticmethod
    def from_nsec(nsec: int) -> Duration:
        """Create a Duration from nanoseconds"""

    def to_sec(self) -> float:
        """Convert to seconds"""

    def to_nsec(self) -> int:
        """Convert to nanoseconds"""

    @property
    def nsec(self) -> int:
        """Underlying nanoseconds representation"""

    def __lt__(self, arg: Duration, /) -> bool:
        """Compare two Durations"""

    def __gt__(self, arg: Duration, /) -> bool:
        """Compare two Durations"""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __sub__(self, arg: Duration, /) -> Duration:
        """Compute the difference between two Durations"""

    def __add__(self, arg: Duration, /) -> Duration:
        """Add two Durations"""

    def __repr__(self) -> str: ...

    def __copy__(self) -> Duration: ...

    def __deepcopy__(self, memo: dict[Any, Any]) -> Duration: ...

    def __getstate__(self) -> tuple[int]: ...

    def __setstate__(self, arg: tuple[int], /) -> None: ...

nsec property

nsec: int

Underlying nanoseconds representation

__add__

__add__(arg: Duration) -> Duration

Add two Durations

Source code in python/evalio/_cpp/types.pyi
def __add__(self, arg: Duration, /) -> Duration:
    """Add two Durations"""

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__gt__

__gt__(arg: Duration) -> bool

Compare two Durations

Source code in python/evalio/_cpp/types.pyi
def __gt__(self, arg: Duration, /) -> bool:
    """Compare two Durations"""

__lt__

__lt__(arg: Duration) -> bool

Compare two Durations

Source code in python/evalio/_cpp/types.pyi
def __lt__(self, arg: Duration, /) -> bool:
    """Compare two Durations"""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

__sub__

__sub__(arg: Duration) -> Duration

Compute the difference between two Durations

Source code in python/evalio/_cpp/types.pyi
def __sub__(self, arg: Duration, /) -> Duration:
    """Compute the difference between two Durations"""

from_nsec staticmethod

from_nsec(nsec: int) -> Duration

Create a Duration from nanoseconds

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def from_nsec(nsec: int) -> Duration:
    """Create a Duration from nanoseconds"""

from_sec staticmethod

from_sec(sec: float) -> Duration

Create a Duration from seconds

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def from_sec(sec: float) -> Duration:
    """Create a Duration from seconds"""

to_nsec

to_nsec() -> int

Convert to nanoseconds

Source code in python/evalio/_cpp/types.pyi
def to_nsec(self) -> int:
    """Convert to nanoseconds"""

to_sec

to_sec() -> float

Convert to seconds

Source code in python/evalio/_cpp/types.pyi
def to_sec(self) -> float:
    """Convert to seconds"""

Experiment dataclass

Bases: Metadata

An experiment is a single run of a pipeline on a dataset.

It contains all the information needed to reproduce the run, including the pipeline parameters, dataset, and status.

Methods:

  • from_pl_ds

    Create an Experiment from a pipeline and dataset.

  • setup

    Setup the experiment by initializing the pipeline and dataset.

Attributes:

Source code in python/evalio/types/extended.py
@dataclass(kw_only=True)
class Experiment(Metadata):
    """An experiment is a single run of a pipeline on a dataset.

    It contains all the information needed to reproduce the run, including
    the pipeline parameters, dataset, and status.
    """

    name: str
    """Name of the experiment."""
    sequence: str | ds.Dataset
    """Dataset used to run the experiment."""
    sequence_length: int
    """Length of the sequence"""
    pipeline: str | type[pl.Pipeline]
    """Pipeline used to generate the trajectory."""
    pipeline_version: str
    """Version of the pipeline used."""
    pipeline_params: dict[str, Param]
    """Parameters used for the pipeline."""
    status: ExperimentStatus = ExperimentStatus.NotRun
    """Status of the experiment, e.g. "success", "failure", etc."""
    total_elapsed: Optional[float] = None
    """Total time taken for the experiment, as a string."""
    max_elapsed: Optional[float] = None
    """Maximum time taken for a single step in the experiment, as a string."""

    def to_dict(self) -> dict[str, Any]:
        d = super().to_dict()
        d["status"] = self.status.value
        if isinstance(self.pipeline, type):
            d["pipeline"] = self.pipeline.name()
        if isinstance(self.sequence, ds.Dataset):
            d["sequence"] = self.sequence.full_name

        return d

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Self:
        if "status" in data:
            data["status"] = ExperimentStatus(data["status"])
        else:
            data["status"] = ExperimentStatus.Started

        return super().from_dict(data)

    @classmethod
    def from_pl_ds(
        cls, pipe: type[pl.Pipeline], ds_obj: ds.Dataset, **kwargs: Any
    ) -> Self:
        """Create an Experiment from a pipeline and dataset.

        Args:
            pipe (type[pl.Pipeline]): The pipeline class.
            ds_obj (ds.Dataset): The dataset object.
            **kwargs: Additional keyword arguments to pass to the Experiment constructor.

        Returns:
            Self: The created Experiment instance.
        """
        return cls(
            name=pipe.name(),
            sequence=ds_obj,
            sequence_length=len(ds_obj),
            pipeline=pipe,
            pipeline_version=pipe.version(),
            pipeline_params=pipe.default_params() | kwargs,
        )

    def setup(
        self,
    ) -> tuple[pl.Pipeline, ds.Dataset] | ds.SequenceNotFound | pl.PipelineNotFound:
        """Setup the experiment by initializing the pipeline and dataset.

        Args:
            self (Experiment): The experiment instance.

        Returns:
            Tuple containing the initialized pipeline and dataset, or an error if the pipeline or dataset could not be found or configured.
        """
        if isinstance(self.pipeline, str):
            ThisPipeline = pl.get_pipeline(self.pipeline)
            if isinstance(ThisPipeline, pl.PipelineNotFound):
                return ThisPipeline
        else:
            ThisPipeline = self.pipeline

        if isinstance(self.sequence, ds.Dataset):
            dataset = self.sequence
        else:
            dataset = ds.get_sequence(self.sequence)
            if isinstance(dataset, ds.SequenceNotFound):
                return dataset

        pipe = ThisPipeline()

        # Set user params
        params = pipe.set_params(self.pipeline_params)
        if len(params) > 0:
            for k, v in params.items():
                print_warning(
                    f"Pipeline {self.name} has unused parameters: {k}={v}. "
                    "Please check your configuration."
                )

        # Set dataset params
        pipe.set_imu_params(dataset.imu_params())
        pipe.set_lidar_params(dataset.lidar_params())
        pipe.set_imu_T_lidar(dataset.imu_T_lidar())

        # Initialize pipeline
        pipe.initialize()

        return pipe, dataset

max_elapsed class-attribute instance-attribute

max_elapsed: Optional[float] = None

Maximum time taken for a single step in the experiment, as a string.

name instance-attribute

name: str

Name of the experiment.

pipeline instance-attribute

pipeline: str | type[Pipeline]

Pipeline used to generate the trajectory.

pipeline_params instance-attribute

pipeline_params: dict[str, Param]

Parameters used for the pipeline.

pipeline_version instance-attribute

pipeline_version: str

Version of the pipeline used.

sequence instance-attribute

sequence: str | Dataset

Dataset used to run the experiment.

sequence_length instance-attribute

sequence_length: int

Length of the sequence

status class-attribute instance-attribute

status: ExperimentStatus = NotRun

Status of the experiment, e.g. "success", "failure", etc.

total_elapsed class-attribute instance-attribute

total_elapsed: Optional[float] = None

Total time taken for the experiment, as a string.

from_pl_ds classmethod

from_pl_ds(
    pipe: type[Pipeline], ds_obj: Dataset, **kwargs: Any
) -> Self

Create an Experiment from a pipeline and dataset.

Parameters:

  • pipe (type[Pipeline]) –

    The pipeline class.

  • ds_obj (Dataset) –

    The dataset object.

  • **kwargs (Any, default: {} ) –

    Additional keyword arguments to pass to the Experiment constructor.

Returns:

  • Self ( Self ) –

    The created Experiment instance.

Source code in python/evalio/types/extended.py
@classmethod
def from_pl_ds(
    cls, pipe: type[pl.Pipeline], ds_obj: ds.Dataset, **kwargs: Any
) -> Self:
    """Create an Experiment from a pipeline and dataset.

    Args:
        pipe (type[pl.Pipeline]): The pipeline class.
        ds_obj (ds.Dataset): The dataset object.
        **kwargs: Additional keyword arguments to pass to the Experiment constructor.

    Returns:
        Self: The created Experiment instance.
    """
    return cls(
        name=pipe.name(),
        sequence=ds_obj,
        sequence_length=len(ds_obj),
        pipeline=pipe,
        pipeline_version=pipe.version(),
        pipeline_params=pipe.default_params() | kwargs,
    )

setup

Setup the experiment by initializing the pipeline and dataset.

Parameters:

Returns:

Source code in python/evalio/types/extended.py
def setup(
    self,
) -> tuple[pl.Pipeline, ds.Dataset] | ds.SequenceNotFound | pl.PipelineNotFound:
    """Setup the experiment by initializing the pipeline and dataset.

    Args:
        self (Experiment): The experiment instance.

    Returns:
        Tuple containing the initialized pipeline and dataset, or an error if the pipeline or dataset could not be found or configured.
    """
    if isinstance(self.pipeline, str):
        ThisPipeline = pl.get_pipeline(self.pipeline)
        if isinstance(ThisPipeline, pl.PipelineNotFound):
            return ThisPipeline
    else:
        ThisPipeline = self.pipeline

    if isinstance(self.sequence, ds.Dataset):
        dataset = self.sequence
    else:
        dataset = ds.get_sequence(self.sequence)
        if isinstance(dataset, ds.SequenceNotFound):
            return dataset

    pipe = ThisPipeline()

    # Set user params
    params = pipe.set_params(self.pipeline_params)
    if len(params) > 0:
        for k, v in params.items():
            print_warning(
                f"Pipeline {self.name} has unused parameters: {k}={v}. "
                "Please check your configuration."
            )

    # Set dataset params
    pipe.set_imu_params(dataset.imu_params())
    pipe.set_lidar_params(dataset.lidar_params())
    pipe.set_imu_T_lidar(dataset.imu_T_lidar())

    # Initialize pipeline
    pipe.initialize()

    return pipe, dataset

ExperimentStatus

Bases: Enum

Status of the experiment.

Source code in python/evalio/types/extended.py
class ExperimentStatus(Enum):
    """Status of the experiment."""

    Complete = "complete"
    Fail = "fail"
    Started = "started"
    NotRun = "not_run"

FailedMetadataParse

Bases: Exception

Exception raised when metadata parsing fails.

Source code in python/evalio/types/base.py
class FailedMetadataParse(Exception):
    """Exception raised when metadata parsing fails."""

    def __init__(self, reason: str):
        super().__init__(f"Failed to parse metadata: {reason}")
        self.reason = reason

GroundTruth dataclass

Bases: Metadata

Metadata for ground truth trajectories.

Attributes:

  • sequence (str) –

    Dataset used to run the experiment.

Source code in python/evalio/types/base.py
@dataclass(kw_only=True)
class GroundTruth(Metadata):
    """Metadata for ground truth trajectories."""

    sequence: str
    """Dataset used to run the experiment."""

sequence instance-attribute

sequence: str

Dataset used to run the experiment.

ImuMeasurement

ImuMeasurement is a simple structure for storing an IMU measurement.

Methods:

  • __eq__

    Check for equality

  • __ne__

    Check for inequality

Attributes:

  • accel (NDArray[float64]) –

    Accelerometer measurement as a 3D vector.

  • gyro (NDArray[float64]) –

    Gyroscope measurement as a 3D vector.

  • stamp (Stamp) –

    Timestamp of the IMU measurement.

Source code in python/evalio/_cpp/types.pyi
class ImuMeasurement:
    """ImuMeasurement is a simple structure for storing an IMU measurement."""

    def __init__(self, stamp: Stamp, gyro: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')], accel: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> None: ...

    @property
    def stamp(self) -> Stamp:
        """Timestamp of the IMU measurement."""

    @property
    def gyro(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Gyroscope measurement as a 3D vector."""

    @property
    def accel(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Accelerometer measurement as a 3D vector."""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __repr__(self) -> str: ...

    def __getstate__(self) -> tuple[Stamp, Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]]: ...

    def __setstate__(self, arg: tuple[Stamp, Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')], Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]], /) -> None: ...

accel property

accel: NDArray[float64]

Accelerometer measurement as a 3D vector.

gyro property

gyro: NDArray[float64]

Gyroscope measurement as a 3D vector.

stamp property

stamp: Stamp

Timestamp of the IMU measurement.

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

ImuParams

ImuParams is a structure for storing the parameters of an IMU

Methods:

  • down

    Simple helper for initializing with a down gravity vector.

  • up

    Simple helper for initializing with an up gravity vector.

Attributes:

  • accel (float) –

    Accelerometer standard deviation, in m/s^2/sqrt(Hz).

  • accel_bias (float) –

    Accelerometer bias standard deviation, in m/s^3/sqrt(Hz).

  • bias_init (float) –

    Initial bias standard deviation.

  • brand (str) –

    Brand of the IMU sensor.

  • gravity (NDArray[float64]) –

    Gravity vector as a 3D vector.

  • gyro (float) –

    Gyroscope standard deviation, in rad/s/sqrt(Hz).

  • gyro_bias (float) –

    Gyroscope bias standard deviation, in rad/s^2/sqrt(Hz).

  • integration (float) –

    Integration standard deviation.

  • model (str) –

    Model of the IMU sensor.

Source code in python/evalio/_cpp/types.pyi
class ImuParams:
    """ImuParams is a structure for storing the parameters of an IMU"""

    def __init__(self, *, gyro: float = 1e-05, accel: float = 1e-05, gyro_bias: float = 1e-06, accel_bias: float = 1e-06, bias_init: float = 1e-07, integration: float = 1e-07, gravity: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')] = ..., brand: str = '-', model: str = '-') -> None: ...

    @staticmethod
    def up() -> ImuParams:
        """Simple helper for initializing with an `up` gravity vector."""

    @staticmethod
    def down() -> ImuParams:
        """Simple helper for initializing with a `down` gravity vector."""

    @property
    def gyro(self) -> float:
        """Gyroscope standard deviation, in rad/s/sqrt(Hz)."""

    @property
    def accel(self) -> float:
        """Accelerometer standard deviation, in m/s^2/sqrt(Hz)."""

    @property
    def gyro_bias(self) -> float:
        """Gyroscope bias standard deviation, in rad/s^2/sqrt(Hz)."""

    @property
    def accel_bias(self) -> float:
        """Accelerometer bias standard deviation, in m/s^3/sqrt(Hz)."""

    @property
    def bias_init(self) -> float:
        """Initial bias standard deviation."""

    @property
    def integration(self) -> float:
        """Integration standard deviation."""

    @property
    def gravity(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Gravity vector as a 3D vector."""

    @property
    def brand(self) -> str:
        """Brand of the IMU sensor."""

    @property
    def model(self) -> str:
        """Model of the IMU sensor."""

    def __repr__(self) -> str: ...

accel property

accel: float

Accelerometer standard deviation, in m/s^2/sqrt(Hz).

accel_bias property

accel_bias: float

Accelerometer bias standard deviation, in m/s^3/sqrt(Hz).

bias_init property

bias_init: float

Initial bias standard deviation.

brand property

brand: str

Brand of the IMU sensor.

gravity property

gravity: NDArray[float64]

Gravity vector as a 3D vector.

gyro property

gyro: float

Gyroscope standard deviation, in rad/s/sqrt(Hz).

gyro_bias property

gyro_bias: float

Gyroscope bias standard deviation, in rad/s^2/sqrt(Hz).

integration property

integration: float

Integration standard deviation.

model property

model: str

Model of the IMU sensor.

down staticmethod

down() -> ImuParams

Simple helper for initializing with a down gravity vector.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def down() -> ImuParams:
    """Simple helper for initializing with a `down` gravity vector."""

up staticmethod

up() -> ImuParams

Simple helper for initializing with an up gravity vector.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def up() -> ImuParams:
    """Simple helper for initializing with an `up` gravity vector."""

LidarMeasurement

LidarMeasurement is a structure for storing a point cloud measurement, with a timestamp and a vector of points.

Note, the stamp always represents the start of the scan. Additionally, the points are always in row major format.

Methods:

Attributes:

  • points (list[Point]) –

    List of points in the point cloud. Note, this is always in row major format.

  • stamp (Stamp) –

    Timestamp of the point cloud, always at the start of the scan.

Source code in python/evalio/_cpp/types.pyi
class LidarMeasurement:
    """
    LidarMeasurement is a structure for storing a point cloud measurement, with a timestamp and a vector of points.

    Note, the stamp always represents the _start_ of the scan. Additionally, the points are always in row major format.
    """

    @overload
    def __init__(self, stamp: Stamp) -> None: ...

    @overload
    def __init__(self, stamp: Stamp, points: Sequence[Point]) -> None: ...

    @property
    def stamp(self) -> Stamp:
        """Timestamp of the point cloud, always at the start of the scan."""

    @stamp.setter
    def stamp(self, arg: Stamp, /) -> None: ...

    @property
    def points(self) -> list[Point]:
        """
        List of points in the point cloud. Note, this is always in row major format.
        """

    @points.setter
    def points(self, arg: Sequence[Point], /) -> None: ...

    def to_vec_positions(self) -> list[Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]]:
        """Convert the point cloud to a (n,3) numpy array."""

    def to_vec_stamps(self) -> list[float]:
        """Convert the point stamps to a list of durations."""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __repr__(self) -> str: ...

    def __getstate__(self) -> tuple[Stamp, list[Point]]: ...

    def __setstate__(self, arg: tuple[Stamp, Sequence[Point]], /) -> None: ...

points property writable

points: list[Point]

List of points in the point cloud. Note, this is always in row major format.

stamp property writable

stamp: Stamp

Timestamp of the point cloud, always at the start of the scan.

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

to_vec_positions

to_vec_positions() -> list[NDArray[float64]]

Convert the point cloud to a (n,3) numpy array.

Source code in python/evalio/_cpp/types.pyi
def to_vec_positions(self) -> list[Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]]:
    """Convert the point cloud to a (n,3) numpy array."""

to_vec_stamps

to_vec_stamps() -> list[float]

Convert the point stamps to a list of durations.

Source code in python/evalio/_cpp/types.pyi
def to_vec_stamps(self) -> list[float]:
    """Convert the point stamps to a list of durations."""

LidarParams

LidarParams is a structure for storing the parameters of a lidar sensor.

Methods:

  • delta_time

    Get the time between two consecutive scans as a Duration. Inverse of the rate.

Attributes:

  • brand (str) –

    Brand of the lidar sensor.

  • max_range (float) –

    Maximum range of the lidar sensor, in meters.

  • min_range (float) –

    Minimum range of the lidar sensor, in meters.

  • model (str) –

    Model of the lidar sensor.

  • num_columns (int) –

    Number of columns in the point cloud, also known as the number of points per scanline.

  • num_rows (int) –

    Number of rows in the point cloud, also known as the scanlines.

  • rate (float) –

    Rate of the lidar sensor, in Hz.

Source code in python/evalio/_cpp/types.pyi
class LidarParams:
    """
    LidarParams is a structure for storing the parameters of a lidar sensor.
    """

    def __init__(self, *, num_rows: int, num_columns: int, min_range: float, max_range: float, rate: float = 10.0, brand: str = '-', model: str = '-') -> None: ...

    @property
    def num_rows(self) -> int:
        """Number of rows in the point cloud, also known as the scanlines."""

    @property
    def num_columns(self) -> int:
        """
        Number of columns in the point cloud, also known as the number of points per scanline.
        """

    @property
    def min_range(self) -> float:
        """Minimum range of the lidar sensor, in meters."""

    @property
    def max_range(self) -> float:
        """Maximum range of the lidar sensor, in meters."""

    @property
    def rate(self) -> float:
        """Rate of the lidar sensor, in Hz."""

    @property
    def brand(self) -> str:
        """Brand of the lidar sensor."""

    @property
    def model(self) -> str:
        """Model of the lidar sensor."""

    def delta_time(self) -> Duration:
        """
        Get the time between two consecutive scans as a Duration. Inverse of the rate.
        """

    def __repr__(self) -> str: ...

brand property

brand: str

Brand of the lidar sensor.

max_range property

max_range: float

Maximum range of the lidar sensor, in meters.

min_range property

min_range: float

Minimum range of the lidar sensor, in meters.

model property

model: str

Model of the lidar sensor.

num_columns property

num_columns: int

Number of columns in the point cloud, also known as the number of points per scanline.

num_rows property

num_rows: int

Number of rows in the point cloud, also known as the scanlines.

rate property

rate: float

Rate of the lidar sensor, in Hz.

delta_time

delta_time() -> Duration

Get the time between two consecutive scans as a Duration. Inverse of the rate.

Source code in python/evalio/_cpp/types.pyi
def delta_time(self) -> Duration:
    """
    Get the time between two consecutive scans as a Duration. Inverse of the rate.
    """

Metadata dataclass

Base class for metadata associated with a trajectory.

Methods:

  • from_dict

    Create an instance of the metadata class from a dictionary.

  • from_yaml

    Create an instance of the metadata class from a YAML string.

  • tag

    Get the tag for the metadata class. Will be used for serialization and deserialization.

  • to_dict

    Convert the metadata instance to a dictionary.

  • to_yaml

    Convert the metadata instance to a YAML string.

Attributes:

  • file (Optional[Path]) –

    File where the metadata was loaded to and from, if any.

Source code in python/evalio/types/base.py
@dataclass(kw_only=True)
class Metadata:
    """Base class for metadata associated with a trajectory."""

    file: Optional[Path] = None
    """File where the metadata was loaded to and from, if any."""
    _registry: ClassVar[dict[str, type[Self]]] = {}

    def __init_subclass__(cls) -> None:
        cls._registry[cls.tag()] = cls

    @classmethod
    def tag(cls) -> str:
        """Get the tag for the metadata class. Will be used for serialization and deserialization.

        Returns:
            The tag for the metadata class.
        """
        return pascal_to_snake(cls.__name__)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Self:
        """Create an instance of the metadata class from a dictionary.

        Args:
            data (dict[str, Any]): The dictionary containing the metadata.

        Returns:
            An instance of the metadata class.
        """
        if "type" in data:
            del data["type"]
        return cls(**data)

    def to_dict(self) -> dict[str, Any]:
        """Convert the metadata instance to a dictionary.

        Returns:
            The dictionary representation of the metadata.
        """
        d = asdict(self)
        d["type"] = self.tag()  # add type tag for deserialization
        del d["file"]  # don't serialize the file path
        return d

    def to_yaml(self) -> str:
        """Convert the metadata instance to a YAML string.

        Returns:
            The YAML representation of the metadata.
        """
        data = self.to_dict()
        return yaml.safe_dump(data)

    @classmethod
    def from_yaml(cls, yaml_str: str) -> Metadata | FailedMetadataParse:
        """Create an instance of the metadata class from a YAML string.

        Will return the appropriate subclass based on the "type" field in the YAML.

        Args:
            yaml_str (str): The YAML string containing the metadata.

        Returns:
            An instance of the metadata class or an error.
        """
        try:
            Loader = yaml.CSafeLoader
        except Exception as _:
            print_warning("Failed to import yaml.CSafeLoader, trying yaml.SafeLoader")
            Loader = yaml.SafeLoader

        data = yaml.load(yaml_str, Loader=Loader)

        if data is None:
            return FailedMetadataParse("Metadata failed to parse.")
        elif "type" not in data:
            return FailedMetadataParse("No type field found in metadata.")

        for name, subclass in cls._registry.items():
            if data["type"] == name:
                try:
                    return subclass.from_dict(data)
                except Exception as e:
                    return FailedMetadataParse(f"Failed to parse {name}: {e}")

        return FailedMetadataParse(f"Unknown metadata type '{data['type']}'")

file class-attribute instance-attribute

file: Optional[Path] = None

File where the metadata was loaded to and from, if any.

from_dict classmethod

from_dict(data: dict[str, Any]) -> Self

Create an instance of the metadata class from a dictionary.

Parameters:

  • data (dict[str, Any]) –

    The dictionary containing the metadata.

Returns:

  • Self

    An instance of the metadata class.

Source code in python/evalio/types/base.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> Self:
    """Create an instance of the metadata class from a dictionary.

    Args:
        data (dict[str, Any]): The dictionary containing the metadata.

    Returns:
        An instance of the metadata class.
    """
    if "type" in data:
        del data["type"]
    return cls(**data)

from_yaml classmethod

from_yaml(yaml_str: str) -> Metadata | FailedMetadataParse

Create an instance of the metadata class from a YAML string.

Will return the appropriate subclass based on the "type" field in the YAML.

Parameters:

  • yaml_str (str) –

    The YAML string containing the metadata.

Returns:

Source code in python/evalio/types/base.py
@classmethod
def from_yaml(cls, yaml_str: str) -> Metadata | FailedMetadataParse:
    """Create an instance of the metadata class from a YAML string.

    Will return the appropriate subclass based on the "type" field in the YAML.

    Args:
        yaml_str (str): The YAML string containing the metadata.

    Returns:
        An instance of the metadata class or an error.
    """
    try:
        Loader = yaml.CSafeLoader
    except Exception as _:
        print_warning("Failed to import yaml.CSafeLoader, trying yaml.SafeLoader")
        Loader = yaml.SafeLoader

    data = yaml.load(yaml_str, Loader=Loader)

    if data is None:
        return FailedMetadataParse("Metadata failed to parse.")
    elif "type" not in data:
        return FailedMetadataParse("No type field found in metadata.")

    for name, subclass in cls._registry.items():
        if data["type"] == name:
            try:
                return subclass.from_dict(data)
            except Exception as e:
                return FailedMetadataParse(f"Failed to parse {name}: {e}")

    return FailedMetadataParse(f"Unknown metadata type '{data['type']}'")

tag classmethod

tag() -> str

Get the tag for the metadata class. Will be used for serialization and deserialization.

Returns:

  • str

    The tag for the metadata class.

Source code in python/evalio/types/base.py
@classmethod
def tag(cls) -> str:
    """Get the tag for the metadata class. Will be used for serialization and deserialization.

    Returns:
        The tag for the metadata class.
    """
    return pascal_to_snake(cls.__name__)

to_dict

to_dict() -> dict[str, Any]

Convert the metadata instance to a dictionary.

Returns:

  • dict[str, Any]

    The dictionary representation of the metadata.

Source code in python/evalio/types/base.py
def to_dict(self) -> dict[str, Any]:
    """Convert the metadata instance to a dictionary.

    Returns:
        The dictionary representation of the metadata.
    """
    d = asdict(self)
    d["type"] = self.tag()  # add type tag for deserialization
    del d["file"]  # don't serialize the file path
    return d

to_yaml

to_yaml() -> str

Convert the metadata instance to a YAML string.

Returns:

  • str

    The YAML representation of the metadata.

Source code in python/evalio/types/base.py
def to_yaml(self) -> str:
    """Convert the metadata instance to a YAML string.

    Returns:
        The YAML representation of the metadata.
    """
    data = self.to_dict()
    return yaml.safe_dump(data)

Point

Point is the general point structure in evalio, with common point cloud attributes included.

Methods:

  • __eq__

    Check for equality

  • __init__

    Create a Point from x, y, z, intensity, t, range, row, col

  • __ne__

    Check for inequality

Attributes:

  • col (int) –

    Column index of the point in the point cloud.

  • intensity (float) –

    Intensity value as a float.

  • range (int) –

    Range value as a uint32.

  • row (int) –

    Row index of the point in the point cloud. Also known as the scanline index.

  • t (Duration) –

    Timestamp of the point as a Duration. In evalio, this is always relative to the point cloud stamp, which occurs at the start of the scan.

  • x (float) –

    X coordinate

  • y (float) –

    Y coordinate

  • z (float) –

    Z coordinate

Source code in python/evalio/_cpp/types.pyi
class Point:
    """
    Point is the general point structure in evalio, with common point cloud attributes included.
    """

    def __init__(self, *, x: float = 0, y: float = 0, z: float = 0, intensity: float = 0, t: Duration = ..., range: int = 0, row: int = 0, col: int = 0) -> None:
        """Create a Point from x, y, z, intensity, t, range, row, col"""

    @property
    def x(self) -> float:
        """X coordinate"""

    @x.setter
    def x(self, arg: float, /) -> None: ...

    @property
    def y(self) -> float:
        """Y coordinate"""

    @y.setter
    def y(self, arg: float, /) -> None: ...

    @property
    def z(self) -> float:
        """Z coordinate"""

    @z.setter
    def z(self, arg: float, /) -> None: ...

    @property
    def intensity(self) -> float:
        """Intensity value as a float."""

    @intensity.setter
    def intensity(self, arg: float, /) -> None: ...

    @property
    def range(self) -> int:
        """Range value as a uint32."""

    @range.setter
    def range(self, arg: int, /) -> None: ...

    @property
    def t(self) -> Duration:
        """
        Timestamp of the point as a Duration. In evalio, this is always relative to the point cloud stamp, which occurs at the start of the scan.
        """

    @t.setter
    def t(self, arg: Duration, /) -> None: ...

    @property
    def row(self) -> int:
        """
        Row index of the point in the point cloud. Also known as the scanline index.
        """

    @row.setter
    def row(self, arg: int, /) -> None: ...

    @property
    def col(self) -> int:
        """Column index of the point in the point cloud."""

    @col.setter
    def col(self, arg: int, /) -> None: ...

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __repr__(self) -> str: ...

    def __getstate__(self) -> tuple[float, float, float, float, Duration, int, int, int]: ...

    def __setstate__(self, arg: tuple[float, float, float, float, Duration, int, int, int], /) -> None: ...

col property writable

col: int

Column index of the point in the point cloud.

intensity property writable

intensity: float

Intensity value as a float.

range property writable

range: int

Range value as a uint32.

row property writable

row: int

Row index of the point in the point cloud. Also known as the scanline index.

t property writable

Timestamp of the point as a Duration. In evalio, this is always relative to the point cloud stamp, which occurs at the start of the scan.

x property writable

x: float

X coordinate

y property writable

y: float

Y coordinate

z property writable

z: float

Z coordinate

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__init__

__init__(
    *,
    x: float = 0,
    y: float = 0,
    z: float = 0,
    intensity: float = 0,
    t: Duration = ...,
    range: int = 0,
    row: int = 0,
    col: int = 0,
) -> None

Create a Point from x, y, z, intensity, t, range, row, col

Source code in python/evalio/_cpp/types.pyi
def __init__(self, *, x: float = 0, y: float = 0, z: float = 0, intensity: float = 0, t: Duration = ..., range: int = 0, row: int = 0, col: int = 0) -> None:
    """Create a Point from x, y, z, intensity, t, range, row, col"""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

SE3

SE3 class for representing a 3D rigid body transformation using a quaternion and a translation vector.

This is outfitted with some basic functionality, but is mostly intended for storage and converting between types.

Methods:

  • __eq__

    Check for equality

  • __mul__

    Compose two rigid body transformations.

  • __ne__

    Check for inequality

  • distance

    Compute the distance between two SE3s.

  • error

    Compute the rotational (degrees) and translational (meters) error between two SE3s as a tuple (rot, trans).

  • exp

    Create a SE3 from a 3D vector.

  • fromMat

    Create a SE3 from a 4x4 transformation matrix.

  • identity

    Create an identity SE3.

  • inverse

    Compute the inverse.

  • log

    Compute the logarithm of the transformation.

  • toMat

    Convert to a 4x4 matrix.

Attributes:

  • rot (SO3) –

    Rotation as a SO3 object.

  • trans (NDArray[float64]) –

    Translation as a 3D vector.

Source code in python/evalio/_cpp/types.pyi
class SE3:
    """
    SE3 class for representing a 3D rigid body transformation using a quaternion and a translation vector.

    This is outfitted with some basic functionality, but is mostly intended for storage and converting between types.
    """

    @overload
    def __init__(self, rot: SO3, trans: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> None:
        """Create a SE3 from a rotation and translation."""

    @overload
    def __init__(self, other: SE3) -> None:
        """Copy constructor for SE3."""

    @staticmethod
    def identity() -> SE3:
        """Create an identity SE3."""

    @staticmethod
    def fromMat(mat: Annotated[NDArray[numpy.float64], dict(shape=(4, 4), order='F')]) -> SE3:
        """Create a SE3 from a 4x4 transformation matrix."""

    @property
    def rot(self) -> SO3:
        """Rotation as a SO3 object."""

    @property
    def trans(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Translation as a 3D vector."""

    def toMat(self) -> Annotated[NDArray[numpy.float64], dict(shape=(4, 4), order='F')]:
        """Convert to a 4x4 matrix."""

    def inverse(self) -> SE3:
        """Compute the inverse."""

    @staticmethod
    def error(a: SE3, b: SE3) -> tuple[float, float]:
        """
        Compute the rotational (degrees) and translational (meters) error between two SE3s as a tuple (rot, trans).
        """

    @staticmethod
    def distance(a: SE3, b: SE3) -> float:
        """Compute the distance between two SE3s."""

    @staticmethod
    def exp(xi: Annotated[NDArray[numpy.float64], dict(shape=(6), order='C')]) -> SE3:
        """Create a SE3 from a 3D vector."""

    def log(self) -> Annotated[NDArray[numpy.float64], dict(shape=(6), order='C')]:
        """Compute the logarithm of the transformation."""

    def __mul__(self, arg: SE3, /) -> SE3:
        """Compose two rigid body transformations."""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __repr__(self) -> str: ...

    def __copy__(self) -> SE3: ...

    def __deepcopy__(self, memo: dict[Any, Any]) -> SE3: ...

    def __getstate__(self) -> tuple[float, float, float, float, float, float, float]: ...

    def __setstate__(self, arg: tuple[float, float, float, float, float, float, float], /) -> None: ...

rot property

rot: SO3

Rotation as a SO3 object.

trans property

trans: NDArray[float64]

Translation as a 3D vector.

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__mul__

__mul__(arg: SE3) -> SE3

Compose two rigid body transformations.

Source code in python/evalio/_cpp/types.pyi
def __mul__(self, arg: SE3, /) -> SE3:
    """Compose two rigid body transformations."""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

distance staticmethod

distance(a: SE3, b: SE3) -> float

Compute the distance between two SE3s.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def distance(a: SE3, b: SE3) -> float:
    """Compute the distance between two SE3s."""

error staticmethod

error(a: SE3, b: SE3) -> tuple[float, float]

Compute the rotational (degrees) and translational (meters) error between two SE3s as a tuple (rot, trans).

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def error(a: SE3, b: SE3) -> tuple[float, float]:
    """
    Compute the rotational (degrees) and translational (meters) error between two SE3s as a tuple (rot, trans).
    """

exp staticmethod

exp(xi: NDArray[float64]) -> SE3

Create a SE3 from a 3D vector.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def exp(xi: Annotated[NDArray[numpy.float64], dict(shape=(6), order='C')]) -> SE3:
    """Create a SE3 from a 3D vector."""

fromMat staticmethod

fromMat(mat: NDArray[float64]) -> SE3

Create a SE3 from a 4x4 transformation matrix.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def fromMat(mat: Annotated[NDArray[numpy.float64], dict(shape=(4, 4), order='F')]) -> SE3:
    """Create a SE3 from a 4x4 transformation matrix."""

identity staticmethod

identity() -> SE3

Create an identity SE3.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def identity() -> SE3:
    """Create an identity SE3."""

inverse

inverse() -> SE3

Compute the inverse.

Source code in python/evalio/_cpp/types.pyi
def inverse(self) -> SE3:
    """Compute the inverse."""

log

log() -> NDArray[float64]

Compute the logarithm of the transformation.

Source code in python/evalio/_cpp/types.pyi
def log(self) -> Annotated[NDArray[numpy.float64], dict(shape=(6), order='C')]:
    """Compute the logarithm of the transformation."""

toMat

toMat() -> NDArray[float64]

Convert to a 4x4 matrix.

Source code in python/evalio/_cpp/types.pyi
def toMat(self) -> Annotated[NDArray[numpy.float64], dict(shape=(4, 4), order='F')]:
    """Convert to a 4x4 matrix."""

SO3

SO3 class for representing a 3D rotation using a quaternion.

This is outfitted with some basic functionality, but mostly intended for storage and converting between types.

Methods:

  • __eq__

    Check for equality

  • __mul__

    Compose two rotations.

  • __ne__

    Check for inequality

  • exp

    Create a rotation from a 3D vector.

  • fromMat

    Create a rotation from a 3x3 rotation matrix.

  • identity

    Create an identity rotation.

  • inverse

    Compute the inverse of the rotation.

  • log

    Compute the logarithm of the rotation.

  • rotate

    Rotate a 3D vector by the rotation.

  • toMat

    Convert the rotation to a 3x3 matrix.

Attributes:

  • qw (float) –

    Scalar component of the quaternion.

  • qx (float) –

    X component of the quaternion.

  • qy (float) –

    Y component of the quaternion.

  • qz (float) –

    Z component of the quaternion.

Source code in python/evalio/_cpp/types.pyi
class SO3:
    """
    SO3 class for representing a 3D rotation using a quaternion.

    This is outfitted with some basic functionality, but mostly intended for storage and converting between types.
    """

    def __init__(self, *, qx: float, qy: float, qz: float, qw: float) -> None: ...

    @property
    def qx(self) -> float:
        """X component of the quaternion."""

    @property
    def qy(self) -> float:
        """Y component of the quaternion."""

    @property
    def qz(self) -> float:
        """Z component of the quaternion."""

    @property
    def qw(self) -> float:
        """Scalar component of the quaternion."""

    @staticmethod
    def identity() -> SO3:
        """Create an identity rotation."""

    @staticmethod
    def fromMat(mat: Annotated[NDArray[numpy.float64], dict(shape=(3, 3), order='F')]) -> SO3:
        """Create a rotation from a 3x3 rotation matrix."""

    @staticmethod
    def exp(v: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> SO3:
        """Create a rotation from a 3D vector."""

    def inverse(self) -> SO3:
        """Compute the inverse of the rotation."""

    def log(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Compute the logarithm of the rotation."""

    def toMat(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3, 3), order='F')]:
        """Convert the rotation to a 3x3 matrix."""

    def rotate(self, v: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
        """Rotate a 3D vector by the rotation."""

    def __mul__(self, arg: SO3, /) -> SO3:
        """Compose two rotations."""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    def __repr__(self) -> str: ...

    def __copy__(self) -> SO3: ...

    def __deepcopy__(self, memo: dict[Any, Any]) -> SO3: ...

    def __getstate__(self) -> tuple[float, float, float, float]: ...

    def __setstate__(self, arg: tuple[float, float, float, float], /) -> None: ...

qw property

qw: float

Scalar component of the quaternion.

qx property

qx: float

X component of the quaternion.

qy property

qy: float

Y component of the quaternion.

qz property

qz: float

Z component of the quaternion.

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__mul__

__mul__(arg: SO3) -> SO3

Compose two rotations.

Source code in python/evalio/_cpp/types.pyi
def __mul__(self, arg: SO3, /) -> SO3:
    """Compose two rotations."""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

exp staticmethod

exp(v: NDArray[float64]) -> SO3

Create a rotation from a 3D vector.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def exp(v: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> SO3:
    """Create a rotation from a 3D vector."""

fromMat staticmethod

fromMat(mat: NDArray[float64]) -> SO3

Create a rotation from a 3x3 rotation matrix.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def fromMat(mat: Annotated[NDArray[numpy.float64], dict(shape=(3, 3), order='F')]) -> SO3:
    """Create a rotation from a 3x3 rotation matrix."""

identity staticmethod

identity() -> SO3

Create an identity rotation.

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def identity() -> SO3:
    """Create an identity rotation."""

inverse

inverse() -> SO3

Compute the inverse of the rotation.

Source code in python/evalio/_cpp/types.pyi
def inverse(self) -> SO3:
    """Compute the inverse of the rotation."""

log

log() -> NDArray[float64]

Compute the logarithm of the rotation.

Source code in python/evalio/_cpp/types.pyi
def log(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
    """Compute the logarithm of the rotation."""

rotate

rotate(v: NDArray[float64]) -> NDArray[float64]

Rotate a 3D vector by the rotation.

Source code in python/evalio/_cpp/types.pyi
def rotate(self, v: Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]) -> Annotated[NDArray[numpy.float64], dict(shape=(3), order='C')]:
    """Rotate a 3D vector by the rotation."""

toMat

toMat() -> NDArray[float64]

Convert the rotation to a 3x3 matrix.

Source code in python/evalio/_cpp/types.pyi
def toMat(self) -> Annotated[NDArray[numpy.float64], dict(shape=(3, 3), order='F')]:
    """Convert the rotation to a 3x3 matrix."""

Stamp

Stamp class for representing an absolute point in time.

Uses uint32 as the underlying data storage for seconds and nanoseconds.

Methods:

  • __add__

    Add a Duration to a Stamp

  • __eq__

    Check for equality

  • __gt__

    Compare two Stamps to see which happened first

  • __lt__

    Compare two Stamps to see which happened first

  • __ne__

    Check for inequality

  • from_nsec

    Create a Stamp from nanoseconds

  • from_sec

    Create a Stamp from seconds

  • to_nsec

    Convert to nanoseconds

  • to_sec

    Convert to seconds

Attributes:

  • nsec (int) –

    Underlying nanoseconds storage

  • sec (int) –

    Underlying seconds storage

Source code in python/evalio/_cpp/types.pyi
class Stamp:
    """
    Stamp class for representing an absolute point in time.

    Uses uint32 as the underlying data storage for seconds and nanoseconds.
    """

    @overload
    def __init__(self, *, sec: int, nsec: int) -> None:
        """Create a Stamp from seconds and nanoseconds"""

    @overload
    def __init__(self, other: Stamp) -> None:
        """Copy constructor for Stamp."""

    @staticmethod
    def from_sec(sec: float) -> Stamp:
        """Create a Stamp from seconds"""

    @staticmethod
    def from_nsec(nsec: int) -> Stamp:
        """Create a Stamp from nanoseconds"""

    def to_sec(self) -> float:
        """Convert to seconds"""

    def to_nsec(self) -> int:
        """Convert to nanoseconds"""

    @property
    def sec(self) -> int:
        """Underlying seconds storage"""

    @property
    def nsec(self) -> int:
        """Underlying nanoseconds storage"""

    def __lt__(self, arg: Stamp, /) -> bool:
        """Compare two Stamps to see which happened first"""

    def __gt__(self, arg: Stamp, /) -> bool:
        """Compare two Stamps to see which happened first"""

    def __eq__(self, arg: object, /) -> bool:
        """Check for equality"""

    def __ne__(self, arg: object, /) -> bool:
        """Check for inequality"""

    @overload
    def __sub__(self, arg: Stamp, /) -> Duration:
        """Compute the difference between two Stamps, returning a duration"""

    @overload
    def __sub__(self, arg: Duration, /) -> Stamp:
        """Subtract a Duration from a Stamp"""

    def __add__(self, arg: Duration, /) -> Stamp:
        """Add a Duration to a Stamp"""

    def __repr__(self) -> str: ...

    def __copy__(self) -> Stamp: ...

    def __deepcopy__(self, memo: dict[Any, Any]) -> Stamp: ...

    def __getstate__(self) -> tuple[int, int]: ...

    def __setstate__(self, arg: tuple[int, int], /) -> None: ...

nsec property

nsec: int

Underlying nanoseconds storage

sec property

sec: int

Underlying seconds storage

__add__

__add__(arg: Duration) -> Stamp

Add a Duration to a Stamp

Source code in python/evalio/_cpp/types.pyi
def __add__(self, arg: Duration, /) -> Stamp:
    """Add a Duration to a Stamp"""

__eq__

__eq__(arg: object) -> bool

Check for equality

Source code in python/evalio/_cpp/types.pyi
def __eq__(self, arg: object, /) -> bool:
    """Check for equality"""

__gt__

__gt__(arg: Stamp) -> bool

Compare two Stamps to see which happened first

Source code in python/evalio/_cpp/types.pyi
def __gt__(self, arg: Stamp, /) -> bool:
    """Compare two Stamps to see which happened first"""

__lt__

__lt__(arg: Stamp) -> bool

Compare two Stamps to see which happened first

Source code in python/evalio/_cpp/types.pyi
def __lt__(self, arg: Stamp, /) -> bool:
    """Compare two Stamps to see which happened first"""

__ne__

__ne__(arg: object) -> bool

Check for inequality

Source code in python/evalio/_cpp/types.pyi
def __ne__(self, arg: object, /) -> bool:
    """Check for inequality"""

from_nsec staticmethod

from_nsec(nsec: int) -> Stamp

Create a Stamp from nanoseconds

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def from_nsec(nsec: int) -> Stamp:
    """Create a Stamp from nanoseconds"""

from_sec staticmethod

from_sec(sec: float) -> Stamp

Create a Stamp from seconds

Source code in python/evalio/_cpp/types.pyi
@staticmethod
def from_sec(sec: float) -> Stamp:
    """Create a Stamp from seconds"""

to_nsec

to_nsec() -> int

Convert to nanoseconds

Source code in python/evalio/_cpp/types.pyi
def to_nsec(self) -> int:
    """Convert to nanoseconds"""

to_sec

to_sec() -> float

Convert to seconds

Source code in python/evalio/_cpp/types.pyi
def to_sec(self) -> float:
    """Convert to seconds"""

Trajectory dataclass

Bases: Generic[M]

A trajectory of poses with associated timestamps and metadata.

Methods:

  • __getitem__

    Get a (stamp, pose) pair by index.

  • __iter__

    Iterate over the trajectory.

  • __len__

    Get the length of the trajectory.

  • append

    Append a new pose to the trajectory.

  • close

    Close the CSV file if it was opened with open.

  • from_csv

    Flexible loader for stamped poses stored in csv files.

  • from_file

    Load a saved evalio trajectory from file.

  • from_tum

    Load a TUM dataset pose file. Simple wrapper around from_csv.

  • open

    Open a CSV file for writing.

  • rewrite

    Update the contents of an open file.

  • to_file

    Save the trajectory to a CSV file.

  • transform_in_place

    Apply a transformation to all poses in the trajectory.

Attributes:

  • metadata (M) –

    Metadata associated with the trajectory, such as the dataset name or other information.

  • poses (list[SE3]) –

    List of poses, in the same order as the timestamps.

  • stamps (list[Stamp]) –

    List of timestamps for each pose.

Source code in python/evalio/types/base.py
@dataclass(kw_only=True)
class Trajectory(Generic[M]):
    """A trajectory of poses with associated timestamps and metadata."""

    stamps: list[Stamp] = field(default_factory=list)
    """List of timestamps for each pose."""
    poses: list[SE3] = field(default_factory=list)
    """List of poses, in the same order as the timestamps."""
    metadata: M = None  # type: ignore
    """Metadata associated with the trajectory, such as the dataset name or other information."""
    _file: Optional[TextIOWrapper] = None
    _csv_writer: Optional[Writer] = None

    def __post_init__(self):
        if len(self.stamps) != len(self.poses):
            raise ValueError("Stamps and poses must have the same length.")

    def __getitem__(self, idx: int) -> tuple[Stamp, SE3]:
        """Get a (stamp, pose) pair by index.

        Args:
            idx (int): The index of the (stamp, pose) pair.

        Returns:
            The (stamp, pose) pair at the given index.
        """
        return self.stamps[idx], self.poses[idx]

    def __len__(self) -> int:
        """Get the length of the trajectory.

        Returns:
            The number of (stamp, pose) pairs in the trajectory.
        """
        return len(self.stamps)

    def __iter__(self) -> Iterator[tuple[Stamp, SE3]]:
        """Iterate over the trajectory.

        Returns:
            An iterator over the (stamp, pose) pairs.
        """
        return iter(zip(self.stamps, self.poses))

    def append(self, stamp: Stamp, pose: SE3):
        """Append a new pose to the trajectory.

        Will also write to file if the trajectory was opened with [open][evalio.types.Trajectory.open].

        Args:
            stamp (Stamp): The timestamp of the pose.
            pose (SE3): The pose to append.
        """
        self.stamps.append(stamp)
        self.poses.append(pose)

        if self._csv_writer is not None:
            self._csv_writer.writerow(self._serialize_pose(stamp, pose))

    def transform_in_place(self, T: SE3):
        """Apply a transformation to all poses in the trajectory.

        Args:
            T (SE3): The transformation to apply.
        """
        for i in range(len(self.poses)):
            self.poses[i] = self.poses[i] * T

    # ------------------------- Loading from file ------------------------- #
    @staticmethod
    def from_csv(
        path: Path,
        fieldnames: list[str],
        delimiter: str = ",",
        skip_lines: int = 0,
    ) -> Trajectory:
        """Flexible loader for stamped poses stored in csv files.

        Will automatically skip any lines that start with a #.

        ``` py
        from evalio.types import Trajectory

        fieldnames = ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"]
        trajectory = Trajectory.from_csv(path, fieldnames)
        ```

        Args:
            path (Path): Location of file.
            fieldnames (list[str]): List of field names to use, in their expected order. See above for an example.
            delimiter (str, optional): Delimiter between elements. Defaults to ",".
            skip_lines (int, optional): Number of lines to skip, useful for skipping headers. Defaults to 0.

        Returns:
            Stored trajectory
        """
        poses: list[SE3] = []
        stamps: list[Stamp] = []

        fields = {name: i for i, name in enumerate(fieldnames)}

        with open(path) as f:
            csvfile = filter(lambda row: row[0] != "#", f)
            for i, line in enumerate(csvfile):
                if i < skip_lines:
                    continue
                stamp, pose = parse_csv_line(line, delimiter, fields)

                poses.append(pose)
                stamps.append(stamp)

        return Trajectory(stamps=stamps, poses=poses)

    @staticmethod
    def from_tum(path: Path) -> Trajectory:
        """Load a TUM dataset pose file. Simple wrapper around [from_csv][evalio.types.Trajectory].

        Args:
            path (Path): Location of file.

        Returns:
            Stored trajectory
        """
        return Trajectory.from_csv(path, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"])

    @staticmethod
    def from_file(
        path: Path,
    ) -> Trajectory[Metadata] | FailedMetadataParse | FileNotFoundError:
        """Load a saved evalio trajectory from file.

        Works identically to [from_tum][evalio.types.Trajectory.from_tum], but also loads metadata from the file.

        Args:
            path (Path): Location of trajectory results.

        Returns:
            Loaded trajectory with metadata, stamps, and poses.
        """
        if not path.exists():
            return FileNotFoundError(f"File {path} does not exist.")

        with open(path) as file:
            metadata_filter = filter(
                lambda row: row[0] == "#" and not row.startswith("# timestamp,"), file
            )
            metadata_list = [row[1:] for row in metadata_filter]
            metadata_str = "".join(metadata_list)

            metadata = Metadata.from_yaml(metadata_str)
            if isinstance(metadata, FailedMetadataParse):
                return metadata

            metadata.file = path

        trajectory = Trajectory.from_csv(
            path,
            fieldnames=["sec", "x", "y", "z", "qx", "qy", "qz", "qw"],
        )
        trajectory = cast(Trajectory[Metadata], trajectory)
        trajectory.metadata = metadata

        return trajectory

    # ------------------------- Saving to file ------------------------- #
    def _serialize_pose(self, stamp: Stamp, pose: SE3) -> list[str | float]:
        return [
            f"{stamp.sec}.{stamp.nsec:09}",
            pose.trans[0],
            pose.trans[1],
            pose.trans[2],
            pose.rot.qx,
            pose.rot.qy,
            pose.rot.qz,
            pose.rot.qw,
        ]

    def _serialize_metadata(self) -> str:
        if self.metadata is None:
            return ""

        metadata_str = self.metadata.to_yaml()
        metadata_str = metadata_str.replace("\n", "\n# ")
        return f"# {metadata_str}\n"

    def _write(self):
        if self._file is None or self._csv_writer is None:
            return

        # write everything we've got so far
        if self.metadata is not None:
            self._file.write(self._serialize_metadata())

        self._file.write("# timestamp, x, y, z, qx, qy, qz, qw\n")
        self._csv_writer.writerows(self._serialize_pose(s, p) for s, p in self)

    def open(self, path: Optional[Path] = None):
        """Open a CSV file for writing.

        This will overwrite any existing file. If no path is provided, will use the path in the metadata, if it exists.

        Args:
            path (Optional[Path], optional): Path to the CSV file. Defaults to None.
        """
        if path is not None:
            pass
        elif self.metadata is not None and self.metadata.file is not None:
            path = self.metadata.file
        else:
            print_warning(
                "Trajectory.open: No metadata or path provided, cannot set metadata file."
            )
            return

        path.parent.mkdir(parents=True, exist_ok=True)
        self._file = path.open("w")
        self._csv_writer = csv.writer(self._file)
        self._write()

    def close(self):
        """Close the CSV file if it was opened with [open][evalio.types.Trajectory.open]."""
        if self._file is not None:
            self._file.close()
            self._file = None
            self._csv_writer = None
        else:
            print_warning("Trajectory.close: No file to close.")

    def to_file(self, path: Optional[Path] = None):
        """Save the trajectory to a CSV file.

        Args:
            path (Optional[Path], optional): Path to the CSV file. If not specified, utilizes the path in the metadata, if it exists. Defaults to None.
        """
        self.open(path)
        self.close()

    def rewrite(self):
        """Update the contents of an open file."""
        if self._file is None or self._csv_writer is None:
            print_warning("Trajectory.rewrite: No file is open.")
            return

        if self.metadata is None:
            print_warning("Trajectory.rewrite: No metadata to update.")
            return

        # Go to start, empty, and rewrite
        self._file.seek(0)
        self._file.truncate()
        self._write()

metadata class-attribute instance-attribute

metadata: M = None

Metadata associated with the trajectory, such as the dataset name or other information.

poses class-attribute instance-attribute

poses: list[SE3] = field(default_factory=list)

List of poses, in the same order as the timestamps.

stamps class-attribute instance-attribute

stamps: list[Stamp] = field(default_factory=list)

List of timestamps for each pose.

__getitem__

__getitem__(idx: int) -> tuple[Stamp, SE3]

Get a (stamp, pose) pair by index.

Parameters:

  • idx (int) –

    The index of the (stamp, pose) pair.

Returns:

  • tuple[Stamp, SE3]

    The (stamp, pose) pair at the given index.

Source code in python/evalio/types/base.py
def __getitem__(self, idx: int) -> tuple[Stamp, SE3]:
    """Get a (stamp, pose) pair by index.

    Args:
        idx (int): The index of the (stamp, pose) pair.

    Returns:
        The (stamp, pose) pair at the given index.
    """
    return self.stamps[idx], self.poses[idx]

__iter__

__iter__() -> Iterator[tuple[Stamp, SE3]]

Iterate over the trajectory.

Returns:

  • Iterator[tuple[Stamp, SE3]]

    An iterator over the (stamp, pose) pairs.

Source code in python/evalio/types/base.py
def __iter__(self) -> Iterator[tuple[Stamp, SE3]]:
    """Iterate over the trajectory.

    Returns:
        An iterator over the (stamp, pose) pairs.
    """
    return iter(zip(self.stamps, self.poses))

__len__

__len__() -> int

Get the length of the trajectory.

Returns:

  • int

    The number of (stamp, pose) pairs in the trajectory.

Source code in python/evalio/types/base.py
def __len__(self) -> int:
    """Get the length of the trajectory.

    Returns:
        The number of (stamp, pose) pairs in the trajectory.
    """
    return len(self.stamps)

append

append(stamp: Stamp, pose: SE3)

Append a new pose to the trajectory.

Will also write to file if the trajectory was opened with open.

Parameters:

  • stamp (Stamp) –

    The timestamp of the pose.

  • pose (SE3) –

    The pose to append.

Source code in python/evalio/types/base.py
def append(self, stamp: Stamp, pose: SE3):
    """Append a new pose to the trajectory.

    Will also write to file if the trajectory was opened with [open][evalio.types.Trajectory.open].

    Args:
        stamp (Stamp): The timestamp of the pose.
        pose (SE3): The pose to append.
    """
    self.stamps.append(stamp)
    self.poses.append(pose)

    if self._csv_writer is not None:
        self._csv_writer.writerow(self._serialize_pose(stamp, pose))

close

close()

Close the CSV file if it was opened with open.

Source code in python/evalio/types/base.py
def close(self):
    """Close the CSV file if it was opened with [open][evalio.types.Trajectory.open]."""
    if self._file is not None:
        self._file.close()
        self._file = None
        self._csv_writer = None
    else:
        print_warning("Trajectory.close: No file to close.")

from_csv staticmethod

from_csv(
    path: Path,
    fieldnames: list[str],
    delimiter: str = ",",
    skip_lines: int = 0,
) -> Trajectory

Flexible loader for stamped poses stored in csv files.

Will automatically skip any lines that start with a #.

from evalio.types import Trajectory

fieldnames = ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"]
trajectory = Trajectory.from_csv(path, fieldnames)

Parameters:

  • path (Path) –

    Location of file.

  • fieldnames (list[str]) –

    List of field names to use, in their expected order. See above for an example.

  • delimiter (str, default: ',' ) –

    Delimiter between elements. Defaults to ",".

  • skip_lines (int, default: 0 ) –

    Number of lines to skip, useful for skipping headers. Defaults to 0.

Returns:

Source code in python/evalio/types/base.py
@staticmethod
def from_csv(
    path: Path,
    fieldnames: list[str],
    delimiter: str = ",",
    skip_lines: int = 0,
) -> Trajectory:
    """Flexible loader for stamped poses stored in csv files.

    Will automatically skip any lines that start with a #.

    ``` py
    from evalio.types import Trajectory

    fieldnames = ["sec", "nsec", "x", "y", "z", "qx", "qy", "qz", "qw"]
    trajectory = Trajectory.from_csv(path, fieldnames)
    ```

    Args:
        path (Path): Location of file.
        fieldnames (list[str]): List of field names to use, in their expected order. See above for an example.
        delimiter (str, optional): Delimiter between elements. Defaults to ",".
        skip_lines (int, optional): Number of lines to skip, useful for skipping headers. Defaults to 0.

    Returns:
        Stored trajectory
    """
    poses: list[SE3] = []
    stamps: list[Stamp] = []

    fields = {name: i for i, name in enumerate(fieldnames)}

    with open(path) as f:
        csvfile = filter(lambda row: row[0] != "#", f)
        for i, line in enumerate(csvfile):
            if i < skip_lines:
                continue
            stamp, pose = parse_csv_line(line, delimiter, fields)

            poses.append(pose)
            stamps.append(stamp)

    return Trajectory(stamps=stamps, poses=poses)

from_file staticmethod

from_file(
    path: Path,
) -> (
    Trajectory[Metadata]
    | FailedMetadataParse
    | FileNotFoundError
)

Load a saved evalio trajectory from file.

Works identically to from_tum, but also loads metadata from the file.

Parameters:

  • path (Path) –

    Location of trajectory results.

Returns:

Source code in python/evalio/types/base.py
@staticmethod
def from_file(
    path: Path,
) -> Trajectory[Metadata] | FailedMetadataParse | FileNotFoundError:
    """Load a saved evalio trajectory from file.

    Works identically to [from_tum][evalio.types.Trajectory.from_tum], but also loads metadata from the file.

    Args:
        path (Path): Location of trajectory results.

    Returns:
        Loaded trajectory with metadata, stamps, and poses.
    """
    if not path.exists():
        return FileNotFoundError(f"File {path} does not exist.")

    with open(path) as file:
        metadata_filter = filter(
            lambda row: row[0] == "#" and not row.startswith("# timestamp,"), file
        )
        metadata_list = [row[1:] for row in metadata_filter]
        metadata_str = "".join(metadata_list)

        metadata = Metadata.from_yaml(metadata_str)
        if isinstance(metadata, FailedMetadataParse):
            return metadata

        metadata.file = path

    trajectory = Trajectory.from_csv(
        path,
        fieldnames=["sec", "x", "y", "z", "qx", "qy", "qz", "qw"],
    )
    trajectory = cast(Trajectory[Metadata], trajectory)
    trajectory.metadata = metadata

    return trajectory

from_tum staticmethod

from_tum(path: Path) -> Trajectory

Load a TUM dataset pose file. Simple wrapper around from_csv.

Parameters:

  • path (Path) –

    Location of file.

Returns:

Source code in python/evalio/types/base.py
@staticmethod
def from_tum(path: Path) -> Trajectory:
    """Load a TUM dataset pose file. Simple wrapper around [from_csv][evalio.types.Trajectory].

    Args:
        path (Path): Location of file.

    Returns:
        Stored trajectory
    """
    return Trajectory.from_csv(path, ["sec", "x", "y", "z", "qx", "qy", "qz", "qw"])

open

open(path: Optional[Path] = None)

Open a CSV file for writing.

This will overwrite any existing file. If no path is provided, will use the path in the metadata, if it exists.

Parameters:

  • path (Optional[Path], default: None ) –

    Path to the CSV file. Defaults to None.

Source code in python/evalio/types/base.py
def open(self, path: Optional[Path] = None):
    """Open a CSV file for writing.

    This will overwrite any existing file. If no path is provided, will use the path in the metadata, if it exists.

    Args:
        path (Optional[Path], optional): Path to the CSV file. Defaults to None.
    """
    if path is not None:
        pass
    elif self.metadata is not None and self.metadata.file is not None:
        path = self.metadata.file
    else:
        print_warning(
            "Trajectory.open: No metadata or path provided, cannot set metadata file."
        )
        return

    path.parent.mkdir(parents=True, exist_ok=True)
    self._file = path.open("w")
    self._csv_writer = csv.writer(self._file)
    self._write()

rewrite

rewrite()

Update the contents of an open file.

Source code in python/evalio/types/base.py
def rewrite(self):
    """Update the contents of an open file."""
    if self._file is None or self._csv_writer is None:
        print_warning("Trajectory.rewrite: No file is open.")
        return

    if self.metadata is None:
        print_warning("Trajectory.rewrite: No metadata to update.")
        return

    # Go to start, empty, and rewrite
    self._file.seek(0)
    self._file.truncate()
    self._write()

to_file

to_file(path: Optional[Path] = None)

Save the trajectory to a CSV file.

Parameters:

  • path (Optional[Path], default: None ) –

    Path to the CSV file. If not specified, utilizes the path in the metadata, if it exists. Defaults to None.

Source code in python/evalio/types/base.py
def to_file(self, path: Optional[Path] = None):
    """Save the trajectory to a CSV file.

    Args:
        path (Optional[Path], optional): Path to the CSV file. If not specified, utilizes the path in the metadata, if it exists. Defaults to None.
    """
    self.open(path)
    self.close()

transform_in_place

transform_in_place(T: SE3)

Apply a transformation to all poses in the trajectory.

Parameters:

  • T (SE3) –

    The transformation to apply.

Source code in python/evalio/types/base.py
def transform_in_place(self, T: SE3):
    """Apply a transformation to all poses in the trajectory.

    Args:
        T (SE3): The transformation to apply.
    """
    for i in range(len(self.poses)):
        self.poses[i] = self.poses[i] * T