Spaces:
Running
Running
Shubham-Rasal Claude Sonnet 4.6 commited on
Commit Β·
07c7d4a
1
Parent(s): 1dd20f4
add real robot dataset converters and sample CSVs
Browse files4 datasets: ALOHA (14-DOF), Push-T real (8-DOF), Franka Panda (13-DOF),
Unitree H1 humanoid (40-DOF). Space now has dataset picker with one-click
CSV download in the upload tab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- __pycache__/app.cpython-313.pyc +0 -0
- app.py +69 -4
- convert_to_eval_csv.py +180 -0
- sample_comparison.csv +0 -0
- sample_pusht.csv +0 -0
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -379,7 +379,7 @@ def run_upload(file):
|
|
| 379 |
return run_analysis(policy_data)
|
| 380 |
|
| 381 |
|
| 382 |
-
# ββ CSV template
|
| 383 |
def make_template():
|
| 384 |
rows = []
|
| 385 |
for ep in range(3):
|
|
@@ -395,6 +395,46 @@ def make_template():
|
|
| 395 |
df.to_csv(path, index=False)
|
| 396 |
return path
|
| 397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
# ββ UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 400 |
CSS = f"""
|
|
@@ -501,15 +541,40 @@ def build_ui():
|
|
| 501 |
|
| 502 |
# ββ TAB 2: UPLOAD βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 503 |
with gr.Tab("π Upload Your Own Data"):
|
|
|
|
| 504 |
with gr.Row():
|
| 505 |
with gr.Column(scale=2):
|
| 506 |
-
gr.Markdown(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
with gr.Column(scale=1):
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
tmpl_file = gr.File(label="Template")
|
| 510 |
tmpl_btn.click(fn=make_template, outputs=tmpl_file)
|
| 511 |
|
| 512 |
-
|
|
|
|
|
|
|
|
|
|
| 513 |
run_upload_btn = gr.Button("βΆ Analyse Uploaded Data", variant="primary", size="lg")
|
| 514 |
|
| 515 |
with gr.Row():
|
|
|
|
| 379 |
return run_analysis(policy_data)
|
| 380 |
|
| 381 |
|
| 382 |
+
# ββ CSV template + sample downloads βββββββββββββββββββββββββββββββββββββββββββ
|
| 383 |
def make_template():
|
| 384 |
rows = []
|
| 385 |
for ep in range(3):
|
|
|
|
| 395 |
df.to_csv(path, index=False)
|
| 396 |
return path
|
| 397 |
|
| 398 |
+
SAMPLE_DATASETS = {
|
| 399 |
+
"ALOHA bimanual β cup opening (14-DOF)":
|
| 400 |
+
("lerobot/aloha_static_cups_open", "observation.state", "action", 20),
|
| 401 |
+
"Push-T real robot β tabletop push (8-DOF)":
|
| 402 |
+
("lerobot/columbia_cairlab_pusht_real", "observation.state", "action", 20),
|
| 403 |
+
"Franka Panda β free-play manipulation (13-DOF)":
|
| 404 |
+
("lerobot/nyu_franka_play_dataset", "observation.state", "action", 20),
|
| 405 |
+
"Unitree H1 humanoid β warehouse (19-DOF / 40-DOF action)":
|
| 406 |
+
("lerobot/unitreeh1_warehouse", "observation.state", "action", 12),
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
def download_sample(choice, progress=gr.Progress()):
|
| 410 |
+
if not choice:
|
| 411 |
+
return None
|
| 412 |
+
progress(0.1, desc=f"Loading {choice}β¦")
|
| 413 |
+
hf, sc, ac, max_eps = SAMPLE_DATASETS[choice]
|
| 414 |
+
ds = load_dataset(hf, split="train")
|
| 415 |
+
df_raw = ds.to_pandas()
|
| 416 |
+
ep_ids = sorted(df_raw["episode_index"].unique())[:max_eps]
|
| 417 |
+
|
| 418 |
+
rows = []
|
| 419 |
+
policy_name = choice.split("β")[0].strip()
|
| 420 |
+
progress(0.4, desc="Extracting episodesβ¦")
|
| 421 |
+
for ei in ep_ids:
|
| 422 |
+
grp = df_raw[df_raw["episode_index"] == ei].reset_index(drop=True)
|
| 423 |
+
success = int(grp["next.reward"].max() > 0) if "next.reward" in grp.columns else 1
|
| 424 |
+
states = np.vstack(grp[sc].values)
|
| 425 |
+
actions = np.vstack(grp[ac].values)
|
| 426 |
+
for fi, (s, a) in enumerate(zip(states, actions)):
|
| 427 |
+
row = {"episode_id": int(ei), "policy_name": policy_name,
|
| 428 |
+
"frame_id": fi, "success": success}
|
| 429 |
+
for i, v in enumerate(s): row[f"state_{i}"] = round(float(v), 6)
|
| 430 |
+
for i, v in enumerate(a): row[f"action_{i}"] = round(float(v), 6)
|
| 431 |
+
rows.append(row)
|
| 432 |
+
|
| 433 |
+
path = f"/tmp/sample_{hf.split('/')[-1]}.csv"
|
| 434 |
+
pd.DataFrame(rows).to_csv(path, index=False)
|
| 435 |
+
progress(1.0, desc="Ready!")
|
| 436 |
+
return path
|
| 437 |
+
|
| 438 |
|
| 439 |
# ββ UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 440 |
CSS = f"""
|
|
|
|
| 541 |
|
| 542 |
# ββ TAB 2: UPLOAD βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 543 |
with gr.Tab("π Upload Your Own Data"):
|
| 544 |
+
gr.Markdown("### Try a real dataset β or upload your own rollouts")
|
| 545 |
with gr.Row():
|
| 546 |
with gr.Column(scale=2):
|
| 547 |
+
gr.Markdown("""
|
| 548 |
+
**Step 1 β pick a real robot dataset to download as a ready-to-use CSV:**
|
| 549 |
+
|
| 550 |
+
| Dataset | Robot | DOF | Task |
|
| 551 |
+
|---|---|---|---|
|
| 552 |
+
| ALOHA bimanual | Stanford ALOHA (2Γ ViperX) | 14 | Cup opening |
|
| 553 |
+
| Push-T real | Columbia delta robot | 8 | Push block to goal |
|
| 554 |
+
| Franka Panda | NYU Franka Emika Panda | 13 | Free-play manipulation |
|
| 555 |
+
| Unitree H1 | Full-size humanoid | 19 state / 40 action | Warehouse pick-place |
|
| 556 |
+
|
| 557 |
+
Then upload the downloaded CSV below and hit **Analyse**.
|
| 558 |
+
""")
|
| 559 |
with gr.Column(scale=1):
|
| 560 |
+
sample_picker = gr.Dropdown(
|
| 561 |
+
choices=list(SAMPLE_DATASETS.keys()),
|
| 562 |
+
label="Real robot dataset",
|
| 563 |
+
value=None,
|
| 564 |
+
)
|
| 565 |
+
dl_btn = gr.Button("β¬ Download as CSV", variant="secondary")
|
| 566 |
+
dl_file = gr.File(label="Downloaded CSV (upload below β)")
|
| 567 |
+
dl_btn.click(fn=download_sample, inputs=[sample_picker], outputs=[dl_file])
|
| 568 |
+
|
| 569 |
+
gr.Markdown("---")
|
| 570 |
+
tmpl_btn = gr.Button("β¬ Blank template CSV", variant="secondary")
|
| 571 |
tmpl_file = gr.File(label="Template")
|
| 572 |
tmpl_btn.click(fn=make_template, outputs=tmpl_file)
|
| 573 |
|
| 574 |
+
with gr.Accordion("CSV format reference", open=False):
|
| 575 |
+
gr.Markdown(FORMAT_HINT)
|
| 576 |
+
|
| 577 |
+
upload = gr.File(label="β¬ Upload rollout CSV (your own or downloaded above)", file_types=[".csv"])
|
| 578 |
run_upload_btn = gr.Button("βΆ Analyse Uploaded Data", variant="primary", size="lg")
|
| 579 |
|
| 580 |
with gr.Row():
|
convert_to_eval_csv.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Converters: real robot datasets β eval harness CSV format.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python convert_to_eval_csv.py --dataset pusht --out pusht_eval.csv
|
| 6 |
+
python convert_to_eval_csv.py --dataset franka --out franka_eval.csv
|
| 7 |
+
python convert_to_eval_csv.py --dataset humanoid --out humanoid_eval.csv
|
| 8 |
+
python convert_to_eval_csv.py --dataset aloha --out aloha_eval.csv
|
| 9 |
+
|
| 10 |
+
Output CSV columns:
|
| 11 |
+
episode_id, policy_name, frame_id, timestamp,
|
| 12 |
+
state_0 ... state_N, action_0 ... action_N, success
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import numpy as np
|
| 17 |
+
import pandas as pd
|
| 18 |
+
from datasets import load_dataset
|
| 19 |
+
|
| 20 |
+
# ββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
+
|
| 22 |
+
def episode_success(group, reward_col="next.reward", done_col="next.done"):
|
| 23 |
+
"""Infer episode success from reward signal or done flag."""
|
| 24 |
+
if reward_col in group.columns:
|
| 25 |
+
return int(group[reward_col].max() > 0)
|
| 26 |
+
# fallback: episode completed normally = success
|
| 27 |
+
return int(group[done_col].iloc[-1]) if done_col in group.columns else 1
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def to_eval_csv(hf_dataset_name, policy_name, state_col, action_col,
|
| 31 |
+
max_episodes=None, out_path=None):
|
| 32 |
+
print(f"Loading {hf_dataset_name} β¦")
|
| 33 |
+
ds = load_dataset(hf_dataset_name, split="train")
|
| 34 |
+
df = ds.to_pandas()
|
| 35 |
+
|
| 36 |
+
ep_ids = sorted(df["episode_index"].unique())
|
| 37 |
+
if max_episodes:
|
| 38 |
+
ep_ids = ep_ids[:max_episodes]
|
| 39 |
+
|
| 40 |
+
rows = []
|
| 41 |
+
for ei in ep_ids:
|
| 42 |
+
grp = df[df["episode_index"] == ei].reset_index(drop=True)
|
| 43 |
+
success = episode_success(grp)
|
| 44 |
+
|
| 45 |
+
states = np.vstack(grp[state_col].values)
|
| 46 |
+
actions = np.vstack(grp[action_col].values) if action_col in grp.columns else states
|
| 47 |
+
|
| 48 |
+
for fi, (s, a) in enumerate(zip(states, actions)):
|
| 49 |
+
row = {
|
| 50 |
+
"episode_id": int(ei),
|
| 51 |
+
"policy_name": policy_name,
|
| 52 |
+
"frame_id": fi,
|
| 53 |
+
"timestamp": round(grp["timestamp"].iloc[fi], 4) if "timestamp" in grp.columns else fi,
|
| 54 |
+
"success": success,
|
| 55 |
+
}
|
| 56 |
+
for i, v in enumerate(s):
|
| 57 |
+
row[f"state_{i}"] = round(float(v), 6)
|
| 58 |
+
for i, v in enumerate(a):
|
| 59 |
+
row[f"action_{i}"] = round(float(v), 6)
|
| 60 |
+
rows.append(row)
|
| 61 |
+
|
| 62 |
+
out = pd.DataFrame(rows)
|
| 63 |
+
if out_path:
|
| 64 |
+
out.to_csv(out_path, index=False)
|
| 65 |
+
print(f"Saved {len(ep_ids)} episodes ({len(out):,} frames) β {out_path}")
|
| 66 |
+
return out
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# ββ dataset-specific converters βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 70 |
+
|
| 71 |
+
DATASETS = {
|
| 72 |
+
# Real tabletop push-T (Columbia / CAIRLAB)
|
| 73 |
+
# Robot: custom delta robot, 2-DOF end-effector + contact sensors
|
| 74 |
+
# Task: push a T-shaped block to a goal region
|
| 75 |
+
# State: 8-dim (EE pos/vel + block pose estimate)
|
| 76 |
+
"pusht": dict(
|
| 77 |
+
hf="lerobot/columbia_cairlab_pusht_real",
|
| 78 |
+
label="Push-T (Columbia real robot)",
|
| 79 |
+
state_col="observation.state",
|
| 80 |
+
action_col="action",
|
| 81 |
+
max_eps=40,
|
| 82 |
+
note="2-DOF delta robot, tabletop push task, 136 episodes total"
|
| 83 |
+
),
|
| 84 |
+
|
| 85 |
+
# Franka Panda free-play dataset (NYU)
|
| 86 |
+
# Robot: 7-DOF Franka Emika Panda β the most common research arm
|
| 87 |
+
# Task: unstructured manipulation play (no fixed goal)
|
| 88 |
+
# State: 13-dim (7 joint pos + 6 EE pose)
|
| 89 |
+
"franka": dict(
|
| 90 |
+
hf="lerobot/nyu_franka_play_dataset",
|
| 91 |
+
label="Franka Panda Play (NYU)",
|
| 92 |
+
state_col="observation.state",
|
| 93 |
+
action_col="action",
|
| 94 |
+
max_eps=50,
|
| 95 |
+
note="7-DOF Franka Panda, 456 episodes of free-play manipulation"
|
| 96 |
+
),
|
| 97 |
+
|
| 98 |
+
# Unitree H1 humanoid β warehouse task
|
| 99 |
+
# Robot: full-size humanoid, 19-DOF state, 40-DOF action
|
| 100 |
+
# Task: pick and place in warehouse setting
|
| 101 |
+
# No reward signal β we treat episode completion as success
|
| 102 |
+
"humanoid": dict(
|
| 103 |
+
hf="lerobot/unitreeh1_warehouse",
|
| 104 |
+
label="Unitree H1 Humanoid (warehouse)",
|
| 105 |
+
state_col="observation.state",
|
| 106 |
+
action_col="action",
|
| 107 |
+
max_eps=24,
|
| 108 |
+
note="19-DOF humanoid state, 40-DOF action, 24 episodes"
|
| 109 |
+
),
|
| 110 |
+
|
| 111 |
+
# ALOHA bimanual static (cups open) β same as demo tab
|
| 112 |
+
"aloha": dict(
|
| 113 |
+
hf="lerobot/aloha_static_cups_open",
|
| 114 |
+
label="ALOHA Bimanual (cups open)",
|
| 115 |
+
state_col="observation.state",
|
| 116 |
+
action_col="action",
|
| 117 |
+
max_eps=50,
|
| 118 |
+
note="14-DOF bimanual ALOHA, 50 episodes, cup-opening task"
|
| 119 |
+
),
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# ββ multi-policy comparison helper βββββββββββββββββββββββββββββββββββββββββββ
|
| 124 |
+
|
| 125 |
+
def make_comparison_csv(datasets_and_names: list[tuple[str, str]],
|
| 126 |
+
max_eps_each: int = 20,
|
| 127 |
+
out_path: str = "comparison_eval.csv"):
|
| 128 |
+
"""
|
| 129 |
+
Combine multiple datasets as different 'policies' for A/B comparison.
|
| 130 |
+
|
| 131 |
+
datasets_and_names: list of (dataset_key, policy_label)
|
| 132 |
+
Example:
|
| 133 |
+
make_comparison_csv([("pusht","Push-T"), ("franka","Franka"), ("aloha","ALOHA")])
|
| 134 |
+
"""
|
| 135 |
+
dfs = []
|
| 136 |
+
for key, label in datasets_and_names:
|
| 137 |
+
cfg = DATASETS[key]
|
| 138 |
+
df = to_eval_csv(cfg["hf"], label, cfg["state_col"], cfg["action_col"],
|
| 139 |
+
max_episodes=max_eps_each)
|
| 140 |
+
# Truncate to common state dim
|
| 141 |
+
dfs.append(df)
|
| 142 |
+
|
| 143 |
+
# Align state/action columns across datasets (fill missing with 0)
|
| 144 |
+
out = pd.concat(dfs, ignore_index=True).fillna(0.0)
|
| 145 |
+
out.to_csv(out_path, index=False)
|
| 146 |
+
print(f"\nSaved multi-policy comparison CSV β {out_path}")
|
| 147 |
+
print(f"Policies: {out['policy_name'].unique().tolist()}")
|
| 148 |
+
print(f"Total episodes: {out['episode_id'].nunique()}")
|
| 149 |
+
print(f"Total frames: {len(out):,}")
|
| 150 |
+
return out
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
# ββ CLI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
parser = argparse.ArgumentParser()
|
| 157 |
+
parser.add_argument("--dataset", choices=list(DATASETS.keys()) + ["compare"],
|
| 158 |
+
default="pusht", help="Dataset to convert")
|
| 159 |
+
parser.add_argument("--out", default=None, help="Output CSV path")
|
| 160 |
+
parser.add_argument("--max-eps", type=int, default=None,
|
| 161 |
+
help="Max episodes to convert (default: all)")
|
| 162 |
+
args = parser.parse_args()
|
| 163 |
+
|
| 164 |
+
if args.dataset == "compare":
|
| 165 |
+
out = args.out or "comparison_eval.csv"
|
| 166 |
+
make_comparison_csv(
|
| 167 |
+
[("pusht","Push-T"), ("franka","Franka"), ("humanoid","H1-Humanoid")],
|
| 168 |
+
max_eps_each=args.max_eps or 15,
|
| 169 |
+
out_path=out,
|
| 170 |
+
)
|
| 171 |
+
else:
|
| 172 |
+
cfg = DATASETS[args.dataset]
|
| 173 |
+
out = args.out or f"{args.dataset}_eval.csv"
|
| 174 |
+
print(f"\n{cfg['label']}")
|
| 175 |
+
print(f"Note: {cfg['note']}\n")
|
| 176 |
+
to_eval_csv(cfg["hf"], cfg["label"], cfg["state_col"], cfg["action_col"],
|
| 177 |
+
max_episodes=args.max_eps or cfg["max_eps"], out_path=out)
|
| 178 |
+
|
| 179 |
+
print("\nDone. Upload the CSV to the HuggingFace Space:")
|
| 180 |
+
print(" https://huggingface.co/spaces/ShubhamRasal/robot-policy-eval")
|
sample_comparison.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
sample_pusht.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|