Download scripts/yam_task_runner.py from yqi19/mp_yam_code: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/yqi19/mp_yam_code/resolve/main/scripts/yam_task_runner.py
- Command line
-
hf download hf://yqi19/mp_yam_code/scripts/yam_task_runner.py
-
curl -L -o yam_task_runner.py https://huggingface.co/yqi19/mp_yam_code/resolve/main/scripts/yam_task_runner.py
12.4 kB
| """Registry + runner for the YAML-declared YAM tasks. | |
| A task is a YAML file under ``tasks/`` (see ``tasks/fruit_basket.yaml``). This module turns it | |
| into a command line for the skill script that implements it, so adding a task means writing a | |
| YAML file -- not another 400-line demo script. Modelled on RoboLab's task registration: a task | |
| declares its scene, its instruction variants, and its success conditions; the conditions come | |
| from ``bimanual.yam_tasks.conditions``. | |
| python scripts/yam_task_runner.py list # registry table | |
| python scripts/yam_task_runner.py show fruit_basket # resolved command + conditions | |
| python scripts/yam_task_runner.py run fruit_basket # run it, print PASS/FAIL + video | |
| python scripts/yam_task_runner.py run --all --tag robotwin-asset | |
| No Isaac import here: the runner only builds and launches commands, so ``list``/``show`` are | |
| instant and usable without a GPU. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| import yaml | |
| REPO = Path(__file__).resolve().parent.parent | |
| TASK_DIR = REPO/"tasks" | |
| PY = os.environ.get("YAM_PYTHON", sys.executable) | |
| KIT_ARGS = "--kit_args=--/rtx/verifyDriverVersion/enabled=false" | |
| # skill name -> the script that implements it. A skill is "how to do it"; a task is "what, | |
| # with which objects, and what counts as done". | |
| SKILLS = { | |
| "pick_place": "scripts/yam_grasp_prm.py", | |
| "insert": "scripts/yam_grasp_prm.py", | |
| "multi_pick": "scripts/yam_multi_pick.py", | |
| "stack": "scripts/yam_stack_target.py", | |
| "dual_arm_lift": "scripts/yam_pot_lift.py", | |
| "tool_use": "scripts/yam_tool_tasks.py", | |
| "sort": "scripts/yam_cubes.py", | |
| } | |
| def load_tasks() -> dict[str, dict]: | |
| tasks = {} | |
| for f in sorted(TASK_DIR.glob("*.yaml")): | |
| spec = yaml.safe_load(f.read_text()) | |
| spec["_file"] = str(f.relative_to(REPO)) | |
| tasks[spec["name"]] = spec | |
| return tasks | |
| def _xy(v): | |
| return f"{float(v[0])},{float(v[1])}" | |
| def build_command(spec: dict) -> list[str]: | |
| """Resolve a task spec into an executable command line.""" | |
| skill = spec["skill"] | |
| if skill not in SKILLS: | |
| raise SystemExit(f"unknown skill {skill!r}; known: {sorted(SKILLS)}") | |
| cmd = [PY, str(REPO/SKILLS[skill]), "--headless"] | |
| scene = spec.get("scene", {}) | |
| params = spec.get("params", {}) | |
| objects = scene.get("objects", []) | |
| container = scene.get("container") | |
| video = spec.get("video", f"outputs/tasks/{spec['name']}.mp4") | |
| if skill == "multi_pick": | |
| pick = params.get("pick", [o["name"] for o in objects if o.get("role") == "target"]) | |
| leave = params.get("leave", [o["name"] for o in objects if o.get("role") == "distractor"]) | |
| by_name = {o["name"]: o for o in objects} | |
| cmd += ["--pick", ",".join(pick), "--leave", ",".join(leave), | |
| "--pick_xy", ";".join(_xy(by_name[n]["xy"]) for n in pick)] | |
| if leave: | |
| cmd += ["--leave_xy", ";".join(_xy(by_name[n]["xy"]) for n in leave)] | |
| if container: | |
| cmd += ["--basket_xy", _xy(container["xy"]), | |
| "--container", container["usd"], | |
| "--container_scale", str(container.get("scale", 1.0))] | |
| if container.get("on_top"): | |
| cmd += ["--on_top"] | |
| if params.get("label"): | |
| cmd += ["--label", params["label"]] | |
| elif skill in ("pick_place", "insert"): | |
| obj = objects[0] | |
| cmd += ["--obj", obj["name"], "--obj_xy", _xy(obj["xy"])] | |
| if container: | |
| cmd += ["--box_xy", _xy(container["xy"])] | |
| if container.get("usd"): | |
| cmd += ["--container", container["usd"], | |
| "--container_scale", str(container.get("scale", 1.0))] | |
| else: | |
| cmd += ["--basket"] | |
| if skill == "insert": | |
| cmd += ["--insert", "--basket"] | |
| if params.get("hole"): | |
| cmd += [f"--hole={params['hole']}"] | |
| if params.get("grasp_top"): | |
| cmd += [f"--grasp_top={params['grasp_top']}"] | |
| if params.get("jaw"): | |
| cmd += ["--jaw", params["jaw"]] | |
| elif skill == "stack": | |
| cmd += ["--blocks", ",".join(params["blocks"]), | |
| "--block_xy", ";".join(_xy(o["xy"]) for o in objects if o.get("role") != "target_region"), | |
| "--target_xy", _xy(scene["target_region"]["xy"]), | |
| "--target_size", str(scene["target_region"].get("size", 0.11))] | |
| elif skill == "dual_arm_lift": | |
| cmd += ["--obj", objects[0]["name"], "--pot_xy", _xy(objects[0]["xy"])] | |
| if params.get("pot_rpy"): | |
| cmd += ["--pot_rpy", params["pot_rpy"]] | |
| if params.get("lift"): | |
| cmd += ["--lift", str(params["lift"])] | |
| elif skill == "tool_use": | |
| cmd += ["--task", params["mode"]] | |
| if objects: | |
| cmd += ["--obj", objects[0]["name"], "--obj_xy", _xy(objects[0]["xy"])] | |
| if params.get("tool"): | |
| cmd += ["--tool", params["tool"]] | |
| if scene.get("target_region"): | |
| cmd += ["--target_xy", _xy(scene["target_region"]["xy"]), | |
| "--target_size", str(scene["target_region"].get("size", 0.10))] | |
| elif skill == "sort": | |
| cmd += ["--task", params.get("mode", "sort")] | |
| if "episode" in params: | |
| cmd += ["--episode", str(params["episode"])] | |
| cmd += ["--video", video, KIT_ARGS] | |
| return cmd | |
| def env_for(spec: dict) -> dict: | |
| """Extra env vars a task needs (RoboTwin asset dir, runtime-registered objects).""" | |
| env = dict(os.environ) | |
| env.setdefault("OMNI_KIT_ACCEPT_EULA", "YES") | |
| env.setdefault("OMNI_KIT_ALLOW_ROOT", "1") | |
| env.setdefault("PYTHONUNBUFFERED", "1") | |
| env.setdefault("ROBOTWIN_USD", os.environ.get("ROBOTWIN_USD", "/home/yu/internship_yu/robotwin_usd")) | |
| extra = spec.get("scene", {}).get("register_objects") | |
| if extra: | |
| env["YAM_RW_OBJECTS"] = ",".join( | |
| f"{o['name']}={o['usd']}:{o.get('scale', 0.05)}:{o.get('mass', 0.10)}" for o in extra) | |
| return env | |
| def describe_conditions(spec: dict) -> list[str]: | |
| out = [] | |
| for c in spec.get("success", []): | |
| (kind, kwargs), = c.items() | |
| out.append(f"{kind}({', '.join(f'{k}={v}' for k, v in kwargs.items())})") | |
| return out | |
| def cmd_list(tasks, args): | |
| tag = args.tag | |
| rows = [(n, s.get("skill", "?"), ",".join(s.get("tags", [])), s.get("video", "")) | |
| for n, s in sorted(tasks.items()) if not tag or tag in s.get("tags", [])] | |
| w = max([len(r[0]) for r in rows]+[4]) | |
| print(f"{'name'.ljust(w)} {'skill'.ljust(14)} tags") | |
| print("-"*(w+70)) | |
| for n, sk, tg, _ in rows: | |
| print(f"{n.ljust(w)} {sk.ljust(14)} {tg}") | |
| print(f"\n{len(rows)} task(s) in {TASK_DIR.relative_to(REPO)}/") | |
| def cmd_show(tasks, args): | |
| spec = tasks[args.name] | |
| print(f"# {spec['name']} ({spec['_file']})") | |
| print(f"instruction: {spec.get('instruction', {}).get('default','')}") | |
| print(f"skill: {spec['skill']} -> {SKILLS.get(spec['skill'])}") | |
| print(f"tags: {', '.join(spec.get('tags', []))}") | |
| print("success conditions:") | |
| for c in describe_conditions(spec): | |
| print(f" - {c}") | |
| print("\ncommand:") | |
| print(" " + " ".join(build_command(spec))) | |
| extra = env_for(spec).get("YAM_RW_OBJECTS") | |
| if extra: | |
| print(f"\nYAM_RW_OBJECTS={extra}") | |
| def verify_run(spec: dict, video: Path, verifier: str | None) -> dict: | |
| """Agent-in-the-loop check: the numeric EPISODE_RESULT only inspects the final object pose, | |
| so it happily passes an episode where the cup landed upside-down or the basket is sunk into | |
| the table. Render a contact sheet and have a vision agent judge what actually happened. | |
| `verifier` is a shell command template receiving {sheet} and {claim}; it must print JSON | |
| {"verdict": ..., "notes": ...}. With no verifier the request is written out for a harness | |
| (e.g. a Claude Code subagent) to pick up -- nothing is uploaded anywhere by default. | |
| """ | |
| sys.path.insert(0, str(REPO/"scripts")) | |
| from yam_agent_loop import make_sheet, REVIEW_PROMPT # noqa: E402 | |
| sheet = make_sheet(video) | |
| if sheet is None: | |
| return {"verdict": "NO_VIDEO", "notes": "no frames rendered"} | |
| claim = spec.get("instruction", {}).get("specific") or spec.get("instruction", {}).get("default", spec["name"]) | |
| prompt = REVIEW_PROMPT.format(sheet=sheet.relative_to(REPO), claim=claim) | |
| if not verifier: | |
| req = REPO/"outputs"/"verify"/f"{spec['name']}_request.json" | |
| req.write_text(json.dumps({"sheet": str(sheet.relative_to(REPO)), "claim": claim, | |
| "prompt": prompt}, indent=2)) | |
| return {"verdict": "PENDING_AGENT", "sheet": str(sheet.relative_to(REPO)), | |
| "request": str(req.relative_to(REPO))} | |
| out = subprocess.run(verifier.format(sheet=str(sheet), claim=json.dumps(claim)), | |
| shell=True, capture_output=True, text=True, cwd=REPO) | |
| try: | |
| return json.loads(out.stdout.strip().splitlines()[-1]) | |
| except Exception: | |
| return {"verdict": "UNPARSED", "notes": (out.stdout or out.stderr)[-400:]} | |
| def cmd_run(tasks, args): | |
| names = sorted(tasks) if args.all else [args.name] | |
| if args.tag: | |
| names = [n for n in names if args.tag in tasks[n].get("tags", [])] | |
| results = {} | |
| for n in names: | |
| spec = tasks[n] | |
| cmd = build_command(spec) | |
| log = REPO/"outputs"/"tasks"/f"{n}.log" | |
| log.parent.mkdir(parents=True, exist_ok=True) | |
| (REPO/"outputs"/"verify").mkdir(parents=True, exist_ok=True) | |
| print(f"\n===== RUN {n} =====\n {' '.join(cmd)}", flush=True) | |
| with open(log, "w") as fh: | |
| rc = subprocess.call(cmd, cwd=REPO, env=env_for(spec), stdout=fh, stderr=subprocess.STDOUT) | |
| text = log.read_text() | |
| verdict = "UNKNOWN" | |
| for line in text.splitlines(): | |
| if "EPISODE_RESULT" in line: | |
| verdict = "SUCCESS" if "SUCCESS" in line else "FAIL" | |
| entry = {"numeric": verdict, "exit": rc, "video": spec.get("video")} | |
| if not args.no_verify: | |
| v = REPO/spec.get("video", f"outputs/tasks/{n}.mp4") | |
| entry["visual"] = verify_run(spec, v, args.verifier) if v.exists() else {"verdict": "NO_VIDEO"} | |
| results[n] = entry | |
| print(f" exit={rc} numeric={verdict} visual={entry.get('visual', {}).get('verdict', 'skipped')}" | |
| f" video={spec.get('video')}", flush=True) | |
| (REPO/"outputs"/"verify"/"run_results.json").write_text(json.dumps(results, indent=2)) | |
| print("\n===== SUMMARY (numeric result + agent visual verdict) =====") | |
| for n, e in results.items(): | |
| vis = e.get("visual", {}).get("verdict", "skipped") | |
| print(f" {e['numeric'].ljust(8)} | {str(vis).ljust(14)} {n} -> {e['video']}") | |
| print("\nagreeing SUCCESS+CONFIRMED is the only state that counts as done; " | |
| "feed the rest to `yam_agent_loop.py update` for the concrete parameter fix.") | |
| return 0 if all(e["numeric"] == "SUCCESS" for e in results.values()) else 1 | |
| def main(): | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| sub = ap.add_subparsers(dest="cmd", required=True) | |
| p = sub.add_parser("list"); p.add_argument("--tag", default="") | |
| p = sub.add_parser("show"); p.add_argument("name") | |
| p = sub.add_parser("run"); p.add_argument("name", nargs="?"); p.add_argument("--all", action="store_true") | |
| p.add_argument("--tag", default="") | |
| p.add_argument("--no-verify", action="store_true", help="skip the agent visual check") | |
| p.add_argument("--verifier", default=os.environ.get("YAM_VERIFIER"), | |
| help="shell command template with {sheet} and {claim}; must print JSON " | |
| "{verdict, notes}. Omit to just emit the review request for an agent.") | |
| args = ap.parse_args() | |
| tasks = load_tasks() | |
| if args.cmd == "list": | |
| return cmd_list(tasks, args) | |
| if args.cmd == "show": | |
| return cmd_show(tasks, args) | |
| if args.cmd == "run": | |
| if not args.all and not args.name: | |
| raise SystemExit("give a task name or --all") | |
| return cmd_run(tasks, args) | |
| if __name__ == "__main__": | |
| sys.exit(main() or 0) | |