Skip to the lesson
FixTheRobot Explore Robotics / Localization & SLAM / M7 SLAM with ROS 2 / M7-T2

Localization & SLAM · Theory · M7

M7-T2 — TF Frames and Coordinate Consistency

The transform tree is a contract. Break it and the SLAM node cannot say where anything is.

  • Lesson 2 of 8
  • Module 7: SLAM with ROS 2
  • Theory
  • About 9 minutes to read

Module 2 introduced transforms as maths: Tab takes points from frame b to frame a. In ROS 2 those transforms live in a shared, time-stamped tree called TF, and a whole set of conventions (REP 105) says what the frames mean and who is allowed to publish each connection. Most “SLAM is broken” reports in practice are violations of those conventions. This lesson states the rules, derives the key transform, and lists the typical errors.

Learning objectives

After this lesson, you should be able to:

  • State the REP 105 frames map, odom and base_link and the properties of each.
  • Compute the map → odom transform that a SLAM node publishes from two poses.
  • Explain why every frame must have exactly one parent and who publishes each link.
  • Recognise the common errors: duplicate parents, wrong frame names, missing static transforms and leading slashes.

The problem and the intuition

A robot needs two different answers to “where am I?”. One is smooth: a pose that never jumps, because a controller using it must not see sudden steps. The other is globally correct: a pose that does not drift, because a planner needs to know where the robot is on the map. No single estimate can have both: to remove drift the estimator must sometimes correct the position, and a correction is a jump.

REP 105 solves this by using two frames. The odom frame is the world in which the robot’s pose evolves smoothly from odometry; it drifts slowly. The map frame is the globally consistent world. The SLAM or localisation node publishes the offset between them, map → odom. When it learns the robot is 0.4 m from where it thought, it changes this offset (a jump in the map frame), but odom → base_link stays smooth. A controller uses the smooth chain; a planner uses the corrected one.

map → odom absorbs the corrections; odom → base_link stays smooth. Each frame has exactly one parent, and each link has exactly one publisher.

REP 105 frame treeThe frame tree map to odom to base_link, with a laser frame attached to base_link. SLAM or a localiser publishes map to odom; odometry publishes odom to base_link; robot_state_publisher publishes the fixed base_link to laser. Every frame has exactly one parent.map odom base_link laserimu_link published by SLAM / AMCLpublished by odometry / EKFstatic: robot_state_publisher discontinuous (jumps at loop closure)continuous (smooth, drifts)rigid
The standard frame tree and who publishes each link.

The rules and the key transform

Problem. Keep a consistent, single-rooted tree of coordinate frames in which every sensor measurement can be expressed in the map frame at its measurement time.

Assumptions. The sensors are rigidly mounted (fixed transforms); odometry is continuous but drifts; the SLAM or localisation node provides a globally consistent pose of the robot at a lower rate and sometimes with jumps; clocks are consistent (M7-T3).

The REP 105 frames

REP 105 frames
FrameMeaningPropertyPublished by
mapGlobal, fixed frame; the map’s coordinatesGlobally consistent, but its transform to odom can jumpSLAM or localisation (SLAM Toolbox, AMCL)
odomWorld-fixed frame of the odometryContinuous, no jumps, drifts over timeOdometry / EKF (as the parent of base_link)
base_linkRigidly attached to the robot bodyThe robot’s pose in odomOdometry (odom → base_link)
sensor frames (laser, camera_link, imu_link)Where each sensor sitsRigid, fixed relative to base_linkrobot_state_publisher from the URDF or static_transform_publisher

Some platforms (TurtleBot 3, many ground robots) add base_footprint, the projection of the base onto the ground, as the parent of base_link. Whichever frame is the robot base must be named identically in the odometry, in the URDF and in the SLAM parameter base_frame.

The map → odom transform

The SLAM node estimates the robot pose in the map frame, Tmap,base, by scan matching. The odometry provides Todom,base (from TF). The node does not publish map → base_link, because base_link already has a parent. It publishes the transform that closes the loop:

map to odom correction
Tmap, odom=Tmap, base⋅(Todom, base)−1T_{\text{map}},\,\text{odom}=T_{\text{map}},\,\text{base}\cdot (T_{\text{odom}},\,\text{base})^{-1}

so that the chain map → odom → base_link reproduces the SLAM estimate. When the SLAM estimate improves (a loop closure), Tmap,odom changes and Todom,base does not.

Rules that the tree enforces

  • One parent per frame. TF is a tree, not a graph. Two nodes publishing different parents for the same child gives an inconsistent tree: lookups flicker or fail.
  • One publisher per link. If both the odometry node and a Gazebo plugin publish odom → base_link, the two fight.
  • No leading slash in frame ids in tf2 (base_link, not /base_link); and names are case-sensitive and spelled identically everywhere.
  • Static transforms go to /tf_static and are published once with transient-local durability; dynamic transforms to /tf at a regular rate.
  • Time travel. TF stores a buffer (10 s by default) of timestamped transforms and interpolates between them. A lookup at a time later than the newest transform needs extrapolation and fails; a lookup earlier than the buffer fails too.
Tab
transform expressing frame b in frame a (maps points from b to a), as in Module 2.
REP 105
ROS Enhancement Proposal defining the mobile-robot frames map, odom, base_link.
static transform
a fixed link, published once on /tf_static.
extrapolation error
a lookup for a time outside the range covered by the buffered transforms.

Common errors

Frame errors, symptoms and fixes
ErrorSymptomFix
Base frame name mismatch (base_link vs base_footprint)Two separate trees; SLAM cannot find the robotUse one name in URDF, odometry and SLAM parameters, or add the missing link
Two publishers of odom → base_linkThe robot jitters in RViz; transform jumps back and forthDisable one (for example the EKF or the simulator’s plugin)
Missing static transform (base_link → laser)“Could not transform” for scans; empty mapAdd it to the URDF or a static_transform_publisher
Wrong static transform (sensor mounted 90° off)Map rotated, smeared walls, laser points in the wrong place in RVizFix the URDF; check by displaying the scan in base_link
Leading slash in a frame idFrames exist but do not connectRemove the slash everywhere
Sensor stamps in the futureExtrapolation errorsFix the driver or the clock (M7-T3)

A worked example: computing map → odom and reading a broken tree

  1. The numbers. The odometry says the robot is at Todom,base = (5.0, 0.0, 0°): it drove 5 m straight. Scan matching against the map says Tmap,base = (5.4, 0.3, 0.05 rad ≈ 2.9°): the robot is a little to the left and slightly rotated relative to the odometry’s opinion.
  2. The correction. Tmap,odom = Tmap,base · Todom,base⁻¹. The inverse of the odometry pose is (−5.0, 0, 0) with no rotation. Applying the SLAM rotation of 0.05 rad to it: translation = (5.4, 0.3) + R(0.05)(−5.0, 0) = (5.4 − 5 cos 0.05, 0.3 − 5 sin 0.05) = (5.4 − 4.994, 0.3 − 0.250) = (0.406, 0.050). So map → odom is (0.406, 0.050, 0.05 rad). Check: composing it with the odometry pose reproduces the SLAM pose.
  3. What a loop closure does. Next the robot returns to a known place and the SLAM estimate shifts by 0.3 m. The odometry pose changes smoothly by its normal increments; only map → odom changes by about 0.3 m, in one step. A controller that follows the pose in odom sees no jump; the planner, using map, sees the correction.
  4. Reading a broken tree. view_frames shows: map → odom → base_footprint, and, as a separate tree, base_link → laser. The lidar frame is not connected to the map. SLAM Toolbox looks up the transform from the scan’s laser frame to its base_frame parameter. With base_frame: base_link it finds the laser but cannot connect base_link to odom: “Failed to compute odom pose”. Either set base_frame: base_footprint and publish base_footprint → base_link → laser, or add the missing static link. Exactly this is the lab M7-L2.
  5. Duplicate parents. Gazebo’s diff-drive plugin publishes odom → base_footprint and a robot_localization EKF also publishes odom → base_footprint at 30 Hz. tf2_echo odom base_footprint alternates between two slightly different values. The visible effect: the robot’s pose in RViz vibrates, and scan matching sees a noisy initial guess. Disable the plugin’s transform publication or the EKF’s (publish_tf).

Engineering implications and limitations

Engineering implications

  • Write the frame tree down for the robot, with the publisher of every link, before writing launch files.
  • Visualise the tree with ros2 run tf2_tools view_frames or ros2 run tf2_ros tf2_echo map base_link; check every sensor’s data in RViz with the fixed frame set to base_link, then odom, then map.
  • Validate extrinsics physically. Drive toward a wall and confirm the laser points in base_link are in front; rotate and confirm the scan rotates the right way.
  • Let only one node own each transform. Decide whether the simulator, an EKF or the driver publishes odom → base_link, and disable the others.
  • Use the same time source and publish transforms at a steady rate (30–50 Hz for odometry) so that lookups can interpolate.
  • Handle jumps downstream. Anything that integrates a map-frame pose over time (a controller, an obstacle tracker) should use the odom frame and transform only the goal.

Limits

REP 105 describes ground robots with a planar assumption and single-robot operation. Multi-robot systems need namespaced or prefixed frames; aerial and legged robots use other conventions (REP 103 defines the axis conventions). The tree also cannot represent uncertainty: a transform is a single value, so a poor map → odom estimate looks exactly like a good one.

Common misconceptions

“The SLAM node publishes the robot’s pose in the map frame as map → base_link.”

It publishes map → odom, so that base_link keeps its single parent (odom).

“odom is a global frame.”

It is continuous but drifts; its origin is wherever the robot started.

“Frame names are just labels and can differ between nodes.”

They are the keys of the tree; a one-character difference creates a disconnected tree.

“Static transforms can be published by any node at any time.”

They should have one publisher, usually robot_state_publisher; duplicates with different values conflict.

Engineering takeaways

  1. REP 105: map (global, may jump), odom (smooth, drifts), base_link (the robot); sensors are fixed children of base_link.
  2. The SLAM node publishes Tmap,odom = Tmap,base · Todom,base⁻¹, not map → base_link.
  3. Every frame has exactly one parent and every link exactly one publisher.
  4. Name mismatches, duplicate publishers, missing static transforms and leading slashes are the usual faults.
  5. Visualise the tree with view_frames and verify each sensor by looking at its data in base_link.

References and further reading

Primary papers and authoritative documentation.

Knowledge check

Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

The odometry says the robot is at (2, 0, 0) and the SLAM estimate in the map is (2.3, 0.1, 0). What is map → odom?

Explanation

With no rotation the correction is the difference of translations: Tmap,odom = (2.3 − 2.0, 0.1 − 0.0, 0) = (0.3, 0.1, 0). Composing it with the odometry pose gives (2.3, 0.1, 0), the SLAM estimate.

Question 2

Why does SLAM publish map → odom instead of map → base_link?

Explanation

In the tree every frame has exactly one parent, and base_link already has odom as its parent from the odometry. Publishing map → base_link would give it two parents. The correction is placed on map → odom so that the odometry stays smooth and the SLAM estimate is still reproduced.

Question 3

The robot icon in RViz vibrates between two positions. What tf fault is likely, and how do you confirm it?

Explanation

Two publishers are writing the same transform (for example odom → base_link from both the simulator plugin and an EKF) with slightly different values. Confirm with ros2 topic echo /tf or tf2_echo odom base_link, which alternate between two values, and disable one publisher.

What’s next

Even with a correct tree, a lookup can fail if the timestamps are wrong. The next lesson covers message stamps, quality-of-service profiles and the timing errors that make SLAM drop scans.

Practice this lesson

Fix a TF Configuration Error

Repair a broken TF tree (duplicate parents, wrong frame names, missing static transform).

Next lesson

M7-T3 — Sensor Topics and Message Timing

Explain stamps, QoS, message filters and the extrapolation errors caused by bad timestamps.

Nothing below is required to finish this lesson.