diff --git a/egomimic/hydra_configs/data/aria.yaml b/egomimic/hydra_configs/data/aria.yaml index 47428fcd4..34fe9b800 100644 --- a/egomimic/hydra_configs/data/aria.yaml +++ b/egomimic/hydra_configs/data/aria.yaml @@ -11,7 +11,9 @@ train_datasets: keymap_mode: cartesian transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/aria_keypoints.yaml b/egomimic/hydra_configs/data/aria_keypoints.yaml index f291b0990..8be34b20f 100644 --- a/egomimic/hydra_configs/data/aria_keypoints.yaml +++ b/egomimic/hydra_configs/data/aria_keypoints.yaml @@ -11,7 +11,9 @@ train_datasets: keymap_mode: keypoints transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: keypoints_headframe_ypr + action_mode: keypoints + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/aria_keypoints_wrist.yaml b/egomimic/hydra_configs/data/aria_keypoints_wrist.yaml index d0076c101..9caab3c96 100644 --- a/egomimic/hydra_configs/data/aria_keypoints_wrist.yaml +++ b/egomimic/hydra_configs/data/aria_keypoints_wrist.yaml @@ -11,7 +11,9 @@ train_datasets: keymap_mode: keypoints transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: keypoints_wristframe_ypr + action_mode: keypoints + coord_frame: eef_frame + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/aria_pi.yaml b/egomimic/hydra_configs/data/aria_pi.yaml index ff9e2af07..b9944f7a8 100644 --- a/egomimic/hydra_configs/data/aria_pi.yaml +++ b/egomimic/hydra_configs/data/aria_pi.yaml @@ -10,6 +10,9 @@ train_datasets: _target_: egomimic.rldb.embodiment.human.Human.get_keymap transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/aria_qwen.yaml b/egomimic/hydra_configs/data/aria_qwen.yaml index 8815fcdff..1d4822662 100644 --- a/egomimic/hydra_configs/data/aria_qwen.yaml +++ b/egomimic/hydra_configs/data/aria_qwen.yaml @@ -16,7 +16,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: null mode: train diff --git a/egomimic/hydra_configs/data/bc_pickplace_eva_qwen.yaml b/egomimic/hydra_configs/data/bc_pickplace_eva_qwen.yaml index bfeba77eb..3401d05be 100644 --- a/egomimic/hydra_configs/data/bc_pickplace_eva_qwen.yaml +++ b/egomimic/hydra_configs/data/bc_pickplace_eva_qwen.yaml @@ -17,7 +17,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -37,7 +39,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language diff --git a/egomimic/hydra_configs/data/cotrain_pi_base.yaml b/egomimic/hydra_configs/data/cotrain_pi_base.yaml index 6d682e2fd..f14e1ff12 100644 --- a/egomimic/hydra_configs/data/cotrain_pi_base.yaml +++ b/egomimic/hydra_configs/data/cotrain_pi_base.yaml @@ -12,7 +12,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: null mode: train valid_ratio: 0.05 @@ -27,7 +29,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: null mode: train diff --git a/egomimic/hydra_configs/data/cotrain_pi_lang_wrist.yaml b/egomimic/hydra_configs/data/cotrain_pi_lang_wrist.yaml index 641e483fa..447de7eb3 100644 --- a/egomimic/hydra_configs/data/cotrain_pi_lang_wrist.yaml +++ b/egomimic/hydra_configs/data/cotrain_pi_lang_wrist.yaml @@ -8,10 +8,14 @@ train_datasets: key_map: keymap_mode: cartesian_wristframe_ypr transform_list: - mode: cartesian_wristframe_ypr + action_mode: cartesian + coord_frame: eef_frame + rotation_mode: euler human_bimanual: resolver: key_map: keymap_mode: cartesian transform_list: - mode: cartesian_wristframe_ypr + action_mode: cartesian + coord_frame: eef_frame + rotation_mode: euler diff --git a/egomimic/hydra_configs/data/cotrain_pi_pickplace_qwen.yaml b/egomimic/hydra_configs/data/cotrain_pi_pickplace_qwen.yaml index 0dcccb548..f8297c29d 100644 --- a/egomimic/hydra_configs/data/cotrain_pi_pickplace_qwen.yaml +++ b/egomimic/hydra_configs/data/cotrain_pi_pickplace_qwen.yaml @@ -16,7 +16,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -34,7 +36,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter @@ -55,7 +59,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language diff --git a/egomimic/hydra_configs/data/cotrain_pickplace_qwen.yaml b/egomimic/hydra_configs/data/cotrain_pickplace_qwen.yaml index c05902d4e..ebfd6ba82 100644 --- a/egomimic/hydra_configs/data/cotrain_pickplace_qwen.yaml +++ b/egomimic/hydra_configs/data/cotrain_pickplace_qwen.yaml @@ -25,7 +25,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -43,7 +45,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter @@ -64,7 +68,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language diff --git a/egomimic/hydra_configs/data/cotrain_pickplace_qwen_objgen.yaml b/egomimic/hydra_configs/data/cotrain_pickplace_qwen_objgen.yaml index abb5958ea..8d860e5ad 100644 --- a/egomimic/hydra_configs/data/cotrain_pickplace_qwen_objgen.yaml +++ b/egomimic/hydra_configs/data/cotrain_pickplace_qwen_objgen.yaml @@ -20,7 +20,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -38,7 +40,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian_padded + action_mode: cartesian_gripper_padded + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter @@ -59,7 +63,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -77,7 +83,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian_padded + action_mode: cartesian_gripper_padded + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter diff --git a/egomimic/hydra_configs/data/cotrain_pickplace_qwen_wrist.yaml b/egomimic/hydra_configs/data/cotrain_pickplace_qwen_wrist.yaml index 9ef6424d5..489e7dfe8 100644 --- a/egomimic/hydra_configs/data/cotrain_pickplace_qwen_wrist.yaml +++ b/egomimic/hydra_configs/data/cotrain_pickplace_qwen_wrist.yaml @@ -1,5 +1,5 @@ # Wristframe variant of cotrain_pickplace_qwen.yaml. -# Transform list uses cartesian_wristframe_ypr — actions are in each EEF wrist +# Transform list uses cartesian + eef_frame + euler — actions are in each EEF # frame instead of head/base frame. Action chunk key + dim unchanged # (actions_cartesian, 14D for eva). Pair with viz=cartesian_wrist_lang. @@ -17,7 +17,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian_wristframe_ypr + action_mode: cartesian + coord_frame: eef_frame + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language @@ -35,7 +37,9 @@ train_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian_wristframe_ypr + action_mode: cartesian + coord_frame: eef_frame + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter @@ -56,7 +60,9 @@ valid_datasets: annotation_key: annotations transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian_wristframe_ypr + action_mode: cartesian + coord_frame: eef_frame + rotation_mode: euler filters: _target_: egomimic.rldb.filters.ScaleAnnotationDatasetFilter project_name: dense-language diff --git a/egomimic/hydra_configs/data/eva.yaml b/egomimic/hydra_configs/data/eva.yaml index dfe59a883..65dd9060a 100644 --- a/egomimic/hydra_configs/data/eva.yaml +++ b/egomimic/hydra_configs/data/eva.yaml @@ -11,7 +11,9 @@ train_datasets: keymap_mode: cartesian transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.DatasetFilter filter_lambdas: diff --git a/egomimic/hydra_configs/data/human.yaml b/egomimic/hydra_configs/data/human.yaml index 13cab563e..956bbf517 100644 --- a/egomimic/hydra_configs/data/human.yaml +++ b/egomimic/hydra_configs/data/human.yaml @@ -17,7 +17,9 @@ train_datasets: keymap_mode: cartesian transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 3 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/industry_eva_pi.yaml b/egomimic/hydra_configs/data/industry_eva_pi.yaml index 5b3728ce3..c7b07a235 100644 --- a/egomimic/hydra_configs/data/industry_eva_pi.yaml +++ b/egomimic/hydra_configs/data/industry_eva_pi.yaml @@ -15,6 +15,9 @@ train_datasets: _target_: egomimic.rldb.embodiment.eva.Eva.get_keymap transform_list: _target_: egomimic.rldb.embodiment.eva.Eva.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler filters: _target_: egomimic.rldb.filters.DatasetFilter filter_lambdas: @@ -30,6 +33,9 @@ train_datasets: has_head_pose: false transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/mecka.yaml b/egomimic/hydra_configs/data/mecka.yaml index 12a22ad45..ed911094a 100644 --- a/egomimic/hydra_configs/data/mecka.yaml +++ b/egomimic/hydra_configs/data/mecka.yaml @@ -11,7 +11,9 @@ train_datasets: keymap_mode: cartesian transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/mecka_pi.yaml b/egomimic/hydra_configs/data/mecka_pi.yaml index ee6d183b6..3b3f9d1b8 100644 --- a/egomimic/hydra_configs/data/mecka_pi.yaml +++ b/egomimic/hydra_configs/data/mecka_pi.yaml @@ -10,6 +10,9 @@ train_datasets: _target_: egomimic.rldb.embodiment.human.Human.get_keymap transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: lab: "mecka" diff --git a/egomimic/hydra_configs/data/mecka_scale_cotrain_pi.yaml b/egomimic/hydra_configs/data/mecka_scale_cotrain_pi.yaml index fac02bc39..af00732ff 100644 --- a/egomimic/hydra_configs/data/mecka_scale_cotrain_pi.yaml +++ b/egomimic/hydra_configs/data/mecka_scale_cotrain_pi.yaml @@ -16,6 +16,9 @@ train_datasets: has_head_pose: false transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/scale.yaml b/egomimic/hydra_configs/data/scale.yaml index 63748b8e6..feb39e158 100644 --- a/egomimic/hydra_configs/data/scale.yaml +++ b/egomimic/hydra_configs/data/scale.yaml @@ -12,7 +12,9 @@ train_datasets: has_head_pose: false transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list - mode: cartesian + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: _target_: egomimic.rldb.filters.DatasetFilter diff --git a/egomimic/hydra_configs/data/scale_pi.yaml b/egomimic/hydra_configs/data/scale_pi.yaml index d2c5f8417..0bfeb57ea 100644 --- a/egomimic/hydra_configs/data/scale_pi.yaml +++ b/egomimic/hydra_configs/data/scale_pi.yaml @@ -11,6 +11,9 @@ train_datasets: has_head_pose: false transform_list: _target_: egomimic.rldb.embodiment.human.Human.get_transform_list + action_mode: cartesian + coord_frame: camframe + rotation_mode: euler stride: 1 filters: filter_lambdas: diff --git a/egomimic/rldb/embodiment/eva.py b/egomimic/rldb/embodiment/eva.py index 4c9cdbb87..128e5c3e5 100644 --- a/egomimic/rldb/embodiment/eva.py +++ b/egomimic/rldb/embodiment/eva.py @@ -8,17 +8,15 @@ from egomimic.rldb.embodiment.human import ARIA_INTRINSICS from egomimic.rldb.zarr.action_chunk_transforms import ( ActionChunkCoordinateFrameTransform, - BatchQuaternionPoseToYPR, ConcatKeys, DeleteKeys, InterpolateLinear, InterpolatePose, NumpyToTensor, PoseCoordinateFrameTransform, - QuaternionPoseToYPR, SplitKeys, Transform, - XYZWXYZ_to_XYZYPR, + transforms_for_rotation_mode, ) from egomimic.utils.pose_utils import ( _matrix_to_xyzwxyz, @@ -48,16 +46,34 @@ class Eva(Embodiment): @staticmethod def get_transform_list( - mode: Literal[ - "cartesian", "cartesian_wristframe_ypr", "cartesian_wristframe_quat" - ], + action_mode: Literal[ + "cartesian", + ] = "cartesian", + coord_frame: Literal[ + "camframe", + "eef_frame", + ] = "camframe", + rotation_mode: Literal[ + "euler", + "quat", + "6D", + ] = "euler", ) -> list[Transform]: - if mode == "cartesian": - return _build_eva_bimanual_transform_list(is_quat=True) - elif mode == "cartesian_wristframe_ypr": - return _build_eva_bimanual_eef_frame_transform_list(is_quat=False) - elif mode == "cartesian_wristframe_quat": - return _build_eva_bimanual_eef_frame_transform_list(is_quat=True) + """``action_mode`` is the action layout; ``coord_frame`` is where poses + live; ``rotation_mode`` is how rotation is stored. + + Cam-frame actions are expressed in the wrist cameras via :attr:`EXTRINSICS`. + EEF-frame actions are a delta from the current EEF pose. In both cases + the geometric hops run in xyz+quat, then ``rotation_mode`` converts rotation + to euler (xyz+ypr, 14D), quat (16D), or Zhou 6D (20D). + """ + if action_mode != "cartesian": + raise ValueError(f"unknown action_mode {action_mode!r}") + if coord_frame == "camframe": + return _build_eva_bimanual_transform_list(rotation_mode=rotation_mode) + if coord_frame == "eef_frame": + return _build_eva_bimanual_eef_frame_transform_list(rotation_mode=rotation_mode) + raise ValueError(f"unknown coord_frame {coord_frame!r}") @classmethod def _get_keymap(cls, keymap_mode: str): @@ -244,7 +260,7 @@ def _build_eva_bimanual_eef_frame_transform_list( obs_key: str = "observations.state.ee_pose", chunk_length: int = 100, stride: int = 1, - is_quat: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: """EVA bimanual transform pipeline with actions expressed relative to the current EEF pose (wrist frame), analogous to keypoints relative to wrist pose.""" @@ -323,27 +339,17 @@ def _build_eva_bimanual_eef_frame_transform_list( ), ] - if not is_quat: - transform_list.extend( - [ - BatchQuaternionPoseToYPR( - pose_key=left_cmd_wristframe, - output_key=left_cmd_wristframe, - ), - BatchQuaternionPoseToYPR( - pose_key=right_cmd_wristframe, - output_key=right_cmd_wristframe, - ), - QuaternionPoseToYPR( - pose_key=left_obs_camframe, - output_key=left_obs_camframe, - ), - QuaternionPoseToYPR( - pose_key=right_obs_camframe, - output_key=right_obs_camframe, - ), - ] + transform_list.extend( + transforms_for_rotation_mode( + keys=[ + left_cmd_wristframe, + right_cmd_wristframe, + left_obs_camframe, + right_obs_camframe, + ], + rotation_mode=rotation_mode, ) + ) transform_list.extend( [ @@ -408,7 +414,7 @@ def _build_eva_bimanual_transform_list( obs_key: str = "observations.state.ee_pose", chunk_length: int = 100, stride: int = 1, - is_quat: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: """Canonical EVA bimanual transform pipeline used by tests and notebooks.""" extrinsics = Eva.EXTRINSICS @@ -417,47 +423,46 @@ def _build_eva_bimanual_transform_list( left_extra_batch_key = {"left_extrinsics_pose": left_extrinsics_pose} right_extra_batch_key = {"right_extrinsics_pose": right_extrinsics_pose} - mode = "xyzwxyz" if is_quat else "xyzypr" transform_list = [ ActionChunkCoordinateFrameTransform( target_world=left_target_world, chunk_world=left_cmd_world, transformed_key_name=left_cmd_camframe, extra_batch_key=left_extra_batch_key, - mode=mode, + mode="xyzwxyz", ), ActionChunkCoordinateFrameTransform( target_world=right_target_world, chunk_world=right_cmd_world, transformed_key_name=right_cmd_camframe, extra_batch_key=right_extra_batch_key, - mode=mode, + mode="xyzwxyz", ), PoseCoordinateFrameTransform( target_world=left_target_world, pose_world=left_obs_pose, transformed_key_name=left_obs_pose, - mode=mode, + mode="xyzwxyz", ), PoseCoordinateFrameTransform( target_world=right_target_world, pose_world=right_obs_pose, transformed_key_name=right_obs_pose, - mode=mode, + mode="xyzwxyz", ), InterpolatePose( new_chunk_length=chunk_length, action_key=left_cmd_camframe, output_action_key=left_cmd_camframe, stride=stride, - mode=mode, + mode="xyzwxyz", ), InterpolatePose( new_chunk_length=chunk_length, action_key=right_cmd_camframe, output_action_key=right_cmd_camframe, stride=stride, - mode=mode, + mode="xyzwxyz", ), InterpolateLinear( new_chunk_length=chunk_length, @@ -473,17 +478,17 @@ def _build_eva_bimanual_transform_list( ), ] - if is_quat: - transform_list.append( - XYZWXYZ_to_XYZYPR( - keys=[ - left_cmd_camframe, - right_cmd_camframe, - left_obs_pose, - right_obs_pose, - ] - ) + transform_list.extend( + transforms_for_rotation_mode( + keys=[ + left_cmd_camframe, + right_cmd_camframe, + left_obs_pose, + right_obs_pose, + ], + rotation_mode=rotation_mode, ) + ) transform_list.extend( [ diff --git a/egomimic/rldb/embodiment/human.py b/egomimic/rldb/embodiment/human.py index 673ccc5e0..d817162b9 100644 --- a/egomimic/rldb/embodiment/human.py +++ b/egomimic/rldb/embodiment/human.py @@ -8,17 +8,15 @@ from egomimic.rldb.embodiment.embodiment import Embodiment from egomimic.rldb.zarr.action_chunk_transforms import ( ActionChunkCoordinateFrameTransform, - BatchQuaternionPoseToYPR, ConcatKeys, DeleteKeys, InterpolatePose, PadGripperZeros, PoseCoordinateFrameTransform, - QuaternionPoseToYPR, Reshape, SplitKeys, Transform, - XYZWXYZ_to_XYZYPR, + transforms_for_rotation_mode, ) from egomimic.utils.viz_utils import ( ColorPalette, @@ -98,7 +96,7 @@ class Human(Embodiment): by the data config, because get_keymap / get_transform_list resolve at hydra config time (before any episode is read): - get_keymap(keymap_mode, has_head_pose=True, include_aria_keypoints=False) - - get_transform_list(mode, stride=3) + - get_transform_list(action_mode, coord_frame, rotation_mode, stride=3) Per-episode camera intrinsics travel in ``batch["intrinsics"]`` (from zarr.json); ``cls.INTRINSICS`` is only a fallback for legacy episodes that lack them. The canonical keypoints are MANO for every vendor. @@ -325,45 +323,74 @@ def _get_keymap( @classmethod def get_transform_list( cls, - mode: Literal[ + action_mode: Literal[ "cartesian", - "cartesian_padded", - "cartesian_wristframe_ypr", - "keypoints_headframe_ypr", - "keypoints_headframe_quat", - "keypoints_wristframe_ypr", - "keypoints_wristframe_quat", - ], + "cartesian_gripper_padded", + "keypoints", + ] = "cartesian", + coord_frame: Literal[ + "camframe", + "eef_frame", + ] = "camframe", + rotation_mode: Literal[ + "euler", + "quat", + "6D", + ] = "euler", stride: int = 3, ) -> list[Transform]: - """Transform pipeline. ``stride`` is the per-vendor action stride - (Aria/LightWheel=3, Scale/Mecka=1), supplied by the data config. + """``action_mode`` is the action layout; ``coord_frame`` is where poses + live; ``rotation_mode`` is how rotation is stored. + + ``stride`` is the per-vendor action stride (Aria/LightWheel=3, + Scale/Mecka=1), supplied by the data config. + + Human cartesian has no gripper. ``cartesian_gripper_padded`` inserts a + zero gripper per arm so the layout matches Eva/Yam (14D euler, 16D quat, + 20D Zhou 6D). """ - if mode == "cartesian": - return _build_human_cartesian_bimanual_transform_list(stride=stride) - if mode == "cartesian_padded": - return _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) - if mode == "keypoints_headframe_ypr": - return _build_human_keypoints_bimanual_transform_list( - stride=stride, is_quat=False - ) - if mode == "keypoints_headframe_quat": - return _build_human_keypoints_bimanual_transform_list( - stride=stride, is_quat=True + if action_mode in ("cartesian", "cartesian_gripper_padded"): + builders = { + "camframe": _build_human_cartesian_bimanual_transform_list, + "eef_frame": _build_human_cartesian_eef_frame_transform_list, + } + elif action_mode == "keypoints": + builders = { + "camframe": _build_human_keypoints_bimanual_transform_list, + "eef_frame": _build_human_keypoints_eef_frame_transform_list, + } + else: + raise ValueError( + f"Unsupported action_mode '{action_mode}' for {cls.__name__}" ) - if mode == "keypoints_wristframe_ypr": - return _build_human_keypoints_eef_frame_transform_list( - stride=stride, is_quat=False + if coord_frame not in builders: + raise ValueError( + f"Unsupported coord_frame '{coord_frame}' for {cls.__name__} " + f"action_mode '{action_mode}'" ) - if mode == "keypoints_wristframe_quat": - return _build_human_keypoints_eef_frame_transform_list( - stride=stride, is_quat=True + transform_list = builders[coord_frame]( + stride=stride, rotation_mode=rotation_mode + ) + if action_mode == "cartesian_gripper_padded": + return _pad_human_cartesian_gripper( + transform_list, rotation_mode=rotation_mode ) - raise ValueError(f"Unsupported transform_list mode '{mode}' for {cls.__name__}") + return transform_list + + +def _pad_human_cartesian_gripper( + transform_list: list[Transform], + *, + rotation_mode: Literal["euler", "quat", "6D"], + actions_key: str = "actions_cartesian", + obs_key: str = "observations.state.ee_pose", +) -> list[Transform]: + """Append zero-gripper slots so human cartesian matches Eva/Yam layout.""" + pose_dim = {"euler": 6, "quat": 7, "6D": 9}[rotation_mode] + return transform_list + [ + PadGripperZeros(action_key=actions_key, pose_dim=pose_dim), + PadGripperZeros(action_key=obs_key, pose_dim=pose_dim), + ] # this works for quat and ypr since actionChunkCoordinateFrameTransform works for both @@ -484,7 +511,7 @@ def _build_human_keypoints_eef_frame_transform_list( delete_target_world: bool = True, chunk_length: int = 100, stride: int = 3, - is_quat: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: transform_list = _build_human_keypoints_bimanual_transform_list( target_world=target_world, @@ -494,7 +521,7 @@ def _build_human_keypoints_eef_frame_transform_list( chunk_length=chunk_length, stride=stride, concat_keys=False, - is_quat=True, + rotation_mode="quat", ) delete_keys = [ left_keypoints_action_world, @@ -596,27 +623,17 @@ def _build_human_keypoints_eef_frame_transform_list( ), ] ) - if not is_quat: - transform_list.extend( - [ - BatchQuaternionPoseToYPR( - pose_key=left_wrist_action_wristframe, - output_key=left_wrist_action_wristframe, - ), - BatchQuaternionPoseToYPR( - pose_key=right_wrist_action_wristframe, - output_key=right_wrist_action_wristframe, - ), - QuaternionPoseToYPR( - pose_key=left_wrist_obs_headframe, - output_key=left_wrist_obs_headframe, - ), - QuaternionPoseToYPR( - pose_key=right_wrist_obs_headframe, - output_key=right_wrist_obs_headframe, - ), - ] + transform_list.extend( + transforms_for_rotation_mode( + keys=[ + left_wrist_action_wristframe, + right_wrist_action_wristframe, + left_wrist_obs_headframe, + right_wrist_obs_headframe, + ], + rotation_mode=rotation_mode, ) + ) transform_list.extend( [ ConcatKeys( @@ -670,7 +687,7 @@ def _build_human_keypoints_bimanual_transform_list( chunk_length: int = 100, stride: int = 3, concat_keys: bool = True, - is_quat: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: keys_to_delete = list( { @@ -814,28 +831,18 @@ def _build_human_keypoints_bimanual_transform_list( mode="xyzwxyz", ), ] - if not is_quat: + if concat_keys: transform_list.extend( - [ - BatchQuaternionPoseToYPR( - pose_key=left_wrist_action_headframe, - output_key=left_wrist_action_headframe, - ), - BatchQuaternionPoseToYPR( - pose_key=right_wrist_action_headframe, - output_key=right_wrist_action_headframe, - ), - QuaternionPoseToYPR( - pose_key=left_wrist_obs_headframe, - output_key=left_wrist_obs_headframe, - ), - QuaternionPoseToYPR( - pose_key=right_wrist_obs_headframe, - output_key=right_wrist_obs_headframe, - ), - ] + transforms_for_rotation_mode( + keys=[ + left_wrist_action_headframe, + right_wrist_action_headframe, + left_wrist_obs_headframe, + right_wrist_obs_headframe, + ], + rotation_mode=rotation_mode, + ) ) - if concat_keys: transform_list.extend( [ ConcatKeys( @@ -943,14 +950,15 @@ def _build_human_cartesian_eef_frame_transform_list( chunk_length: int = 100, stride: int = 3, delete_target_world: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: """ARIA bimanual cartesian pipeline expressed in the current wrist frame. Action ee-pose chunks are first transformed world → headframe (via ``obs_head_pose``), then headframe → wristframe (via the proprio ``*.obs_ee_pose_headframe`` for each side). Proprio ee-poses remain in - headframe (wristframe of the wrist itself is identity). All retained poses - are converted to xyz-ypr. + headframe (wristframe of the wrist itself is identity). Rotation is + converted per ``rotation_mode``. """ keys_to_delete = list( { @@ -1018,26 +1026,33 @@ def _build_human_cartesian_eef_frame_transform_list( transformed_key_name=right_action_wristframe, mode="xyzwxyz", ), - XYZWXYZ_to_XYZYPR( + ] + transform_list.extend( + transforms_for_rotation_mode( keys=[ left_action_wristframe, right_action_wristframe, left_obs_headframe, right_obs_headframe, - ] - ), - ConcatKeys( - key_list=[left_action_wristframe, right_action_wristframe], - new_key_name=actions_key, - delete_old_keys=True, - ), - ConcatKeys( - key_list=[left_obs_headframe, right_obs_headframe], - new_key_name=obs_key, - delete_old_keys=True, - ), - DeleteKeys(keys_to_delete=keys_to_delete), - ] + ], + rotation_mode=rotation_mode, + ) + ) + transform_list.extend( + [ + ConcatKeys( + key_list=[left_action_wristframe, right_action_wristframe], + new_key_name=actions_key, + delete_old_keys=True, + ), + ConcatKeys( + key_list=[left_obs_headframe, right_obs_headframe], + new_key_name=obs_key, + delete_old_keys=True, + ), + DeleteKeys(keys_to_delete=keys_to_delete), + ] + ) return transform_list @@ -1059,6 +1074,7 @@ def _build_human_cartesian_bimanual_transform_list( chunk_length: int = 100, stride: int = 3, delete_target_world: bool = True, + rotation_mode: Literal["euler", "quat", "6D"] = "euler", ) -> list[Transform]: """Canonical ARIA bimanual transform pipeline used by tests and notebooks. @@ -1121,17 +1137,17 @@ def _build_human_cartesian_bimanual_transform_list( ), ] - if target_world_is_quat: - transform_list.append( - XYZWXYZ_to_XYZYPR( - keys=[ - left_action_headframe, - right_action_headframe, - left_obs_headframe, - right_obs_headframe, - ] - ) + transform_list.extend( + transforms_for_rotation_mode( + keys=[ + left_action_headframe, + right_action_headframe, + left_obs_headframe, + right_obs_headframe, + ], + rotation_mode=rotation_mode, ) + ) transform_list.extend( [ diff --git a/egomimic/rldb/zarr/action_chunk_transforms.py b/egomimic/rldb/zarr/action_chunk_transforms.py index 0388d386a..0e2015a1b 100644 --- a/egomimic/rldb/zarr/action_chunk_transforms.py +++ b/egomimic/rldb/zarr/action_chunk_transforms.py @@ -26,6 +26,7 @@ _interpolate_quat_wxyz, _interpolate_xyz, _matrix_to_xyz, + _matrix_to_xyzrot6d, _matrix_to_xyzwxyz, _matrix_to_xyzypr, _xyz_to_matrix, @@ -366,6 +367,27 @@ def transform(self, batch): return batch +class XYZWXYZ_to_XYZRot6D(Transform): + """Convert listed keys from xyz+quat(wxyz) to xyz+rot6d in-place.""" + + def __init__(self, keys: list[str]): + self.keys = list(keys) + + def transform(self, batch: dict) -> dict: + for key in self.keys: + value = np.asarray(batch[key]) + if value.ndim == 1 and value.shape[0] == 7: + batch[key] = _matrix_to_xyzrot6d(_xyzwxyz_to_matrix(value[None, :]))[0] + elif value.ndim == 2 and value.shape[1] == 7: + batch[key] = _matrix_to_xyzrot6d(_xyzwxyz_to_matrix(value)) + else: + raise ValueError( + f"XYZWXYZ_to_XYZRot6D expects key '{key}' to have shape (7,) " + f"or (T, 7), got {value.shape}" + ) + return batch + + class XYZWXYZ_to_XYZYPR(Transform): """Convert listed keys from xyz+quat(wxyz) to xyz+ypr in-place.""" @@ -387,6 +409,25 @@ def transform(self, batch: dict) -> dict: return batch +def transforms_for_rotation_mode( + keys: list[str], + rotation_mode: Literal["euler", "quat", "6D"], +) -> list[Transform]: + """Convert xyz+quat(wxyz) poses to the requested rotation representation. + + Geometric frame hops always run in quaternion form. This is the last step + that turns those 7D poses into what the policy sees: ``euler`` -> xyz+ypr + (6), ``quat`` -> leave 7D, ``6D`` -> Zhou 6D (xyz + first two columns of R). + """ + if rotation_mode == "quat": + return [] + if rotation_mode == "euler": + return [XYZWXYZ_to_XYZYPR(keys=keys)] + if rotation_mode == "6D": + return [XYZWXYZ_to_XYZRot6D(keys=keys)] + raise ValueError(f"unknown rotation_mode {rotation_mode!r}") + + class CartesianWithGripperCoordinateTransform(Transform): def __init__( self, @@ -513,30 +554,31 @@ def transform(self, batch): class PadGripperZeros(Transform): - """Pad a 12D bimanual cartesian action chunk to 14D by inserting a zero - gripper slot at position 6 (end of left arm) and position 13 (end of right - arm), matching the canonical [L xyz ypr g, R xyz ypr g] layout used by Eva. + """Insert a zero gripper slot after each arm's pose. - Used so aria (which has no gripper signal) can share an FM denoiser head - sized for 14D actions without needing in-model padding branches. + Default ``pose_dim=6`` (xyz+ypr) pads 12D -> 14D. ``pose_dim=9`` + (xyz+Zhou 6D) pads 18D -> 20D so human can share a 20D head with Eva/Yam. """ - def __init__(self, action_key: str = "actions_cartesian"): + def __init__(self, action_key: str = "actions_cartesian", pose_dim: int = 6): self.action_key = action_key + self.pose_dim = int(pose_dim) def transform(self, batch: dict) -> dict: actions = batch[self.action_key] is_tensor = isinstance(actions, torch.Tensor) arr = actions.cpu().numpy() if is_tensor else np.asarray(actions) - if arr.shape[-1] != 12: + expected = 2 * self.pose_dim + if arr.shape[-1] != expected: raise ValueError( - f"PadGripperZeros expects last-dim 12, got {arr.shape} for " - f"'{self.action_key}'" + f"PadGripperZeros expects last-dim {expected} (2 x pose_dim=" + f"{self.pose_dim}), got {arr.shape} for '{self.action_key}'" ) pad_shape = (*arr.shape[:-1], 1) pad = np.zeros(pad_shape, dtype=arr.dtype) + half = self.pose_dim padded = np.concatenate( - (arr[..., :6], pad, arr[..., 6:], pad), axis=-1 + (arr[..., :half], pad, arr[..., half:], pad), axis=-1 ) batch[self.action_key] = ( torch.from_numpy(padded) if is_tensor else padded diff --git a/egomimic/robot/rollout.py b/egomimic/robot/rollout.py index a9d93e485..1001dd3ef 100644 --- a/egomimic/robot/rollout.py +++ b/egomimic/robot/rollout.py @@ -254,7 +254,11 @@ def __init__( self.debug_actions = None self.resampled_action_len = resampled_action_len self.debug = debug - self.transform_list = Eva.get_transform_list(mode="cartesian_wristframe_ypr") + self.transform_list = Eva.get_transform_list( + action_mode="cartesian", + coord_frame="eef_frame", + rotation_mode="euler", + ) self.annotation = None self._tokenizer = None self.collate_fn = default_collate diff --git a/egomimic/scripts/tutorials/aria_to_mano_convert.py b/egomimic/scripts/tutorials/aria_to_mano_convert.py index 0ddfb9833..e96901110 100644 --- a/egomimic/scripts/tutorials/aria_to_mano_convert.py +++ b/egomimic/scripts/tutorials/aria_to_mano_convert.py @@ -3,7 +3,7 @@ Pipeline: 1. Pull first aria episode via SQL. 2. Build MultiDataset with Aria.get_keymap("keypoints") + transform_list - "keypoints_headframe_ypr" -> "actions_keypoints" (left wrist+kp, right wrist+kp). + keypoints + camframe + euler -> "actions_keypoints" (left wrist+kp, right wrist+kp). 3. For the first N frames, stack aria keypoints into a batched tensor and fit MANO_RIGHT / MANO_LEFT to them in parallel (batched Adam). 4. Permute MANO's otaheri joint order (wrist, index*3, middle*3, pinky*3, @@ -78,7 +78,12 @@ def fetch_episode_loader(): print(f"Aria episode: {episode_hash} (of {len(aria_df)})") key_map = Human.get_keymap(keymap_mode="keypoints") - transform_list = Human.get_transform_list(mode="keypoints_headframe_ypr", stride=3) + transform_list = Human.get_transform_list( + action_mode="keypoints", + coord_frame="camframe", + rotation_mode="euler", + stride=3, + ) resolver = S3EpisodeResolver( str(CACHE_DIR), key_map=key_map, transform_list=transform_list ) @@ -94,7 +99,7 @@ def fetch_episode_loader(): def gather_aria_keypoints(loader, n_frames): """Collect first n_frames worth of (image, left_kp, right_kp, batch) tuples. - Aria's batched actions_keypoints layout (after keypoints_headframe_ypr): + Aria's batched actions_keypoints layout (after keypoints + camframe + euler): [left_wrist_xyz(3), left_wrist_ypr(3), left_kp(63), right_wrist_xyz(3), right_wrist_ypr(3), right_kp(63)] = 138 per timestep, shape (T_chunk, 138). We take chunk[0] (current timestep). diff --git a/egomimic/scripts/tutorials/mecka_keypoints_viz.py b/egomimic/scripts/tutorials/mecka_keypoints_viz.py index 65786380c..e62a07c52 100644 --- a/egomimic/scripts/tutorials/mecka_keypoints_viz.py +++ b/egomimic/scripts/tutorials/mecka_keypoints_viz.py @@ -34,7 +34,12 @@ def main() -> None: print(f"Using mecka episode: {episode_hash} (of {len(mecka_df)} candidates)") key_map = Human.get_keymap(keymap_mode="keypoints") - transform_list = Human.get_transform_list(mode="keypoints_headframe_ypr", stride=1) + transform_list = Human.get_transform_list( + action_mode="keypoints", + coord_frame="camframe", + rotation_mode="euler", + stride=1, + ) resolver = S3EpisodeResolver( str(CACHE_DIR), key_map=key_map, transform_list=transform_list diff --git a/egomimic/scripts/tutorials/zarr_data_viz.ipynb b/egomimic/scripts/tutorials/zarr_data_viz.ipynb index a9eb11bc0..a7bc559f0 100644 --- a/egomimic/scripts/tutorials/zarr_data_viz.ipynb +++ b/egomimic/scripts/tutorials/zarr_data_viz.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "7cd73ea6", "metadata": {}, "outputs": [], @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "32d9110f", "metadata": {}, "outputs": [], @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "cc9edba1", "metadata": {}, "outputs": [], @@ -66,7 +66,7 @@ "outputs": [], "source": [ "key_map = Eva.get_keymap(keymap_mode=\"cartesian\")\n", - "transform_list = Eva.get_transform_list(mode=\"cartesian\")\n", + "transform_list = Eva.get_transform_list(action_mode=\"cartesian\", coord_frame=\"camframe\", rotation_mode=\"euler\")\n", "\n", "resolver = S3EpisodeResolver(\n", " TEMP_DIR, key_map=key_map, transform_list=transform_list\n", @@ -120,7 +120,7 @@ "metadata": {}, "source": [ "## Human Datasets\n", - "All human data uses the single `Human` embodiment. Per-source differences (head pose, action stride) are passed as args: `Human.get_keymap(keymap_mode=..., has_head_pose=...)`, `Human.get_transform_list(mode=..., stride=...)`." + "All human data uses the single `Human` embodiment. Per-source differences (head pose, action stride) are passed as args: `Human.get_keymap(keymap_mode=..., has_head_pose=...)`, `Human.get_transform_list(action_mode=..., coord_frame=..., rotation_mode=..., stride=...)`." ] }, { @@ -133,7 +133,7 @@ "from egomimic.rldb.zarr.zarr_dataset_multi import LocalEpisodeResolver\n", "\n", "key_map = Human.get_keymap(keymap_mode=\"cartesian\")\n", - "transform_list = Human.get_transform_list(mode=\"cartesian\", stride=3)\n", + "transform_list = Human.get_transform_list(action_mode=\"cartesian\", coord_frame=\"camframe\", rotation_mode=\"euler\", stride=3)\n", "\n", "TEMP_DIR = '/coc/flash7/scratch/egoverseDebugDatasets/egoverseS3DatasetTest/'\n", "resolver = LocalEpisodeResolver(\n", @@ -157,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "3cea6d5a", "metadata": {}, "outputs": [], @@ -167,53 +167,20 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "c77c89d6", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['observations.images.front_img_1', 'actions_cartesian', 'observations.state.ee_pose', 'embodiment', 'intrinsics', 'episode_hash'])" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "batch.keys()" ] }, - { - "cell_type": "markdown", - "id": "08fb2784", - "metadata": {}, - "source": [] - }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "af65095a", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "