twanghcmut's picture
download
raw
5.26 kB
"""DROID raw bucket URL derivation from episode uuids.
The public GCS bucket keys objects by lab, calendar date, outcome
(``success``/``failure``) and a strftime-derived directory name. Everything
here is pure stdlib -- the network probe in :func:`resolve_episode_url` does a
lazy ``import requests`` -- so this module (and its uuid parsing logic) is
testable without numpy/h5py/requests installed, per :mod:`tests.test_ids`.
"""
from __future__ import annotations
from urllib.parse import quote
from fpgm.types import EpisodeId, EpisodeNotFoundError
#: DROID raw bucket root for the public, anonymous-access 1.0.1 release.
DROID_RAW_BUCKET_ROOT = "https://storage.googleapis.com/gresearch/robotics/droid_raw/1.0.1"
#: Whether an episode is a "success" or "failure" trajectory is not encoded
#: anywhere in the uuid, so callers who don't already know it must probe both.
#: "success" is tried first since it is the overwhelmingly common outcome.
OUTCOME_PROBE_ORDER = ("success", "failure")
def episode_dirname(episode_id: EpisodeId) -> str:
"""Derive the per-episode directory name used inside the DROID raw bucket.
This reproduces the directory name Python's own ``datetime.strftime``
produced when the episode was recorded:
``strftime('%a_%b_%e_%H:%M:%S_%Y').replace(' ', '_')``.
The trap is glibc's ``%e``: it pads single-digit days with a *space*, not
a zero, and that space then gets replaced by the trailing ``.replace(' ',
'_')`` just like the literal underscores in the format string do -- so
single-digit days produce a **double** underscore
(e.g. ``"Fri_Oct__6_19:50:14_2023"``), not a single one. Verified against
PointWorld's own ``real/droid_paths.txt`` for both single- and
double-digit days.
"""
raw = episode_id.timestamp.strftime("%a_%b_%e_%H:%M:%S_%Y")
return raw.replace(" ", "_")
def episode_date(episode_id: EpisodeId) -> str:
"""The ``YYYY-MM-DD`` bucket path component."""
return episode_id.timestamp.strftime("%Y-%m-%d")
def episode_base_url(episode_id: EpisodeId, outcome: str) -> str:
"""Build the (unverified) bucket URL for one success/failure hypothesis.
The colon-heavy ``episode_dirname`` and the plus-heavy uuid both need
percent-encoding for use in a URL; ``urllib.parse.quote``'s defaults
(which treat ``:`` and ``+`` as reserved) handle both correctly.
"""
if outcome not in OUTCOME_PROBE_ORDER:
raise ValueError(f"outcome must be one of {OUTCOME_PROBE_ORDER}, got {outcome!r}")
dirname = quote(episode_dirname(episode_id))
return (
f"{DROID_RAW_BUCKET_ROOT}/{episode_id.lab}/{outcome}/"
f"{episode_date(episode_id)}/{dirname}/"
)
def metadata_filename(episode_id: EpisodeId) -> str:
"""``metadata_<uuid>.json``, with the uuid's ``+`` percent-encoded for URLs."""
return f"metadata_{quote(episode_id.uuid)}.json"
#: PointWorld's ``cameras.json`` records the episode's real bucket directory as a
#: ``gs://`` URL. It is worth preferring over the derived one: across all 42935
#: episodes the uuid-derived path is wrong for 1.52% of them (465 use underscores
#: where the dirname normally has colons, 189 sit under a calendar date one day
#: off their own timestamp -- both concentrated in IRIS, where 14% of episodes are
#: unreachable without it). It also states the outcome, saving the HEAD probe that
#: :func:`resolve_episode_url` needs for the 16.4% of episodes under ``failure/``.
_GS_PREFIX = "gs://gresearch/robotics/droid_raw/1.0.1/"
def bucket_url_from_scene_path(scene_path: str) -> str:
"""Convert a ``cameras.json`` ``scene_path`` into an https base URL (trailing slash).
Raises:
ValueError: if ``scene_path`` is not a DROID raw 1.0.1 ``gs://`` URL --
silently falling back would turn a catalogue that disagrees with this
code's assumptions into a mysterious 404 much later.
"""
if not scene_path.startswith(_GS_PREFIX):
raise ValueError(f"not a DROID raw 1.0.1 scene_path: {scene_path!r}")
rel = scene_path[len(_GS_PREFIX):].strip("/")
return f"{DROID_RAW_BUCKET_ROOT}/" + "/".join(quote(p) for p in rel.split("/")) + "/"
def resolve_episode_url(uuid: str, timeout: float = 10.0) -> str:
"""Probe the bucket for ``uuid`` under ``success/`` then ``failure/``.
Returns the base URL (trailing slash) of whichever outcome directory
actually contains the episode. Network access is isolated in this one
function via a lazy ``requests`` import so the rest of :mod:`fpgm.data.ids`
stays free of third-party dependencies.
Raises:
EpisodeNotFoundError: neither outcome directory has this uuid.
"""
import requests
episode_id = EpisodeId(uuid)
for outcome in OUTCOME_PROBE_ORDER:
base_url = episode_base_url(episode_id, outcome)
probe_url = base_url + metadata_filename(episode_id)
try:
resp = requests.head(probe_url, timeout=timeout, allow_redirects=True)
except requests.RequestException:
continue
if resp.status_code == 200:
return base_url
raise EpisodeNotFoundError(
f"episode {uuid!r} not found under success/ or failure/ in the DROID raw bucket"
)

Xet Storage Details

Size:
5.26 kB
·
Xet hash:
3a5c82bd3350ff5a93f71f32d32de98e54d6cc71899f574ffaf1d338b3446afd

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