File size: 4,560 Bytes
deb6167 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | """Pinned raw Grabette inventory. No LeRobot or generic video SLAM inputs."""
import json
import math
import re
import tempfile
from pathlib import Path
from huggingface_hub import HfApi, hf_hub_download
from huggingface_hub.utils import validate_repo_id
REQUIRED = {
"raw_video.mp4",
"frame_timestamps.json",
"rpi_camera_intrinsics.json",
"metadata.json",
"dcam_imu.json",
"frames.json",
"dcam_calib_offline.json",
}
RECORDING = re.compile(r"[0-9]{8}_[0-9]{6}")
def inventory(dataset, token=False, revision=None):
"""Pin a repository revision and require complete raw Grabette RGB and IMU inputs."""
dataset = dataset.strip()
validate_repo_id(dataset)
if dataset.count("/") != 1:
raise ValueError("Use a raw Grabette repository ID: owner/dataset")
api = HfApi(token=token or False)
info = api.dataset_info(dataset, revision=revision)
files = set(api.list_repo_files(dataset, repo_type="dataset", revision=info.sha))
streams = []
for name in sorted(files):
parts = name.split("/")
if len(parts) != 3 or parts[2] != "raw_video.mp4":
continue
recording, hand, _ = parts
if not RECORDING.fullmatch(recording) or hand not in {"left", "right"}:
continue
prefix = f"{recording}/{hand}"
missing = sorted(f for f in REQUIRED if f"{prefix}/{f}" not in files)
if missing:
raise ValueError(f"{prefix}: raw Grabette + IMU inputs missing: {', '.join(missing)}")
streams.append({"recording": recording, "hand": hand, "prefix": prefix})
if not streams:
raise ValueError(
"Grabette SLAM requires raw recordings at YYYYMMDD_HHMMSS/right/ or left/, including raw_video.mp4, per-recording intrinsics and OAK IMU calibration. Converted LeRobot datasets are not supported here."
)
return info, streams
def read_raw_cameras(dataset, token=False):
"""Discover available Grabette hands without downloading video or IMU streams."""
info, streams = inventory(dataset, token)
return {
"dataset": info.id,
"revision": info.sha,
"cameras": sorted({s["hand"] for s in streams}),
"source": "raw-grabette",
"recordings": len({s["recording"] for s in streams}),
}
def read_raw_selection(dataset, start, count, camera=None, token=False, revision=None):
"""Read pinned per-recording metadata for one hand and a reproducible range."""
if type(start) is not int or type(count) is not int or min(start, count) < 0:
raise ValueError("Starting recording and count must be nonnegative integers")
info, streams = inventory(dataset, token, revision)
cameras = sorted({s["hand"] for s in streams})
camera = camera or cameras[0]
if camera not in cameras:
raise ValueError("Choose a Grabette hand available in this repository")
streams = [s for s in streams if s["hand"] == camera]
total = len(streams)
chosen = streams[start : start + count] if count else streams[start:]
if not chosen:
raise ValueError("No raw Grabette recordings in this range")
rows, seconds = [], 0.0
with tempfile.TemporaryDirectory(prefix="grabette-metadata-") as cache:
for index, stream in enumerate(chosen, start=start):
path = hf_hub_download(
info.id,
f"{stream['prefix']}/metadata.json",
repo_type="dataset",
revision=info.sha,
token=token or False,
cache_dir=cache,
)
meta = json.loads(Path(path).read_text())
duration, frames = float(meta["duration_seconds"]), int(meta["frame_count"])
if (
not math.isfinite(duration)
or duration <= 0
or frames < 2
or meta.get("backend") != "rpi"
or meta.get("hand") != camera
):
raise ValueError(f"{stream['prefix']}: unsupported raw Grabette metadata")
rows.append(dict(stream, episode_index=index, length=frames, duration_seconds=duration))
seconds += duration
return {
"dataset": info.id,
"revision": info.sha,
"source": "raw-grabette",
"info": {"fps": 50, "features": {}},
"episodes": rows,
"cameras": cameras,
"camera": camera,
"seconds": seconds,
"total_episodes": total,
"start": start,
"count": count,
"private": bool(info.private),
}
|