| |
| |
| |
| |
|
|
| """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 |
| import torch |
|
|
| import isaaclab.utils.math as math_utils |
| from isaaclab.managers import EventTermCfg as EventTerm |
| from isaaclab.managers import SceneEntityCfg |
| from isaaclab.utils import configclass |
|
|
| import uwlab_tasks |
| from uwlab_tasks.manager_based.manipulation.omnireset import mdp as task_mdp |
| from uwlab_tasks.manager_based.manipulation.omnireset.config.ur5e_robotiq_2f85.sysid_cfg import ( |
| 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) |
|
|
| |
| joint_pos = robot.data.joint_pos[:, :6].cpu().numpy() |
| ee_pos_w = robot.data.body_link_pos_w[:, ee_idx] |
| ee_quat_w = robot.data.body_link_quat_w[:, ee_idx] |
| 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() |
|
|