Skip to content

Coordinate Frames

Practical

Coordinate frames (or reference frames) are the foundation for how robots understand position in space. A frame consists of an origin point and three orthogonal axes (x, y, z) that define directions of measurement. Every position, velocity, or orientation in robotics is defined relative to some frame.

A coordinate frame defines:

  • An origin — the (0, 0, 0) point where measurements start
  • Three orthogonal axes — x, y, z directions
  • A handedness — typically right-handed in robotics
z (up)
│
│
│
●───────► y (left)
/origin
/
↙
x (forward)
Right-Hand Rule (ROS Convention)

Frames can be fixed (attached to the world) or moving (attached to a robot or sensor).

  • Sensors report locally: A camera sees objects in its frame — you must transform to the robot frame to act
  • Multiple perspectives: “1 meter to the left” depends on whose left
  • Kinematic chains: Robot arms have frames for each link: base → link1 → link2 → gripper
  • Sensor fusion: Without consistent frames, you can’t combine data from multiple sensors
map ← World-fixed (SLAM corrected)
└── odom ← Robot starting point (drifts over time)
└── base_link ← Robot body center
├── camera_link
│ └── camera_optical_frame
├── lidar_link
└── arm_base
└── ... → gripper_link
FrameDescriptionROS Name
World/MapFixed global reference, does not movemap
OdometryTracks motion from start, continuous but driftsodom
Robot BaseAttached to robot body, moves with robotbase_link
SensorLocal to each sensor (camera, LiDAR, IMU)*_link
End-EffectorAt gripper or tool tipgripper_link

Different systems use different axis conventions:

Convention X-axis Y-axis Z-axis Used By
───────────────────────────────────────────────────────
FLU (ROS) Forward Left Up ROS, ground robots
FRD Forward Right Down PX4, aircraft
NED North East Down Aviation, GPS
ENU East North Up Geographic systems
Camera Right Down Forward Computer vision
USD/Graphics Right Up -Forward NVIDIA Isaac, Blender

ROS 2 uses TF2 to manage frames as a directed tree:

  • Each frame has exactly one parent (except the root)
  • No cycles — unambiguous path between any two frames
  • Time-buffered — query where a frame was at a past time
  • Static transforms (/tf_static): Fixed relationships like sensor mounts
  • Dynamic transforms (/tf): Changing relationships like odometry or joint positions
Terminal window
# Generate PDF of current transform tree
ros2 run tf2_tools view_frames
# Print transform between two frames
ros2 run tf2_ros tf2_echo map base_link
Terminal window
ros2 run tf2_ros static_transform_publisher \
--x 0.1 --y 0.0 --z 0.2 \
--roll 0.0 --pitch 0.0 --yaw 0.0 \
--frame-id base_link \
--child-frame-id camera_link
import rclpy
from rclpy.time import Time
from tf2_ros import Buffer, TransformListener
rclpy.init()
node = rclpy.create_node('frame_query')
tf_buffer = Buffer()
tf_listener = TransformListener(tf_buffer, node)
# Spin briefly to receive transforms
rclpy.spin_once(node, timeout_sec=1.0)
# Check if transform exists
can_transform = tf_buffer.can_transform('map', 'base_link', Time())
print(f"Can transform map→base_link: {can_transform}")
# List all known frames
print(tf_buffer.all_frames_as_yaml())
  1. Wrong frame assumption: Data is in a different frame than expected
  2. Forgetting camera conventions: Optical frames are oriented differently
  3. Ignoring time: Transforms change — use the correct timestamp
  4. Mixing conventions: ROS uses meters/radians, other systems may differ