Skip to content

fix(autoware_carla_interface): publish the ego pose at base_link instead of the vehicle center - #13372

Merged
hakuturu583 merged 6 commits into
autowarefoundation:mainfrom
scien-inc:feat/carla-interface-base-link-rear-axle
Oct 2, 2026
Merged

hakuturu583 merged 6 commits into
autowarefoundation:mainfrom
scien-inc:feat/carla-interface-base-link-rear-axle

Conversation

@tomoki2252

@tomoki2252 tomoki2252 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Description

SensorKitLoader places every sensor wheelbase / 2 behind its base_link calibration (it subtracts wheelbase / 2 from x before spawning), so in this bridge base_link is the rear axle and the CARLA actor origin is the vehicle center, wheelbase / 2 ahead of it. The GNSS pose, the ground truth localization (publish_ground_truth_localization) and the RViz initial pose still used the actor origin as base_link, which put the ego 1.425 m (with the default wheelbase) ahead of its sensors and of every other actor.

This applies the same wheelbase offset in those three places, using the value the sensor loader already reads from the sensor mapping (vehicle_config.wheelbase, default 2.85 m), so the published poses stay consistent with the sensor placement.

Related links

Parent Issue:

  • None.

Related:

How was this PR tested?

colcon build --packages-select autoware_carla_interface --symlink-install and pre-commit pass.

Run against a live CARLA 0.9.16 server on Town01 with planning_simulator.launch.xml and this bridge, localization replaced by the package's carla_state_publisher (GNSS pose to /localization/kinematic_state, no NDT), and the ground truth objects of #13321 as the reference. Compared with the rear axle that CARLA reports through the wheel physics, base_link moved from 1.40 m ahead of it to 0.03 m behind it (mostly the default wheelbase against the Prius's 2.82 m). A vehicle placed right beside the ego is drawn beside it in RViz after the change and 1.4 m behind it before; driving to a goal 200 m away behaved the same. A video is attached.

Notes for reviewers

The offset reuses the sensor loader's wheelbase on purpose: the carla_sensor_kit calibration is authored against WB = 2.85 m, so the poses have to use the same number as the sensors; vehicle_info.wheel_base (2.79 m for sample_vehicle) and the CARLA physics wheelbase differ from it.

The RViz initial pose is converted the other way, so a 2D Pose Estimate now puts base_link on the clicked point. spawn_point still places the actor origin.

Interface changes

None.

Effects on system behavior

With publish_ground_truth_localization (the E2E planning setup) the ego localization moves from the vehicle center to the rear axle, 1.425 m backward with the default wheelbase; a setup that compensated for the old offset elsewhere, for example in its goal or spawn positions, will see that compensation become an error. spawn_point keeps placing the actor origin, so base_link now starts wheelbase / 2 behind the given coordinates.

With NDT localization the estimated pose does not change: the GNSS pose only seeds the initialization, and a 2D Pose Estimate now lands base_link on the clicked point instead of 1.4 m behind it. Any consumer that takes the GNSS pose as base_link gets the corrected pose, for example the package's carla_state_publisher (not started by the shipped launch files) or external tools that read /sensing/gnss/pose_with_covariance.

The twist of that odometry is shifted with the pose, so while turning its twist.linear.y changes by omega_z * wheelbase / 2 from main, which the E2E planners read as the ego lateral velocity.

Update after review: shift the twist with the pose

c32da50 also shifts the linear velocity of the ground truth odometry back by the same wheelbase / 2 as the pose, as v + omega x r. This cancels exactly the mismatch between pose and twist that this PR introduced. Thanks to @ujinf74 for the measurement that caught it.

The other ~0.4 m found in the review is on main as well: with the default physics, CARLA reports the velocity of the physics body's center of mass, and the Python API exposes neither the velocity of another point nor where the center of mass is (center_of_mass in the physics control is only an offset added to the computed one). Removing that part would need the base_link velocity computed by differencing its pose between ticks, which adds state and teleport handling, so I kept it out of this PR; I can follow up on it separately if needed.

Measured in my own run with the review's settings on CARLA 0.9.16 (Town10HD_Opt, vehicle.toyota.prius on autopilot, 60 Hz synchronous, max_substep_delta_time 2 ms, 600 frames). A probe calls _publish_ground_truth_odometry() of main, cfb1381 and c32da50 on the same vehicle every frame and takes the true velocity from central differences of each version's own published poses (probe written and run with Claude Code). Over the 197 samples with |yaw rate| > 1 deg/s, cfb1381 reproduces the review's table to within 0.0001 m/s.

twist.linear.y at base_link, 197 turning samples, rms [m/s]:

cfb1381 (reviewed) c32da50 (fix)
as published 0.5083 0.0353
true at base_link 0.1915 0.1915
error as published 0.6992 0.1578
residual after adding omega x r 0.1578 –

c32da50 gives exactly the residual predicted by adding omega x (wheelbase / 2) to cfb1381, removing 77% of the error as the review estimated. The rest corresponds to a 0.416 m lever arm, the distance from the actor origin to the center of mass; main shows the same (0.1586 m/s, 0.418 m) against its own pose at the actor origin. A 60 s run and the default 0.05 s step (10 ms substeps) give the same result, about 0.61 → 0.14 m/s.

At the default 0.05 s step, the yaw rate CARLA reports jitters around the actual turning rate even on straight roads, flipping sign every frame, by about 0.04 rad/s rms; twist.angular.z on main already carries this, and it disappears at 60 Hz with 2 ms substeps. Because the shift multiplies the yaw rate by wheelbase / 2, the jitter now also reaches twist.linear.y, as about 0.05 m/s rms while driving straight (0.005 m/s before).

AI assistance

I used Claude Opus 5.5 / Opus 5 / Fable 5 to check, modify, and review the code.
I have personally reviewed the entire content of this PR.

town01_pr_sample.mp4

…ead of the vehicle center

The sensor kit loader spawns sensors wheelbase/2 ahead of their base_link
calibration, so base_link is the rear axle and the CARLA actor origin is
the vehicle center. The GNSS pose, the ground-truth localization
(kinematic_state and map->base_link) and the RViz initial pose still used
the actor origin as base_link, which put the ego wheelbase/2 (1.4 m with
the default wheelbase) ahead of the sensors and of the other actors.
Apply the same wheelbase offset in those three places.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>
@github-actions github-actions Bot added the component:simulation Virtual environment setups and simulations. (auto-assigned) label Sep 11, 2026
@github-actions

Copy link
Copy Markdown

Thank you for contributing to the Autoware project!

🚧 If your pull request is in progress, switch it to draft mode.

Please ensure:

…README and the pose conversions

Note in the README that the sensor mapping's wheelbase also places base_link
wheelbase/2 behind the actor origin for the published poses, and that
spawn_point places the actor origin. Comment the reverse conversion of the RViz
initial pose and shorten the helper docstring to the line length. No behavior
change.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>
@github-actions github-actions Bot added the type:documentation Creating or refining documentation. (auto-assigned) label Sep 17, 2026
@tomoki2252
tomoki2252 marked this pull request as ready for review September 17, 2026 03:41

@ujinf74 ujinf74 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base_link diagnosis looks right to me, and the mechanics hold up — the offset goes through Transform.transform() rather than a flat 2D shift, and the rotation is carried through unchanged so the body-frame conversion is untouched.

This is about the one thing the Notes leave open — "the twist is still the actor origin's velocity". After this change that is no longer neutral: Odometry.twist is expressed in child_frame_id, which stays "base_link" while only the pose moves, so one message now refers to two points ~1.8 m apart. Before this PR both were the actor origin: offset from the sensors, but self-consistent.

Measured on Town10HD, vehicle.toyota.prius, autopilot, 60 Hz synchronous, max_substep_delta_time 2 ms; 197 turning samples of 600. The twist is rebuilt exactly as _publish_ground_truth_odometry does, and the true velocity of the base_link point comes independently from central-differencing that point's position.

twist.linear.y at base_link, turning samples, rms [m/s]:

as published 0.5084
true at base_link 0.1914
error as published 0.6993
residual after adding omega x r 0.1577

The noise floor — the same point differenced two ways — is 0.0909 m/s, so this is 7.7x the floor. Regressing (published − true) on the lever-arm term gives R² = 0.9999, so the error is that term and nothing else. In all 169 samples with |omega_z| > 0.05 the two have opposite signs; worst is omega_z −0.678 rad/s at 8.0 m/s publishing +0.904 m/s against a true −0.340. The standing/straight comparison in the description is the one condition under which this term vanishes, which is why it passed.

The part I did not expect. The slope came out 1.287x the term computed with wheelbase / 2, so I solved for the reference point separately: regressing (reported velocity − central-differenced velocity of the actor origin) on omega_z gives 0.4116 * omega_z, R² = 0.9964. get_velocity() is not referenced to the actor origin — it reports a point ~0.412 m ahead of it along body x. Declared center_of_mass.x is 0.500 for this vehicle, 18% off the measured value, so that number does not look safe either.

So the lever arm to base_link is 1.425 + 0.412 = 1.837 m, and the independent error regression required 1.834 m — agreeing to 3 mm. Adding omega x (wheelbase / 2) alone would recover about 78% of it.

Worth noting autoware_pose_instability_detector dead-reckons this twist and ERRORs when it disagrees with the pose, though I have not checked whether it runs with publish_ground_truth_localization.

Not asking you to widen this PR — you flagged the twist yourself and keeping the pose change separate is defensible. Would you rather take it here or as a follow-up? Happy to open the follow-up with this measurement.

Measured with Claude Code; I can post the probe scripts if you want to reproduce.

The ground truth odometry places the pose at base_link, but the twist was
still CARLA's velocity, so while turning twist.linear.y described a point
about 1.8 m ahead of the pose. Move the linear velocity to base_link as
v + omega x r, with the same wheelbase/2 offset as the pose, before the
CARLA-to-ROS conversion.

CARLA reports the velocity of the physics body's center of mass, whose
position the Python API does not expose, so the offset between it and the
actor origin (about 0.4 m on the Prius) remains as before.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>
… with the pose

CARLA reports the velocity of the center of mass, so shifting it by
wheelbase/2 leaves the twist about 0.4 m ahead of base_link rather than
at it. Describe the shift instead of claiming it reaches base_link. No
behavior change.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>
@hakuturu583
hakuturu583 enabled auto-merge (squash) October 1, 2026 03:23
@hakuturu583 hakuturu583 added the run:build-and-test-differential Mark to enable build-and-test-differential workflow. (used-by-ci) label Oct 1, 2026
@ujinf74

ujinf74 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

The fix covers what I raised, thanks for taking it on — and your read on the remaining ~0.4 m matches what I measured, so here are the numbers in case they help whoever picks up the follow-up.

Regressing (reported velocity − central-differenced velocity of the actor origin) on omega_z, on Town10HD with vehicle.toyota.prius at a 2 ms substep:

0.4116 * omega_z,  R^2 = 0.9964

So get_velocity() reports a point about 0.412 m ahead of the actor origin along body x, which is the residual you are seeing. Two things that may save some time:

  • get_physics_control().center_of_mass.x is 0.500 for this vehicle, 18% away from the measured 0.412, so it does not look safe to use as the offset.
  • The full lever arm from that point to base_link came out at 1.425 + 0.412 = 1.837 m, and an independent regression of the twist error itself required 1.834 m — the two agree to 3 mm, which is why I read 0.412 as the reference-point offset rather than noise.

One vehicle and one map, so the 0.412 is presumably vehicle-dependent; the structure — that the reference point is not the actor origin — should not be.

@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.44%. Comparing base (4912efa) to head (641fd96).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #13372   +/-   ##
=======================================
  Coverage   37.44%   37.44%           
=======================================
  Files        2030     2030           
  Lines      142134   142134           
=======================================
  Hits        53217    53217           
  Misses      88917    88917           
Flag Coverage Δ *Carryforward flag
full-suite 37.44% <ø> (ø) Carriedforward from 4912efa

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hakuturu583
hakuturu583 merged commit bff1089 into autowarefoundation:main Oct 2, 2026
30 checks passed
shmpwk pushed a commit to tier4/pilot_auto_simulation that referenced this pull request Oct 14, 2026
…ead of the vehicle center (autowarefoundation/autoware_universe#13372)

* fix(autoware_carla_interface): publish the ego pose at base_link instead of the vehicle center

The sensor kit loader spawns sensors wheelbase/2 ahead of their base_link
calibration, so base_link is the rear axle and the CARLA actor origin is
the vehicle center. The GNSS pose, the ground-truth localization
(kinematic_state and map->base_link) and the RViz initial pose still used
the actor origin as base_link, which put the ego wheelbase/2 (1.4 m with
the default wheelbase) ahead of the sensors and of the other actors.
Apply the same wheelbase offset in those three places.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>

* docs(autoware_carla_interface): describe the base_link offset in the README and the pose conversions

Note in the README that the sensor mapping's wheelbase also places base_link
wheelbase/2 behind the actor origin for the published poses, and that
spawn_point places the actor origin. Comment the reverse conversion of the RViz
initial pose and shorten the helper docstring to the line length. No behavior
change.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>

* fix(autoware_carla_interface): move the ground truth twist to base_link

The ground truth odometry places the pose at base_link, but the twist was
still CARLA's velocity, so while turning twist.linear.y described a point
about 1.8 m ahead of the pose. Move the linear velocity to base_link as
v + omega x r, with the same wheelbase/2 offset as the pose, before the
CARLA-to-ROS conversion.

CARLA reports the velocity of the physics body's center of mass, whose
position the Python API does not expose, so the offset between it and the
actor origin (about 0.4 m on the Prius) remains as before.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>

* docs(autoware_carla_interface): say the ground truth twist is shifted with the pose

CARLA reports the velocity of the center of mass, so shifting it by
wheelbase/2 leaves the twist about 0.4 m ahead of base_link rather than
at it. Describe the shift instead of claiming it reaches base_link. No
behavior change.

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>

---------

Signed-off-by: Tomoki Cho <tomoki225255@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:simulation Virtual environment setups and simulations. (auto-assigned) run:build-and-test-differential Mark to enable build-and-test-differential workflow. (used-by-ci) type:documentation Creating or refining documentation. (auto-assigned)

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants