UWLab / scripts_v2 /tools /sim2real /collect_fk_pairs.py
Xinyi0214's picture
Upload UWLab (code, datasets, checkpoints, eval outputs)
0037d53 verified
Raw
History Blame Contribute Delete
5.64 kB
# Copyright (c) 2024-2026, The UW Lab Project Developers. (https://github.com/uw-lab/UWLab/blob/main/CONTRIBUTORS.md).
# All Rights Reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Collect (joint_pos, ee_pose) pairs from the Isaac Lab physics engine.
Uses IK-based workspace randomization (identical to training resets) to
sample diverse, reachable joint configurations. For each configuration the
physics-engine wrist_3_link pose (in the robot base frame) is recorded.
The companion script (diffusion_policy/test_fk_comparison.py) then runs
our calibrated analytical FK on the same joint angles and compares,
verifying that the sim and real FK agree to < 0.01 mm per dimension.
Usage:
python scripts_v2/tools/sim2real/collect_fk_pairs.py \\
--num_samples 50 --output fk_pairs.npz
# more samples via larger parallel batch:
python scripts_v2/tools/sim2real/collect_fk_pairs.py \\
--num_samples 200 --output fk_pairs.npz --settle_steps 100
"""
import argparse
import numpy as np
from isaaclab.app import AppLauncher
parser = argparse.ArgumentParser(description="Collect FK verification pairs from simulation.")
parser.add_argument(
"--num_samples", "-n", type=int, default=50, help="Number of IK-solved joint configurations to collect"
)
parser.add_argument("--output", "-o", type=str, default="fk_pairs.npz", help="Output npz file path")
parser.add_argument("--settle_steps", type=int, default=200, help="Physics steps to settle after each reset")
parser.add_argument(
"--num_resets", type=int, default=1, help="Number of env resets (total pairs = num_samples * num_resets)"
)
AppLauncher.add_app_launcher_args(parser)
args_cli = parser.parse_args()
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import gymnasium as gym # noqa: E402
import torch # noqa: E402
import isaaclab.utils.math as math_utils # noqa: E402
from isaaclab.managers import EventTermCfg as EventTerm # noqa: E402
from isaaclab.managers import SceneEntityCfg # noqa: E402
from isaaclab.utils import configclass # noqa: E402
import uwlab_tasks # noqa: F401, E402
from uwlab_tasks.manager_based.manipulation.omnireset import mdp as task_mdp # noqa: E402
from uwlab_tasks.manager_based.manipulation.omnireset.config.ur5e_robotiq_2f85.sysid_cfg import ( # noqa: E402
SysidEnvCfg,
)
@configclass
class FkPairsEventCfg:
"""Reset events: IK-based EE workspace randomization matching training."""
reset_everything = EventTerm(
func=task_mdp.reset_scene_to_default,
mode="reset",
params={},
)
reset_end_effector_pose = EventTerm(
func=task_mdp.reset_end_effector_round_fixed_asset,
mode="reset",
params={
"fixed_asset_cfg": SceneEntityCfg("robot"),
"fixed_asset_offset": None,
"pose_range_b": {
"x": (0.3, 0.7),
"y": (-0.4, 0.4),
"z": (0.0, 0.5),
"roll": (0.0, 0.0),
"pitch": (np.pi / 4, 3 * np.pi / 4),
"yaw": (np.pi / 2, 3 * np.pi / 2),
},
"robot_ik_cfg": SceneEntityCfg(
"robot",
joint_names=["shoulder.*", "elbow.*", "wrist.*"],
body_names="robotiq_base_link",
),
},
)
def main():
env_cfg = SysidEnvCfg()
env_cfg.scene.num_envs = args_cli.num_samples
env_cfg.events = FkPairsEventCfg()
env = gym.make("OmniReset-Ur5eRobotiq2f85-Sysid-v0", cfg=env_cfg)
device = env.unwrapped.device
robot = env.unwrapped.scene["robot"]
ee_idx = robot.body_names.index("wrist_3_link")
arm_dim = 6
gripper_dim = 1
zero_action = torch.zeros(args_cli.num_samples, arm_dim + gripper_dim, device=device)
all_joint_pos = []
all_ee_pos = []
all_ee_quat = []
all_ee_aa = []
for r in range(args_cli.num_resets):
obs, _ = env.reset()
for _ in range(args_cli.settle_steps):
obs, _, _, _, _ = env.step(zero_action)
# Read all envs at once
joint_pos = robot.data.joint_pos[:, :6].cpu().numpy() # (N, 6)
ee_pos_w = robot.data.body_link_pos_w[:, ee_idx] # (N, 3)
ee_quat_w = robot.data.body_link_quat_w[:, ee_idx] # (N, 4)
ee_pos_b, ee_quat_b = math_utils.subtract_frame_transforms(
robot.data.root_pos_w,
robot.data.root_quat_w,
ee_pos_w,
ee_quat_w,
)
ee_aa_b = math_utils.axis_angle_from_quat(ee_quat_b)
all_joint_pos.append(joint_pos)
all_ee_pos.append(ee_pos_b.cpu().numpy())
all_ee_quat.append(ee_quat_b.cpu().numpy())
all_ee_aa.append(ee_aa_b.cpu().numpy())
print(f" Reset {r+1}/{args_cli.num_resets}: collected {len(joint_pos)} pairs")
all_joint_pos = np.concatenate(all_joint_pos, axis=0)
all_ee_pos = np.concatenate(all_ee_pos, axis=0)
all_ee_quat = np.concatenate(all_ee_quat, axis=0)
all_ee_aa = np.concatenate(all_ee_aa, axis=0)
np.savez(
args_cli.output,
joint_pos=all_joint_pos,
ee_pos=all_ee_pos,
ee_quat=all_ee_quat,
ee_rot_aa=all_ee_aa,
)
n = len(all_joint_pos)
print(f"\nSaved {n} pairs to {args_cli.output}")
print(f" joint_pos : {all_joint_pos.shape}")
print(f" ee_pos : {all_ee_pos.shape} (meters, robot base frame)")
print(f" ee_quat : {all_ee_quat.shape} (w,x,y,z)")
print(f" ee_rot_aa : {all_ee_aa.shape} (axis-angle, radians)")
env.close()
if __name__ == "__main__":
main()
simulation_app.close()