twanghcmut's picture
download
raw
4.19 kB
#!/usr/bin/env python
"""Sample DROID episode uuids across labs and fetch each one's task instruction.
The 42,935 ``*_cameras.json`` filenames already on disk under
``data/pointworld/droid/cameras`` are the full episode universe -- the uuid is the
filename, so no network call is needed to *enumerate* episodes, only to learn what
each one is about. That "what" lives in the episode's own
``metadata_<uuid>.json`` on the public DROID bucket (1.8 KB, key ``current_task``).
**Sampled per lab, not uniformly at random over all 42,935.** The local corpus is
12,300 TRI / 6,140 AUTOLab / ... down to 512 RAD, and a uniform draw would return
mostly TRI. Object variety in DROID tracks the lab (each site set up its own
scenes and props), so a per-lab quota is what actually buys diverse objects, which
is the point of the survey.
Politeness: a small thread pool and a single pass. ``resolve_episode_url`` may probe
``success`` then ``failure``, so budget up to 2 requests per uuid.
Usage:
PYTHONPATH=src python scripts/survey_droid_tasks.py \\
--per-lab 60 --out /tmp/droid_tasks.jsonl [--workers 12]
"""
from __future__ import annotations
import argparse
import json
import random
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from fpgm.data.droid_raw import DroidRawClient
from fpgm.utils.logging import get_logger, setup_logging
logger = get_logger("survey_droid_tasks")
def episode_uuids(cameras_dir: Path) -> list[str]:
return sorted(p.name.removesuffix("_cameras.json") for p in cameras_dir.glob("*_cameras.json"))
def sample_per_lab(uuids: list[str], per_lab: int, seed: int) -> list[str]:
by_lab: dict[str, list[str]] = defaultdict(list)
for u in uuids:
by_lab[u.split("+", 1)[0]].append(u)
rng = random.Random(seed)
picked: list[str] = []
for lab in sorted(by_lab):
pool = by_lab[lab]
picked.extend(rng.sample(pool, min(per_lab, len(pool))))
rng.shuffle(picked)
return picked
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--cameras-dir", type=Path, default=Path("data/pointworld/droid/cameras"))
ap.add_argument("--cache-dir", type=Path, default=Path("data/droid_meta_survey"))
ap.add_argument("--out", type=Path, required=True)
ap.add_argument("--per-lab", type=int, default=60)
ap.add_argument("--workers", type=int, default=12)
ap.add_argument("--seed", type=int, default=42)
ap.add_argument("--log-level", default="WARNING")
args = ap.parse_args()
setup_logging(args.log_level)
uuids = episode_uuids(args.cameras_dir)
picked = sample_per_lab(uuids, args.per_lab, args.seed)
print(f"universe {len(uuids)} episodes -> sampling {len(picked)}")
client = DroidRawClient(args.cache_dir)
def fetch(uuid: str) -> dict | None:
try:
meta = client.load_episode_metadata(uuid)
except Exception as exc: # noqa: BLE001 - a dead uuid must not kill the survey
return {"uuid": uuid, "error": type(exc).__name__, "detail": str(exc)[:160]}
return {
"uuid": uuid,
"lab": uuid.split("+", 1)[0],
"task": (meta.get("current_task") or "").strip(),
"ext1_serial": meta.get("ext1_cam_serial"),
"ext2_serial": meta.get("ext2_cam_serial"),
}
rows: list[dict] = []
ok = err = 0
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(fetch, u): u for u in picked}
for i, fut in enumerate(as_completed(futures), 1):
row = fut.result()
if row is None:
continue
rows.append(row)
if "error" in row:
err += 1
else:
ok += 1
if i % 100 == 0:
print(f" {i}/{len(picked)} ok={ok} err={err}")
args.out.parent.mkdir(parents=True, exist_ok=True)
with args.out.open("w") as f:
for row in rows:
f.write(json.dumps(row) + "\n")
print(f"done: ok={ok} err={err} -> {args.out}")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
4.19 kB
·
Xet hash:
042e128ded263dcf93ac40de3eaaba8c5e3b577c12ad6e073ebedd37bbd987c5

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.