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),
    }