mp_yam_code / scripts /yam_orient_probe.py
yqi19's picture
YAM bimanual task suite: env, solvers, tasks, converters
7399b6f verified
Raw
History Blame Contribute Delete
4.91 kB
"""Find the rotation that makes a RoboTwin asset sit the right way up.
RoboTwin GLBs are not authored Z-up, so a cup spawns upside-down and a basket spawns on its
side. Rather than guess one rotation per asset, this drops the object at several candidate
orientations, lets each settle, and RENDERS a labelled contact-sheet so the correct one can be
picked by eye -- plus the settled bbox height, which is the objective tell (an upright cup is
taller than it is wide; a tipped one is not).
ROBOTWIN_USD=... YAM_RW_OBJECTS="rw_cupA=021_cup/base0.usd:0.075:0.08" \
python scripts/yam_orient_probe.py --headless --obj rw_cupA --out outputs/probe_cup.png
"""
import argparse, sys, os
from isaaclab.app import AppLauncher
p = argparse.ArgumentParser()
p.add_argument("--obj", required=True)
p.add_argument("--xy", default="0.00,0.10")
p.add_argument("--rpys", default="0,0,0;180,0,0;90,0,0;-90,0,0;0,90,0;0,180,0",
help="';'-separated roll,pitch,yaw candidates in degrees")
p.add_argument("--out", default="outputs/orient_probe.png")
AppLauncher.add_app_launcher_args(p)
args = p.parse_args(); args.headless = True; args.enable_cameras = True
app = AppLauncher(args).app
import numpy as np, torch, gymnasium as gym
from PIL import Image, ImageDraw
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "source")); sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import bimanual.tasks.manager_based.yam # noqa
from isaaclab_tasks.utils import parse_env_cfg
TASK = "Template-YAM-Play-v0"; dev = "cuda:0"
cfg = parse_env_cfg(TASK, device=dev, num_envs=1)
cfg.episode_length_s = 1.0e6
try:
cfg.terminations.time_out = None
except Exception:
pass
try:
# close-in view of the object spot, so the contact sheet actually shows the orientation
cfg.viewer.eye = (0.55, -0.35, 0.85); cfg.viewer.lookat = (0.0, 0.10, 0.50)
cfg.viewer.resolution = (640, 480)
except Exception as e:
print("viewer cfg:", e)
env = gym.make(TASK, cfg=cfg, render_mode="rgb_array"); u = env.unwrapped; env.reset()
if args.obj not in u.scene.rigid_objects:
raise SystemExit(f"[probe] {args.obj!r} not in scene; register it with YAM_RW_OBJECTS")
OBJ = u.scene.rigid_objects[args.obj]
origin = u.scene.env_origins[0].cpu().numpy()
TABLE_TOP = 0.45
XY = [float(v) for v in args.xy.split(",")]
import omni.usd
from pxr import UsdGeom, Usd
stage = omni.usd.get_context().get_stage()
bbc = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_, UsdGeom.Tokens.render])
rng = bbc.ComputeWorldBound(stage.GetPrimAtPath(OBJ.root_physx_view.prim_paths[0])).ComputeAlignedRange()
ext = np.array(rng.GetMax())-np.array(rng.GetMin())
print(f"[probe] {args.obj} authored extents={np.round(ext,4)}", flush=True)
def quat(r, pi, y):
r, pi, y = np.radians([r, pi, y])
cr, sr = np.cos(r/2), np.sin(r/2); cp, sp = np.cos(pi/2), np.sin(pi/2); cy, sy = np.cos(y/2), np.sin(y/2)
return np.array([cr*cp*cy+sr*sp*sy, sr*cp*cy-cr*sp*sy, cr*sp*cy+sr*cp*sy, cr*cp*sy-sr*sp*cy])
def zeros_act():
return torch.zeros((1, env.action_space.shape[1]), dtype=torch.float32, device=dev)
tiles = []
for spec in [s for s in args.rpys.split(";") if s]:
r, pi, y = [float(v) for v in spec.split(",")]
q = quat(r, pi, y)
seat = TABLE_TOP+float(max(ext))/2.0+0.01 # drop from just above, whatever axis is up
OBJ.write_root_pose_to_sim(torch.tensor(np.concatenate([origin+np.array([XY[0], XY[1], seat]), q]),
dtype=torch.float32, device=dev).view(1, 7))
OBJ.write_root_velocity_to_sim(torch.zeros((1, 6), device=dev))
for _ in range(120): # let it settle onto the table
env.step(zeros_act())
w = OBJ.data.root_pos_w[0].cpu().numpy()-origin
qs = OBJ.data.root_quat_w[0].cpu().numpy()
# world-space height of the object above the table = the objective "is it upright" signal
height = float(w[2]-TABLE_TOP)*2
img = env.render()
im = Image.fromarray(np.asarray(img)[..., :3].copy()) if img is not None else Image.new("RGB", (640, 480))
d = ImageDraw.Draw(im)
txt = f"rpy=({r:.0f},{pi:.0f},{y:.0f}) settled z={w[2]:.3f} (~{height:.3f} tall)"
d.rectangle([0, 0, 430, 22], fill=(0, 0, 0)); d.text((5, 4), txt, fill=(255, 235, 60))
tiles.append(im)
print(f"[probe] rpy=({r:.0f},{pi:.0f},{y:.0f}) settled pos=({w[0]:.3f},{w[1]:.3f},{w[2]:.3f}) "
f"quat={np.round(qs,3)}", flush=True)
cols = 3
rows = (len(tiles)+cols-1)//cols
W, H = tiles[0].size
sheet = Image.new("RGB", (W*cols, H*rows))
for i, im in enumerate(tiles):
sheet.paste(im, (W*(i % cols), H*(i//cols)))
os.makedirs(os.path.dirname(args.out), exist_ok=True)
sheet.save(args.out)
print(f"[probe] contact sheet -> {args.out}", flush=True)
env.close(); app.close(); print("ORIENT_PROBE_OK", flush=True)