FixTheRobot Explore Robotics / Systems Engineering / Engineering Guides / Module 06

Engineering Guides · Module 06

TF2 in ROS 2: static transforms and debugging

tf2 keeps a tree of frames over time. A valid tree has one parent per frame, no cycles and one root.

  • Intermediate
  • Module 06: Camera–LiDAR Calibration & Coordinate Transformations
  • Written for ROS 2 Jazzy where ROS 2 is used
  • Sources checked 2026-11-06

Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.

1. Engineering problem

Everything in a robot that uses positions relies on tf2. Failures are easy to cause (two publishers for one frame, missing links, stale stamps) and hard to see without tools.

2. When is this needed?

  • Adding a sensor to the robot description.
  • When a lookup fails with a missing frame or an extrapolation error.
  • Before testing navigation or SLAM.

3. Selection and design decisions

Verified in the Jazzy tf2 tutorials: tf2_ros provides static_transform_publisher, usable on the command line or in launch files, taking --x --y --z with either --yaw --pitch --roll or a quaternion (--qx --qy --qz --qw) and --frame-id (parent) and --child-frame-id. Debugging tools are tf2_echo, tf2_monitor and view_frames (tf2_tools). The error “Lookup would require extrapolation into the future” means the requested time is newer than the latest transform.

Overview
SymptomLikely causeTool
Frame does not existName typo or no publishertf2_echo, view_frames
Two parents for a frameDuplicate publishers (URDF and a static publisher)view_frames, ros2 topic info /tf_static
Disconnected treesA missing link between two framesview_frames
Extrapolation into the future or pastClock or timestamp mismatch, slow publishertf2_monitor, check use_sim_time
Transform invertedParent and child swappedtf2_echo against expected sign

4. Advantages and disadvantages

  • URDF with robot_state_publisher keeps fixed links in one description; individual static publishers are quick and scatter the truth.
  • Static transforms are latched on /tf_static; dynamic ones are published repeatedly on /tf.
  • Quaternion arguments avoid ambiguity in Euler angle conventions.

5. Requirements and constraints

  • Every frame has exactly one parent.
  • The tree has one root.
  • Frame names follow REP 105 (map, odom, base_link) for the main chain.

6. Architecture

map ─▶ odom ─▶ base_link ─┬▶ lidar
                              └▶ camera ─▶ camera_optical

7. Implementation

  1. Define the fixed links in the URDF or in static publishers, not in both.
  2. Run ros2 run tf2_tools view_frames and check the tree.
  3. Use ros2 run tf2_ros tf2_echo parent child to compare with expected values.
  4. Use tf2_monitor to check update rates and delays.
  5. Check the tree with a script or checker (below) in CI.
tree = [("map", "odom"), ("odom", "base_link"), ("base_link", "lidar"), ("base_link", "camera"), ("odom", "camera"), ("gps", "antenna")]
parents, issues = {}, []
for p, c in tree:
    if c in parents and parents[c] != p:
        issues.append(f"{c} has two parents: {parents[c]} and {p}")
    parents.setdefault(c, p)
roots = sorted({p for p in parents.values()} - set(parents))
if len(roots) > 1:
    issues.append(f"{len(roots)} disconnected trees with roots {roots}")
for c in parents:
    seen, x = set(), c
    while x in parents:
        if x in seen:
            issues.append(f"cycle through {x}"); break
        seen.add(x); x = parents[x]
print("\n".join(issues) or "tree is valid")

Printed (checked by the site’s tests):

camera has two parents: base_link and odom
2 disconnected trees with roots ['gps', 'map']

Reading it: the checker flags that camera is given two parents (base_link and odom) and that the tree is split into two with roots gps and map. The frame names are illustrative.

8. Real examples

  • Commands (Jazzy): ros2 run tf2_ros static_transform_publisher --x 0.3 --y 0 --z 0.5 --yaw 0 --pitch 0 --roll 0 --frame-id base_link --child-frame-id lidar (not executed by the site’s tests).

9. Failure scenarios

  • Two publishers for the same child frame so the transform flickers.
  • A frame without any path to the others.
  • Simulation time not enabled on a node, so stamps differ by days.

10. Troubleshooting

  1. Run view_frames and read the tree before changing anything.
  2. Look for duplicate publishers with ros2 topic info /tf_static --verbose.
  3. Compare stamps with the clock; check use_sim_time.

11. Validation

  • The checker example prints the lines shown (executed by the site tests).
  • view_frames shows a single tree and tf2_echo values match the CAD within tolerance.

12. Engineering checklist

13. Further references

Related on FixTheRobot