Embodiments#
An embodiment descriptor is a YAML file describing the robot from the data generator’s
point of view: where to read its end-effector pose(s) in the observation buffer, and how to
convert between target end-effector poses and the environment’s action vector. It is loaded
into an embodiment adapter — the object that performs those transforms at runtime — via
embodiment_adapter_from_yaml().
This is the entire robot-specific surface of the framework: the generator itself never sees joint names, controllers, or kinematics, only “give me the EEF pose” and “turn this target pose into an action.”
Note
Conventions used throughout. Poses are 4×4 homogeneous matrices. Quaternions are
(x, y, z, w) ordered — identity is [0, 0, 0, 1] — in observations, action layouts,
and everywhere in between. Pose observations are read from the "policy" observation
group unless obs_group says otherwise.
Adapter Types#
The type: field selects the adapter class from EMBODIMENT_TYPE_REGISTRY:
|
Use for |
Shipped examples |
|---|---|---|
|
Single arms driven by relative (delta) pose IK — e.g. the Franka tasks. The action is the clipped delta from the current EEF pose to the target, plus a gripper dimension. |
|
|
Humanoids driven by absolute pose targets through a whole-body IK controller. The action carries one absolute pose per arm plus hand-joint positions. |
|
New morphology + controller combinations register a new adapter class in the registry — see Writing a New Embodiment.
What the Adapter Does at Runtime#
Every adapter is bound to the live environment once (bind_env(), done automatically when
the Datastream is built) and then serves five queries. The first four are
the generator’s entire view of the robot:
get_eef_poses()— read each end-effector’s current pose from the configured observation keys (pose_obs_keys).target_eef_pose_to_action()— the forward direction: turn per-EEF target poses plus the passthrough channels into one action forenv.step(), optionally adding action noise.action_to_target_eef_pose()— the inverse direction: recover the target poses encoded in a recorded action. This is how the source demonstrations’ controller targets are extracted.actions_to_passthrough_actions()— pull the non-pose channels (gripper or hand joints) out of recorded actions so the generator can replay them verbatim.
Passthrough actions are the action dimensions that carry no pose information — gripper actuation, hand joints, a mobile-base command — and are copied from the source segment rather than recomputed.
The adapter also owns the robot’s raw joint state (get_joint_positions(),
get_joint_names()), which motion planners read as their planning start state. Nothing
else in the framework touches the robot articulation directly.
Single-Arm Adapter: delta_pose_ik_single_arm#
The action vector is [delta_position (3), delta_rotation (3), gripper (gripper_dim)] —
6 + gripper_dim values in total. The pose part is the delta from the current EEF pose to
the target; the rotation delta uses the compact axis-angle form (unit axis × angle in
radians).
From franka_ik_rel.yaml:
type: delta_pose_ik_single_arm
name: franka_panda
description: Franka 7-DOF arm with parallel gripper, delta-pose IK control.
eef_name: franka
pose_obs_keys:
pos: eef_pos
quat: eef_quat
action_layout:
gripper_dim: 1
clip_pose_action_to_unit: true
eef_offset: [0.0, 0.0, 0.0]
Field reference#
Key |
Required |
Default |
Meaning |
|---|---|---|---|
|
yes |
— |
Adapter class; |
|
yes |
— |
Identifier for the embodiment (used in logs and registries). |
|
no |
|
Human-readable summary. |
|
yes |
— |
Name of the single end-effector. Must match the EEF name used by the task
descriptor’s |
|
no |
|
Observation-buffer group holding the pose keys. |
|
yes |
— |
Observation key with the EEF position (3-vector [m]). |
|
yes |
— |
Observation key with the EEF orientation quaternion, (x, y, z, w) ordered. |
|
yes |
— |
Number of trailing action dimensions occupied by the gripper. |
|
no |
|
Clamp the 6-D pose part of the action to |
|
no |
|
Translation [m] from the robot’s kinematic control link to the frame the pose observation reports — see The eef_offset Frame Shift. |
The Transform, Step by Step#
Here is one 7-D Franka action followed through both directions. Suppose the current EEF pose is at the origin with identity rotation, and the recorded action is:
action = [0.10, 0.20, 0.30, 0.10, 0.00, 0.00, 0.7]
└─ delta position ─┘└─ delta rotation ─┘ └ gripper
Inverse — action → target pose (action_to_target_eef_pose). Used when loading source
demonstrations: it recovers the controller target each recorded action encoded.
Split the action:
delta_pos = [0.10, 0.20, 0.30],delta_aa = [0.10, 0, 0]. The gripper value carries no pose and is ignored here.Read the current EEF pose from the observation buffer (shifted by
-eef_offsetif one is configured).Target position = current position +
delta_pos→(0.10, 0.20, 0.30).Target rotation =
R(delta_aa) @ current_rotation— the axis-angle vector[0.10, 0, 0]means “rotate 0.10 rad about the x axis”, composed onto the current orientation.
Forward — target pose → action (target_eef_pose_to_action). Used during generation:
each transformed waypoint becomes the action that is actually stepped.
Delta position = target position − current position.
Delta rotation =
target_rotation @ current_rotationᵀ, converted back to the compact axis-angle vector.Optional noise: if the subtask sets
action_noise, Gaussian noise scaled by it is added to the 6-D pose part — never to the gripper.Optional clipping: with
clip_pose_action_to_unit: true, the pose part is clamped to[-1, 1]— after the noise, so noise cannot push the action out of range.The gripper value is appended verbatim from the source demonstration’s passthrough channel — the adapter never invents gripper commands.
Running the forward direction on the inverse’s output reproduces the original action (up to noise and clipping). That round-trip property is exactly what the unit tests check — and what you should check first for a new embodiment (see below).
Bimanual Adapter: absolute_pose_whole_body_bimanual#
The action vector is
[left_pos (3), left_quat (4), right_pos (3), right_quat (4), hand_joints, ...extras].
Poses are absolute targets tracked by a whole-body IK controller. The hand-joints block
interleaves both hands’ joints in URDF order; gripper_action_indices records which
positions belong to which arm.
From gr1_ik_abs.yaml (the GR1T2 humanoid; g1_ik_abs.yaml has the same shape for the G1):
type: absolute_pose_whole_body_bimanual
name: gr1t2
eefs:
left:
pose_obs_keys: {pos: left_eef_pos, quat: left_eef_quat}
gripper_action_indices: [0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 20]
right:
pose_obs_keys: {pos: right_eef_pos, quat: right_eef_quat}
gripper_action_indices: [5, 6, 7, 8, 9, 15, 16, 17, 18, 19, 21]
action_layout:
left_pose_slice: [0, 7] # pos (3) + quat (4)
right_pose_slice: [7, 14]
hand_joints_slice: [14, 36] # 11 DOF per hand, interleaved
canonicalize_quat: true
Field reference#
Key |
Required |
Default |
Meaning |
|---|---|---|---|
|
yes |
— |
Adapter class; |
|
yes |
— |
Identifier for the embodiment. |
|
no |
|
Human-readable summary. |
|
no |
|
Observation-buffer group holding the pose keys. |
|
yes |
— |
One block per arm. The EEF names are literally |
|
yes |
— |
|
|
yes |
— |
That arm’s positions within the hand-joints block (0-based, relative to the block, not the full action). Left and right must not overlap; the length is that arm’s gripper action dim. |
|
yes |
— |
Half-open |
|
yes |
— |
Range of the right pose block. Must immediately follow the left one. |
|
yes |
— |
Range of the interleaved hand-joints block. Must immediately follow the right pose block. |
|
no |
|
Flip quaternions to |
|
no |
|
Extra non-EEF passthrough channels, |
All slice layouts are validated at load time — spans, adjacency, index ranges, and overlaps fail immediately with a named reason.
The eef_offset Frame Shift#
eef_offset (single-arm only) is a translation [m] from the robot’s kinematic control link
to the end-effector frame the observation reports — equivalently, the difference between the
env-reported EEF frame and the frame the source dataset’s annotations use. When the two
frames agree it is zero; when they do not, generation silently produces offset grasps, so
this is the first thing to check when transformed segments look shifted.
Concrete example: the base Franka IK-Rel tasks report the inter-fingertip end_effector
frame, and the MimicGen source dataset is annotated in that same frame — offset zero. The
SkillGen cube-stacking dataset, however, is annotated in the panda_hand frame (cuRobo’s
planning link), so its embodiment config (franka_ik_rel_skillgen.yaml) sets
eef_offset: [0, 0, 0.1034] — the fingertip-to-hand distance.
Writing a New Embodiment#
A new embodiment for an already-supported control scheme is pure YAML — no code. Work through these steps:
Identify the observations. Find the observation keys holding each end-effector’s position and quaternion (
pose_obs_keys), and which group they live in (obs_group). The quaternion observation must be (x, y, z, w) ordered.Map the action vector. Work out the pose slice(s), gripper dims/indices, and whether pose actions are relative or absolute — this picks the adapter
type. Anything that is neither pose nor gripper becomes apassthrough_channelsentry.Determine ``eef_offset``. Compare the env’s reported EEF frame with the frame your source dataset’s poses use (single-arm; see above).
Round-trip it in a unit test. The adapter’s two directions must be mutually consistent: converting an action to a target pose and back must reproduce the action. This runs in seconds, without Isaac Sim:
import torch from autodata_interfaces.embodiments import embodiment_adapter_from_yaml from autodata_tests.interfaces.mocks import MockEnv def test_my_embodiment_round_trip(): adapter = embodiment_adapter_from_yaml("my_robot.yaml") # Fake the env: only the pose observations the adapter reads. adapter.bind_env( MockEnv( obs_buf={ "policy": { "eef_pos": torch.zeros(1, 3), "eef_quat": torch.tensor([[0.0, 0.0, 0.0, 1.0]]), # (x, y, z, w) } } ) ) action_in = torch.tensor([[0.1, 0.2, 0.3, 0.1, 0.0, 0.0, 0.7]]) target = adapter.action_to_target_eef_pose(action_in)["my_eef"][0] action_out = adapter.target_eef_pose_to_action( {"my_eef": target}, {"my_eef": action_in[0, 6:]}, env_id=0 ) assert torch.allclose(action_out, action_in[0], atol=1e-5)
The shipped tests in
autodata_tests/interfaces/embodiments/show the full pattern (disable clipping for large test deltas, batch shapes, per-arm variants). Run them withpytest autodata_tests/interfaces/embodiments/.Verify against real data. Replay a recorded demonstration through
action_to_target_eef_poseand check the recovered targets track the recorded EEF poses.
If no registered adapter fits the robot’s control scheme, implement a new adapter class and
add it to EMBODIMENT_TYPE_REGISTRY — the two existing adapters are the template: subclass
the morphology base class, implement the three action-encoding methods, and provide
from_dict.
Troubleshooting#
Symptom |
Likely cause |
Fix |
|---|---|---|
Every grasp lands offset from the object by the same fixed distance. |
|
Measure the offset between the two frames and set |
Assets or motions come out flipped ~180°, or rotations drift wildly. |
Quaternion order mismatch: a (w, x, y, z) quaternion fed where (x, y, z, w) is expected, or vice versa. |
The framework is (x, y, z, w) throughout, identity |
Large motions execute slowly or fall short; fast recorded motions replay truncated. |
The 6-D pose action saturates at the |
Expected for delta-IK envs whose action term clips — per-step deltas should be
small. If the env does not clip, set |
|
|
List the env’s observation terms and copy the exact key and group names. |
Fingers don’t move — or the wrong hand’s fingers move (bimanual). |
|
Indices are 0-based within |
|
The adapter was bound twice, or used before binding. |
Build the adapter and hand it to the Datastream — it binds the env exactly once. |