ReTime / real_robot /source /deployment /model_server /xtrainer2_command.py
mmmmmcyaaa's picture
Add Xtrainer2 real robot source and Chinese handoff guide
8101696 verified
Raw History Blame Contribute Delete
3.6 kB
"""xtrainer2 command layout used between StarVLA and the real robot.
The converted LeRobot dataset stores each arm as ``xyz + quaternion + gripper``.
StarVLA / PyTorch3D read that quaternion as ``w, x, y, z``. The robot HDF5
streams and the robot controller use ``x, y, z, w``.
The training transform already maps quaternion <-> rotation-6D. After
un-normalization the server therefore has 16-D **wxyz** commands. Sending
those 16 numbers to the robot without this reorder will execute the wrong
orientation.
Do not confuse these widths:
* 14 – joint angles in the source HDF5 (not used for this VLA)
* 16 – stored / robot EEF command (after this conversion, xyzw)
* 20 – model tensor after rotation-6D (horizon is a separate axis)
"""
from __future__ import annotations
import numpy as np
from starVLA.dataloader.relative_pose import compose_quaternion
QUATERNION_STARTS = (3, 11)
POSITION_SLICES = ((0, 3), (8, 11))
ACTION_DIM = 16
ROBOT_TYPE = "xtrainer2_bimanual_eef"
SOURCE_QUATERNION_FORMAT = "xyzw"
MODEL_QUATERNION_FORMAT = "wxyz"
#: Value of ``DataConfig.robot_command_layout`` that selects the conversions in
#: this module. The policy server reads that attribute instead of matching on a
#: class name.
COMMAND_LAYOUT = "xtrainer2_bimanual_eef_xyzw"
def _as_float_array(values: np.ndarray) -> np.ndarray:
array = np.asarray(values, dtype=np.float32)
if array.shape[-1] != ACTION_DIM:
raise ValueError(f"expected last dimension {ACTION_DIM}, got {array.shape}")
return array
def wxyz_to_xyzw_block(block: np.ndarray) -> np.ndarray:
"""``w, x, y, z`` -> ``x, y, z, w``."""
return np.concatenate([block[..., 1:4], block[..., :1]], axis=-1)
def xyzw_to_wxyz_block(block: np.ndarray) -> np.ndarray:
"""``x, y, z, w`` -> ``w, x, y, z``."""
return np.concatenate([block[..., 3:4], block[..., :3]], axis=-1)
def model_actions_to_robot(values: np.ndarray) -> np.ndarray:
"""Convert un-normalized StarVLA actions (wxyz) to robot commands (xyzw)."""
converted = np.array(_as_float_array(values), copy=True)
for start in QUATERNION_STARTS:
converted[..., start : start + 4] = wxyz_to_xyzw_block(converted[..., start : start + 4])
return converted
def robot_state_to_model(values: np.ndarray) -> np.ndarray:
"""Convert robot observations (xyzw) to the layout StarVLA was trained on."""
converted = np.array(_as_float_array(values), copy=True)
for start in QUATERNION_STARTS:
converted[..., start : start + 4] = xyzw_to_wxyz_block(converted[..., start : start + 4])
return converted
def compose_relative_eef_wxyz(relative_actions: np.ndarray, state_wxyz: np.ndarray) -> np.ndarray:
"""Turn rel-mode 16-D wxyz actions into absolute 16-D wxyz poses.
Positions add the current EE; rotations left-multiply the current
quaternion; grippers are already absolute and are left unchanged.
"""
relative = np.array(_as_float_array(relative_actions), copy=True)
state = np.asarray(state_wxyz, dtype=np.float32).reshape(-1, ACTION_DIM)[0]
absolute = relative
for start, end in POSITION_SLICES:
absolute[..., start:end] = relative[..., start:end] + state[start:end]
for start in QUATERNION_STARTS:
absolute[..., start : start + 4] = compose_quaternion(
state[start : start + 4],
relative[..., start : start + 4],
)
return absolute
def is_relative_action_mode(action_mode: object | None) -> bool:
if action_mode is None:
return False
return str(action_mode).lower() in {"rel", "relative"}