Nodes/ComfyUI CV/CV Trajectory Error (ATE / RPE)
ComfyUI Node

CV Trajectory Error (ATE / RPE)

How to tell whether your odometry is actually any good

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Trajectory Error (ATE / RPE)
  • estimate
  • reference
  • aligned
  • errors
  • ate_rmse
  • ate_mean
  • ate_max
  • scale
  • rpe_rmse
  • found
◄alignsimilarity (Sim3, monocular scale-free)►
◄rpe_step1►

Ask someone whether their visual odometry "works" and you'll usually get a screenshot of a trajectory that looks a bit like the ground truth. That's not a measurement, it's a vibe. CV Trajectory Error (ATE / RPE) is the node that turns the vibe into numbers, in the two forms the SLAM literature actually standardised on.

  • ATE (absolute trajectory error) - distance between corresponding points after the two tracks have been brought into one frame. Global error, tells you how far the whole thing drifted.
  • RPE (relative pose error) - disagreement between consecutive steps. Sees local jitter and scale noise that a globally-fitted ATE smooths away entirely.

Alignment is the whole argument

Two odometry runs each start in their own coordinate frame. A monocular run doesn't even have a scale - it recovers the shape of the path and nothing about its size. Compare raw coordinates and you're measuring which arbitrary frame each run happened to pick, not accuracy.

The align dropdown is the answer:

  • none - raw comparison. Only meaningful if both tracks genuinely already share a frame and a scale. Rarely.
  • rigid (SE3) - fits a rotation and translation. Removes the frame choice.
  • similarity (Sim3) - also fits one global scale factor. This is the honest default for monocular output, and it's the node's own default for that reason. Pair it with the scale output: if the fitted scale is far from 1.0, your estimate's size is off even if its shape is right.

Mechanically it's an Umeyama fit via cv2.estimateAffine3D(..., force_rotation=True) - the same routine behind CV Register Point Clouds (3D) - with the scale returned separately rather than baked into the matrix.

Inputs and outputs

estimate is the trajectory you're scoring, Nx3. reference is what you score it against, also Nx3, one row per estimate row; the longer one gets truncated. rpe_step sets the frame gap the relative error is measured over - 1 is frame-to-frame motion, larger values report drift across that window.

Note the comparison is strictly index-for-index. Row 40 of the estimate is compared to row 40 of the reference, so a dropped or duplicated frame silently poisons everything downstream. Align them before you get here.

Outputs:

  • aligned - your estimate mapped into the reference's frame, Nx3. Draw this, not the raw input, when you overlay the two paths.
  • errors - (N,) per-point distance. Chart it against frame index to see where the drift happened, which is far more actionable than a single RMS number.
  • ate_rmse, ate_mean, ate_max - the summary statistics, in the reference's unit. ate_max is the worst single point: peak drift.
  • scale - the global scale the alignment applied (1.0 for none and rigid).
  • rpe_rmse - relative-pose RMS over rpe_step frames, after alignment.
  • found - false when fewer than three usable pairs survive. Then the metrics are zeroed and the estimate passes through unaligned rather than erroring.

The natural pairing

Score CV Visual Odometry (Sequence) output here - its trajectory output is exactly the Nx3 this wants, and the pack's own example does precisely that against the KITTI ground-truth poses. One warning before you redistribute anything: that poses file is CC BY-NC-SA, non-commercial, the only non-commercial asset in the repository. Your own GPS or wheel-odometry logs are the commercial-safe substitute.

Installing it

Manager → ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart. Python ≥ 3.12, recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93. No models, no downloads. GPL-3.0, a fork of opencv-comfyui, and the author's warning that this is LLM-assisted code not meant for production without review applies with extra force to metric code - validate the numbers against a case where you know the answer before you quote them at anyone.

Where people get burned

Comparing a monocular run with rigid. You'll get a huge ATE that says more about the missing scale than about the odometry. Use Sim3 and read scale.

Treating NaN rows as fine. Non-finite points are excluded from the fit and get NaN in errors, so ate_mean can look respectable while a stretch of your trajectory was garbage. Check the mask, and look at errors as a curve.

Only reading the RMS. A run that's perfect for 400 frames and then jumps 30 metres has a modest RMSE. ate_max and the error curve are how you catch that.

Expecting an absolute unit. ATE is in the reference's unit. With a constant-scale monocular run against a reference in metres, the numbers are only meaningful once the alignment has fitted a scale - which is another reason Sim3 is the default.

Categoryimage/CV/features

Inputs (4)

NameTypeDefaultDescription
estimateNPARRAYThe trajectory being scored, Nx3 (from 'CV Visual Odometry (Sequence)' or 'CV Compose Pose').
referenceNPARRAYThe trajectory to score AGAINST, Nx3, one row per estimate row. The longer one is truncated.
alignCOMBOsimilarity (Sim3, monocular scale-free)How much of the difference to treat as a choice of coordinate frame rather than error. 'none' compares raw coordinates (only meaningful when both already share a frame AND a scale). 'rigid (SE3)' fits rotation + translation. 'similarity (Sim3)' also fits one global scale - required for monocular odometry, which recovers shape but not size.
rpe_stepINT11–10000Frame gap the relative-pose error is measured over. 1 compares frame-to-frame motion; a larger gap reports drift over that window instead.

Outputs (8)

NameTypeDescription
alignedNPARRAYThe estimate mapped into the reference's frame, Nx3 - draw this on top of the reference, not the raw input.
errorsNPARRAY(N,) float64 per-point ATE distance. Chart it against the frame index to see WHERE the drift happened.
ate_rmseFLOATRoot-mean-square ATE, in the reference's unit.
ate_meanFLOAT—
ate_maxFLOATWorst single-point error - the drift at its peak.
scaleFLOATThe global scale the alignment had to apply (1.0 for 'none' and 'rigid'). Far from 1 with Sim3 means the estimate's size is off even though its shape may be right.
rpe_rmseFLOATRoot-mean-square relative-pose error over 'rpe_step' frames, after alignment.
foundBOOLEAN—