This repository is an offline, non-ROS runner derived from vectr-ucla/direct_lidar_odometry. The odometry defaults and IMU behavior track upstream v1.4.3, while YAML input, stable output files, and sequence evaluation support rapid offline experiments.
Required packages are CMake, Eigen3, PCL (common, filters, I/O, registration, and surface), Boost filesystem/system, yaml-cpp, OpenMP, and Python 3 with NumPy for the evaluator.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failureThe build uses the PCL installation discovered at configure time. Do not reuse a checked-in or copied build directory from a different host/PCL version.
Every PCD filename stem must be its finite numeric timestamp in seconds. Files are sorted numerically and duplicate timestamps are rejected.
./build/direct_lidar_odometry_offline \
--config config/uturn_frf.yamlCommand-line input/output overrides make one profile reusable:
./build/direct_lidar_odometry_offline \
--config config/upstream.yaml \
--lidar-dir /path/to/timestamped_pcds \
--imu-file /path/to/imu.csv \
--output-dir result/experimentUse --max-scans N for a prefix smoke test. Configuration is strict: unknown
keys, invalid values, missing required paths, non-numeric PCD names, and
non-monotonic IMU data fail with an error instead of silently changing behavior.
When IMU is enabled, offline.input.maxImuGap also enforces end coverage and a
maximum internal sampling gap before any scans are processed.
Two profiles are included:
config/upstream.yaml: exact upstream v1.4.3 algorithm defaults, with IMU enabled and offline paths left for the caller.config/uturn_frf.yaml: the suppliedlidar_frf_at128sequence, using upstream algorithm defaults and LiDAR-only mode. IMU is intentionally off because the dataset does not provide a verified IMU-to-LiDAR extrinsic.
IMU support matches upstream DLO semantics. The measurements must already be expressed in the LiDAR frame. The first configured calibration interval (default: 3 seconds) estimates gyroscope bias and mean gravity; LiDAR frames in that interval are recorded as skipped. Gravity alignment is optional. After calibration, integrated angular velocity provides a rotation-only prior for scan-to-scan GICP.
This is not tightly coupled LIO: it does not estimate IMU extrinsics, predict translation, or deskew individual points.
Set dlo.imu: false for a fully supported LiDAR-only run.
The configured output directory contains:
local_pose.txt: accepted poses asframe_id filename tx ty tz qx qy qz qw.frames.csv: one row per input, including accepted/skipped/rejected status, reason, keyframe flag, and odometry-core latency.run_summary.json: counts, keyframes, mean core latency, and loop wall time.local_map_keyframes.pcd: merged keyframe map whensaveMapis true.keyframes/<index>.pcd: transformed keyframe clouds whensaveKeyframesis true.
Frame IDs are original input indices. This keeps poses and diagnostics traceable even when calibration skips or load/validation failures occur.
The evaluator synchronizes each accepted pose to the nearest INS sample, converts WGS84 positions to local ENU, and applies rigid SE(3) alignment without scale fitting.
python3 tools/evaluate_trajectory.py \
--poses result/uturn_frf/local_pose.txt \
--ins /path/to/ins.txt \
--output result/uturn_frf/evaluation.jsonIt reports 3D ATE RMSE/median/max and signed path-length error. See
docs/upstream-parity.md for the restored behaviors, intentional offline
differences, and regression procedure.