Calibration evidence framework for robotics
Estimate it, check it, watch it drift.¶
Three commands for the sensor calibration of a robot, on ROS 2 and ROS 1 bags, with honest per-axis verdicts for LiDAR, IMU, camera, GNSS, and vehicle. Calibrex says plainly which axes it measured, which it could not judge, and what to record to fix that.

Real runs on the Koide hard-localization recordings (abridged output): estimate a calibration from one bag, check it on another, then ask whether it changed across recordings. The third recording is a copy with its IMU rotated 2° about z.
The workflow¶
| Command | Question | Result | |
|---|---|---|---|
| 1 | calibrex estimate <bag> |
The bag has no calibration yet: what do the data say? | frames.yaml, static transforms, URDF joints, a Kalibr camchain; per-axis std; axes the data did not observe are marked NOT MEASURED; --html report |
| 2 | calibrex check <bag2> --tf frames.yaml |
Is the deployed calibration still right on another recording? | pass / warn / fail / inconclusive per axis, what could not be judged, next steps, progress; --html report |
| 3 | calibrex drift <bag1> <bag2> ... |
Did the calibration change over time, and in which bag? | stable / drift / inconclusive per axis, the deviating bag and the size of the change |
When a lever-arm axis is not observable, estimate and check say why (which rotation the
recording lacks) and how much motion or duration would fix it
(translation observability). The same check and
estimate run in the browser page on your own bag, with no install and no upload.
Details and real outputs: the workflow page.
What it does¶
-
One command, ten sensor pairs
calibrex checkreads/tf_static(or theframes.yamlthatcalibrex estimatewrote) and checks every sensor pair it finds, from LiDAR-vehicle to camera-IMU. camera-LiDAR is judged on rotation only; translation is never judged. -
Reads your bag as it is
rosbag2 (
.db3), ROS 2 MCAP (zstd/lz4) and ROS 1.bag(bz2/lz4) are detected from the file: no conversion, no ROS install. Autoware's raw Velodyne packets are decoded too (Autoware note). -
Honest per-axis verdicts
Partial coverage is named, not hidden: a
passcovers only the judged axes, and detection power tells you whether the data could have caught a wrong calibration. -
Check or estimate in your browser
Drop a ROS 2 bag on the bag check page to plan, run
calibrex checkor runcalibrex estimateunder Pyodide, with progress and downloads. Files are read in place and never uploaded. Compared with the command line on real bags of every pair: same verdicts (camera pairs agree within one to two sigma). -
Pre-registered SOTA claims
Thresholds are frozen before held-out data is scored, and claims that fail stay on the leaderboard.
-
Evidence and provenance
Configs and results are schema-validated, every report records its inputs, and the README GIFs are digest-bound to their sources.
Catching a bad calibration¶

Real calibrex check runs on KITTI development drives: a deployed LiDAR transform
with a 3° yaw error fails; restoring the calibration file and re-checking
passes (pitch and yaw judged; roll is not observable from driving).
Camera ↔ LiDAR, rotation only¶

Two real calibrex check --pairs camera-lidar runs on KITTI development drive 0005:
the vendor calibration passes (roll and pitch judged); the same calibration turned +3° about the
camera y axis fails (pitch 3.00° against 0.50°). The frames in between only slide the overlay.
Translation is not judged; results and limits.
Real data, rendered by the code¶
Every frame comes from a real recording, rendered by Calibrex's own code.
calibrex lidar-lidar estimate; median point-to-plane residual 19.1 → 4.4 cm (the design value gives 4.6 cm).
/tf_static. Rotation drawn ×8 for visibility; colours follow the real calibrex check verdict at each step.)SOTA standings¶
Calibrex calls a method state of the art only for a claim that a frozen,
pre-registered audit marks supported. Refuted audits are listed too.
| Pair | Standing | Notes |
|---|---|---|
imu-lidar |
supported | Livox MID360 against LI-Init: rotation and clock offset, and the full extrinsic in a second round (the first round was refuted) |
lidar-vehicle |
supported | KITTI motion-only rotation against calib_imu_to_velo, eight unseen drives |
camera-imu |
supported | Hilti 2022 forward cameras against Kalibr: every judged axis within 1 deg, five unseen recordings (third round; the first two were refuted) |
lidar-lidar |
refuted | NTU VIRAL, 2/4 gates |
camera-camera |
refuted | Hilti 2022 against Kalibr, 4/6 gates |
camera-lidar |
refuted | KITTI check verdicts: v2 8/9 gates on 18 unseen drives of four days (no calibration flagged; one turned-run axis 1.503 deg off, gate 1.5), v1 5/9 |
ins-lidar |
refuted | KITTI + Boreas, 6/8 gates: estimated axes agree, but one recording has no observable rotation |
lidar-wheel_odometry |
refuted | NCLT, five unseen sessions: yaw within 0.43 deg, roll and pitch up to 2.4 deg off (gate 0.5); check never failed the calibration |
camera |
supported | TUM-VI focal lengths, five unseen sequences: check never failed Kalibr's values and passed none of the 3 %-wrong ones (half inconclusive; no accuracy claim) |
gnss-imu, imu-vehicle |
no_claim | Native method, no audited claim yet |
Quickstart¶
python -m pip install "https://github.com/rsasaki0109/Calibrex/releases/download/v0.6.2/calibrex-0.6.2-py3-none-any.whl"
calibrex estimate my_bag/ --output est/ --html est.html # no calibration yet
calibrex check other_bag/ --tf est/frames.yaml --html check.html
calibrex drift day1/ day2/ day3/ --output drift/
See the workflow, Install and first check, or the check tutorial for the KITTI yaw-injection demo.
Hand-eye benchmark¶
Five AX=YB methods were recomputed on the
ETHZ ASL real robot-arm dataset
with one shared 80/20 split.
| Method | Rotation holdout RMSE deg ↓ | Translation holdout RMSE mm ↓ | Known-bad detection fraction ↑ | Failure rate | Runtime s |
|---|---|---|---|---|---|
| Shah | 0.585795 | 10.0628 | 1 | 0.0% | — |
| Li-Wang-Wu | 0.58728 | 17.458 | 1 | 0.0% | — |
| Dornaika-Horaud | 0.585795 | 10.0628 | 1 | 0.0% | — |
| Zhuang-Roth-Sudhakar | 0.590747 | 10.132 | 1 | 0.0% | — |
| Calibrex nonlinear refinement | 0.58721 | 10.0394 | 1 | 0.0% | — |
Calibrex's nonlinear refinement is best on translation in this benchmark; Shah is marginally best on rotation. These are held-out closure errors, not ground-truth extrinsic errors. Read the benchmark protocol and provenance.
Integration paths¶
- Open3D SLAC adapter
- Targetless LiDAR-camera adapter
- Solid-state LiDAR calibration
- Problem builder
- Schema reference
- License boundaries
Current status¶
Calibrex is alpha research software. Failed controls are reported rather than hidden; see the changelog and development roadmap for current limitations. Read the frame conventions before integrating transforms.