Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 0 additions & 70 deletions egomimic/hydra_configs/data/industry_eva_pi.yaml

This file was deleted.

1 change: 1 addition & 0 deletions egomimic/hydra_configs/data/mecka.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ train_datasets:
transform_list:
_target_: egomimic.rldb.embodiment.human.Human.get_transform_list
mode: cartesian_6d
fix_left_wrist_convention: true
# 18 -> 20-D ee_pose proprio (zero grip slots at 9/19) so the pi0.5
# State: prompt bins align with the robot 20-D layout on every config.
pad_proprio_gripper: true
Expand Down
44 changes: 0 additions & 44 deletions egomimic/hydra_configs/data/mecka_scale_cotrain_pi.yaml

This file was deleted.

4 changes: 4 additions & 0 deletions egomimic/hydra_configs/data/scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ train_datasets:
transform_list:
_target_: egomimic.rldb.embodiment.human.Human.get_transform_list
mode: cartesian_6d
# Scale exports were written by the pre-fix hand-pose converter too:
# measured on all 20 production episodes, the left wrist matches
# rot_left to <= 2.06 deg with the right-hand control clean.
fix_left_wrist_convention: true
# 18 -> 20-D ee_pose proprio (zero grip slots at 9/19) so the pi0.5
# State: prompt bins align with the robot 20-D layout on every config.
pad_proprio_gripper: true
Expand Down
17 changes: 0 additions & 17 deletions egomimic/hydra_configs/model/pi0.5_cotrain_mecka_scale.yaml

This file was deleted.

57 changes: 46 additions & 11 deletions egomimic/rldb/embodiment/human.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
PoseCoordinateFrameTransform,
QuaternionPoseToYPR,
Reshape,
RotateLocalFrame,
SplitKeys,
Transform,
UnpadGripperZeros,
Expand Down Expand Up @@ -373,6 +374,7 @@ def get_transform_list(
"keypoints_wristframe_quat",
],
stride: int = 3,
fix_left_wrist_convention: bool = False,
pad_proprio_gripper: bool = False,
allow_legacy_rotation: bool = False,
) -> list[Transform]:
Expand All @@ -383,12 +385,37 @@ def get_transform_list(
the modes in ``LEGACY_ROTATION_MODES``; only the data/rollout boundary
(viz, a checkpoint that predates the 6D conversion) may set it.

``fix_left_wrist_convention`` retroactively corrects the LEFT
wrist-frame convention of zarrs written before the ``rot_left`` fix in
``mecka_to_zarr.compute_hand_pose_xyzquat`` (which double-mirrored the
left hand onto the right hand's spatial convention): the raw left
pose keys (ee_pose in cartesian modes, wrist_pose in keypoints modes;
the converter writes the same rotation into both) are right-multiplied
by Rz(180 deg) before any frame math, exactly equivalent to
reconverting. It is a property of the DATA, not of the vendor: enable
it for any zarr written by the pre-fix hand-pose converter (measured:
every mecka and scale episode in the production set), and disable it
for data reconverted after the fix, which would double-flip. Aria and
LightWheel are not covered by that measurement -- no such episodes are
in the production set -- so leave them unset.
``left_wrist_convention`` re-derives the verdict from a zarr's own
keypoints when a setting has to be checked.

``pad_proprio_gripper`` pads the cartesian proprio
``observations.state.ee_pose`` 18 -> 20 (zero grip slots at 9/19) so
the pi0.5 ``State:`` prompt bins align positionally with the robot
20D layout. 6D modes only.
"""
_reject_legacy_rotation(cls, mode, allow_legacy_rotation)
is_keypoints = mode.startswith("keypoints")
prefix: list[Transform] = []
if fix_left_wrist_convention:
fix_keys = (
["left.action_wrist_pose", "left.obs_wrist_pose"]
if is_keypoints
else ["left.action_ee_pose", "left.obs_ee_pose"]
)
prefix = [RotateLocalFrame(keys=fix_keys)]
if pad_proprio_gripper and not mode.endswith("_6d"):
raise ValueError(
"pad_proprio_gripper needs a 6D-encoded ee_pose proprio: a "
Expand All @@ -402,47 +429,55 @@ def get_transform_list(
proprio_6d.append(PadGripperZeros(action_key="observations.state.ee_pose"))

if mode == "cartesian":
return _build_human_cartesian_bimanual_transform_list(stride=stride)
return prefix + _build_human_cartesian_bimanual_transform_list(
stride=stride
)
if mode == "cartesian_6d":
# Head/camera-frame cartesian (12D xyz+ypr per arm pair) with the
# rotation re-expressed as the continuous 6D representation (18D)
# for pi0.5. The proprio ee_pose is 6D-encoded too: normalized YPR
# saturates yaw/roll at +-pi, so per-dim normalization needs the
# continuous representation.
return (
_build_human_cartesian_bimanual_transform_list(stride=stride)
prefix
+ _build_human_cartesian_bimanual_transform_list(stride=stride)
+ [CartesianYPRToRot6D(action_key="actions_cartesian")]
+ proprio_6d
)
if mode == "cartesian_padded":
return _build_human_cartesian_bimanual_transform_list(stride=stride) + [
PadGripperZeros(action_key="actions_cartesian")
]
return (
prefix
+ _build_human_cartesian_bimanual_transform_list(stride=stride)
+ [PadGripperZeros(action_key="actions_cartesian")]
)
if mode == "cartesian_wristframe_ypr":
return _build_human_cartesian_eef_frame_transform_list(stride=stride)
return prefix + _build_human_cartesian_eef_frame_transform_list(
stride=stride
)
if mode == "cartesian_wristframe_6d":
# Wrist-frame cartesian with the 6D rotation (18D). The head-frame
# proprio ee_pose is 6D-encoded too (see cartesian_6d); it is the
# only head-frame signal the model sees with wrist-relative targets.
return (
_build_human_cartesian_eef_frame_transform_list(stride=stride)
prefix
+ _build_human_cartesian_eef_frame_transform_list(stride=stride)
+ [CartesianYPRToRot6D(action_key="actions_cartesian")]
+ proprio_6d
)
if mode == "keypoints_headframe_ypr":
return _build_human_keypoints_bimanual_transform_list(
return prefix + _build_human_keypoints_bimanual_transform_list(
stride=stride, is_quat=False
)
if mode == "keypoints_headframe_quat":
return _build_human_keypoints_bimanual_transform_list(
return prefix + _build_human_keypoints_bimanual_transform_list(
stride=stride, is_quat=True
)
if mode == "keypoints_wristframe_ypr":
return _build_human_keypoints_eef_frame_transform_list(
return prefix + _build_human_keypoints_eef_frame_transform_list(
stride=stride, is_quat=False
)
if mode == "keypoints_wristframe_quat":
return _build_human_keypoints_eef_frame_transform_list(
return prefix + _build_human_keypoints_eef_frame_transform_list(
stride=stride, is_quat=True
)
raise ValueError(f"Unsupported transform_list mode '{mode}' for {cls.__name__}")
Expand Down
44 changes: 44 additions & 0 deletions egomimic/rldb/zarr/action_chunk_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,50 @@ def transform(self, batch: dict) -> dict:
return batch


class RotateLocalFrame(Transform):
"""Right-multiply listed xyz+quat(wxyz) pose keys by a constant LOCAL
rotation: ``R_new = R_old @ R_fix``. Relabels the pose's own axes without
moving its origin. Handles ``(7,)`` poses and ``(T, 7)`` chunks.

Used to retroactively fix the mecka LEFT wrist-frame convention without
reconverting the zarrs: ``compute_hand_pose_xyzquat`` built the palm
normal as ``cross(thumb_dir, pinky_dir)``, which already mirrors chirality
between hands, and then ``rot_left`` flipped x/y again, double-mirroring
the left hand onto the right hand's spatial convention. Since
``rot_left == rot_right @ diag(-1, -1, 1)``, right-multiplying the stored
left pose by Rz(180 deg) (the default ``quat_wxyz``) is exactly equivalent
to reconverting with ``rot_right`` for both hands.
"""

def __init__(
self,
keys: list[str],
quat_wxyz: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
):
self.keys = list(keys)
self.quat_wxyz = tuple(float(v) for v in quat_wxyz)
w, x, y, z = self.quat_wxyz
self._fix = R.from_quat([x, y, z, w]) # scipy xyzw

def transform(self, batch: dict) -> dict:
for key in self.keys:
pose = np.asarray(batch[key])
if pose.shape[-1] != 7:
raise ValueError(
f"RotateLocalFrame expects xyz+quat(wxyz) with last dim 7, "
f"got {pose.shape} for '{key}'"
)
flat = pose.reshape(-1, 7).astype(np.float64, copy=True)
# Zero-norm quats mark padded/invalid frames; leave them alone.
valid = np.linalg.norm(flat[:, 3:7], axis=-1) > 1e-6
if valid.any():
q_xyzw = flat[valid][:, [4, 5, 6, 3]]
rotated = (R.from_quat(q_xyzw) * self._fix).as_quat() # xyzw
flat[np.flatnonzero(valid), 3:7] = rotated[:, [3, 0, 1, 2]]
batch[key] = flat.reshape(pose.shape)
return batch


class CartesianWithGripperCoordinateTransform(Transform):
def __init__(
self,
Expand Down
Loading
Loading