evo: Python Trajectory Evaluation for SLAM and Odometry

┌─────────────────────────────────────────────────────┐
│ Analysis Summary                                    │
├─────────────────────────────────────────────────────┤
│ Type: Project                                       │
│ Purpose: Python Trajectory Evaluation for SLAM and Odometry│
│ Primary Language: python + markdown + json          │
│ LOC: 15K                                            │
│ Test Files: 10                                      │
│ Architecture: python                                │
│ Confidence: High                                    │
└─────────────────────────────────────────────────────┘

Analyzed: 43d02fc6 from 2025-10-04

evo: Python Trajectory Evaluation for SLAM and Odometry

evo provides a comprehensive toolkit for evaluating and comparing trajectory outputs from SLAM and odometry algorithms. The 14,515-line Python codebase supports multiple trajectory formats including TUM, KITTI, EuRoC MAV, and ROS bag files, offering both command-line tools and a programmatic API for trajectory analysis.

The project implements standard trajectory evaluation metrics including absolute pose error (APE) and relative pose error (RPE), with flexible options for trajectory association, alignment, and visualization. Built on NumPy, Matplotlib, and SciPy, it processes trajectories as 3D pose sequences with support for various coordinate representations and reference frame transformations.

Quick Start

pip install evo

Basic trajectory comparison:

cd test/data
evo_traj kitti KITTI_00_ORB.txt KITTI_00_SPTAM.txt --ref=KITTI_00_gt.txt -p --plot_mode=xz

Time to first result: ~2 minutes

Alternative Approaches

Solution Setup Time Learning Curve Format Support Visualization
evo Low Medium TUM/KITTI/ROS/EuRoC Excellent
rpg_trajectory_evaluation Medium High TUM/EuRoC Basic
TUM RGB-D tools Low Low TUM only Basic
KITTI devkit Low Low KITTI only Basic
Custom scripts High Variable Custom Variable

Architecture and Implementation

The codebase centers around trajectory synchronization and error computation. The core synchronization logic in evo/core/sync.py:42-56 implements timestamp matching:

def matching_time_indices(stamps_1: np.ndarray, stamps_2: np.ndarray,
                          max_diff: float = 0.01,
                          offset_2: float = 0.0) -> MatchingIndices:
    """
    Searches for the best matching timestamps of two lists of timestamps
    and returns the list indices of the best matches.
    :param stamps_1: first vector of timestamps (numpy array)
    :param stamps_2: second vector of timestamps (numpy array)
    :param max_diff: max. allowed absolute time difference
    :param offset_2: optional time offset to be applied to stamps_2
    :return: 2 lists of the matching timestamp indices (stamps_1, stamps_2)
    """
    matching_indices_1 = []
    matching_indices_2 = []
    stamps_2 = copy.deepcopy(stamps_2)

The trajectory association function in evo/core/sync.py:67-81 builds on this foundation:

def associate_trajectories(
        traj_1: PoseTrajectory3D, traj_2: PoseTrajectory3D,
        max_diff: float = 0.01, offset_2: float = 0.0,
        first_name: str = "first trajectory",
        snd_name: str = "second trajectory") -> TrajectoryPair:
    """
    Synchronizes two trajectories by matching their timestamps.
    :param traj_1: trajectory.PoseTrajectory3D object of first trajectory
    :param traj_2: trajectory.PoseTrajectory3D object of second trajectory
    :param max_diff: max. allowed absolute time difference for associating
    :param offset_2: optional time offset of second trajectory
    :param first_name: name of first trajectory for verbose logging
    :param snd_name: name of second trajectory for verbose/debug logging
    :return: traj_1, traj_2 (synchronized)
    """

Results are encapsulated in a structured container from evo/core/result.py:37-51:

class Result(object):
    def __init__(self):
        self.info = {}
        self.stats = {}
        self.np_arrays = {}
        self.trajectories = {}

    def __str__(self) -> str:
        return self.pretty_str(stats=True)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Result):
            return False
        equal = (self.info == other.info)
        equal &= (self.stats == other.stats)

The equality implementation in evo/core/result.py:47-61 handles NumPy array comparisons:

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Result):
            return False
        equal = (self.info == other.info)
        equal &= (self.stats == other.stats)
        equal &= (self.trajectories == other.trajectories)
        for k in self.np_arrays:
            if k not in other.np_arrays:
                equal &= False
                break
            if not equal:
                break
            equal &= all(
                [np.array_equal(self.np_arrays[k], other.np_arrays[k])])
        return equal

Performance Characteristics

Bundle Impact:

  • Core library: ~13KB Python code
  • Dependencies: 14 packages including NumPy, Matplotlib, SciPy
  • Total installation: ~50MB with dependencies

Other Considerations:

  • Runtime dependencies: 14 core packages
  • Test coverage: 10 test files across 128 total files
  • Build tooling: Standard Python packaging with pip

Best for: Research environments requiring flexible trajectory analysis with publication-quality visualizations and statistical comparisons.

When to Use evo

The evidence suggests this project fits well for:

  • Multi-format trajectory evaluation where datasets use different formats (TUM, KITTI, ROS bags) as shown by the format support in the README
  • Research workflows requiring statistical analysis with the evo_res tool demonstrating comparative statistics and visualization capabilities
  • Publication-quality visualization needs evidenced by the extensive matplotlib integration and plot customization options

Consider alternatives when:

  • Single-format, simple comparisons are sufficient, as lighter-weight tools may be more appropriate
  • Real-time trajectory evaluation is required, since the analysis focuses on post-processing rather than streaming
  • Custom metrics beyond APE/RPE are needed, as the core focuses on these standard evaluation approaches

The test suite in test/test_trajectory.py:35-54 demonstrates robust trajectory validation with proper error handling for incorrect initialization parameters, while test/test_trajectory.py:78-97 shows sophisticated quaternion equivalence checking, indicating mature handling of 3D rotation representations.