opentau.envs.subgoal
Eval-time subgoal image generators.
The pi07 low-level and pi07-paligemma low-level policies are trained with
subgoal images keyed as subgoal{k} (one per camera in
config.image_features) plus a single subgoal_is_pad bool. At eval
time these keys are produced by a SubgoalImageGenerator that
inspects the observation dict (post preprocess_observation +
add_envs_task + add_eval_metadata) and returns the matching batch
keys. opentau.envs.utils.add_subgoal_images() is the wiring helper
that calls the generator and merges its output into the observation.
The only concrete generator implemented today is
LiberoLastFrameSubgoalGenerator, which serves the
TensorAuto/libero 20fps v2.1 relabel: it looks up the env’s task
language (one of the 40 LIBERO task strings) in the dataset, samples a
random matching episode at the start of each rollout, and returns that
episode’s last frame from each camera as the subgoal.
Classes
|
Naive LIBERO subgoal generator backed by |
|
Serve harvested success goal-frames as eval-time subgoals, keyed by scene seed. |
|
Eval-time contract for producing subgoal images. |
- class opentau.envs.subgoal.LiberoLastFrameSubgoalGenerator(repo_id: str = 'TensorAuto/libero', root: str | Path | None = None, resolution: tuple[int, int] = (256, 256), num_cams: int = 2, tolerance_s: float = 0.0001, revision: str | None = None, seed: int | None = None)[source]
Bases:
objectNaive LIBERO subgoal generator backed by
TensorAuto/libero.For each env in the rollout, looks up the task language string (one of the 40 LIBERO subtask strings) in the dataset’s episode index, samples a random matching episode at the start of the rollout, and returns that episode’s last frame from each video camera as the subgoal. Camera images are resized with aspect-ratio padding to match
resolution(the same shapepreprocess_observationproduces for the livecamera{k}keys).The chosen episodes are pinned by
start_episode()and reused across every__call__()in the rollout — fresh draws happen on the nextrollout()call, which corresponds to the nextenv.reset(). If a prompt has no matching episode in the dataset,start_episode()raisesValueError.Today only
repo_id="TensorAuto/libero"is exercised; other LeRobot v2.1 repos with the same camera-name convention should work but are not tested.- Train↔eval distribution shift (intentional, naive baseline):
Training samples a within-episode future frame (
BaseDataset._sample_subgoal_frame: uniform[t, t+4s]or segment end). This generator picks a different episode (matched only by task language) and always returns its last frame. That’s a different conditional distribution from training; the policy was not conditioned on a foreign trajectory’s terminus. This is deliberate as a first baseline — see the PR that introduced this module for the rationale. Follow-up improvements (uniform within-episode sampling, or sampling from the rollout’s own replay) would close most of the gap.- Memory:
The per-(episode, camera) frame cache is unbounded — it grows monotonically across rollouts and survives every mid-training eval block. Bounded in practice by the source dataset size (~660 MB worst case for full LIBERO at the default resolution), but a larger source would need an LRU. TODO if/when this generator gets pointed at a non-LIBERO repo.
- __init__(repo_id: str = 'TensorAuto/libero', root: str | Path | None = None, resolution: tuple[int, int] = (256, 256), num_cams: int = 2, tolerance_s: float = 0.0001, revision: str | None = None, seed: int | None = None)[source]
Load dataset metadata and build the task-language episode index.
- Parameters:
repo_id – HuggingFace dataset repo to source subgoal frames from. Must be a v2.1 LeRobot dataset registered in
DATA_FEATURES_NAME_MAPPING.root – Optional local cache root for the dataset. Defaults to the OpenTau-managed location used by
LeRobotDataset.resolution – Target
(H, W)for the returned subgoal images, matchingcfg.resolution.num_cams – Number of camera slots to populate. Capped by the cameras the source dataset actually exposes (typically 2 for LIBERO).
tolerance_s – Timestamp tolerance for video frame decoding.
revision – Optional explicit dataset revision (e.g.
"v2.1", a branch, or a commit SHA).Nonefalls back toCODEBASE_VERSIONinsideLeRobotDatasetMetadata— currentlyv2.1, matchingTensorAuto/libero. Pin this when the codebase and dataset versions might drift apart and you need the eval-time subgoals to come from a specific relabel pass.seed – Seed for the per-instance
random.Randomused to pick episodes instart_episode().Nonefalls back to a default-constructedRandom(entropy-seeded per process). Threadingcfg.seedhere is what makes episode picks reproducible across runs; the process- globalrandomis not used so concurrenteval_policy_allworkers don’t race on shared RNG state.
- property fps: int
- start_episode(prompts: list[str], seeds: list[int] | None = None) None[source]
Sample one random matching episode per env, store for the rollout.
- Parameters:
prompts – Task language string per env in the vector.
seeds – Unused — this generator matches by task language and samples a random matching episode, so the scene seed is irrelevant. Accepted to satisfy the
SubgoalImageGeneratorprotocol.
- Raises:
ValueError – If any prompt has no matching episode in
lang_to_episodes. The error names the prompt and the count of known languages.
- class opentau.envs.subgoal.RoboCasaGoalFrameSubgoalGenerator(manifest_dirs: list[str | Path], task: str, resolution: tuple[int, int] = (256, 256), num_cams: int = 3, decoder_preference: tuple[str, ...] = ('flow', 'discrete'))[source]
Bases:
objectServe harvested success goal-frames as eval-time subgoals, keyed by scene seed.
Unlike
LiberoLastFrameSubgoalGenerator(which matches by task language and samples a random matching episode), this generator is keyed by the exact per-episode scene seed. Under a pinnedcfg.eval.seedeach RoboCasa env runs a deterministic sceneseed(seed + i), and we previously harvested the terminal camera frames of successful rollouts of those exact scenes — across several checkpoints — intogoal_frames/dirs, each with amanifest.csv(columnstask,seed,decoder,success,camera0, camera1,...) plus the per-(task, seed, decoder, camera).pngfiles.Given the
(task, seed)an env is running, this returns the matching goal frames assubgoal{k}; scenes with no harvested success getsubgoal_is_pad=True(no subgoal). That realises the “feed the successful last frame whenever available” semantics, and is the world-model upper-bound probe: the subgoal is the real terminus of a successful trajectory of the very scene being solved, so it measures how much a perfect goal-image predictor could help. The pi07/pi07-paligemma checkpoints were trained withsubgoal_end_of_segment_prob > 0, so terminal frames are an in-distribution subgoal.- Scope and assumptions:
Single task per instance.
taskis fixed at construction (it matchescfg.env.task, which RoboCasa eval sets to one task per invocation). The manifest’staskcolumn is filtered to it.Camera alignment.
camera{k}in the manifest maps directly tosubgoal{k}(samecamera{k}convention the env wrapper andpreprocess_observationproduce). The experiment must use the sameenv.camera_nameordering the frames were harvested under.Decoder preference. A scene may have a success frame from both the
flowanddiscretedecoders (same scene, slightly different terminal pose). One is chosen per seed bydecoder_preference; the chosen decoder’s frames are used for all cameras (never mixed).Union across dirs. Multiple
goal_frames/dirs (one per harvest checkpoint) are merged so coverage is the union; on a seed present in several dirs the best-ranked decoder wins, ties broken by dir order.
- __init__(manifest_dirs: list[str | Path], task: str, resolution: tuple[int, int] = (256, 256), num_cams: int = 3, decoder_preference: tuple[str, ...] = ('flow', 'discrete'))[source]
Build the
seed -> {camera_idx: png_path}index fortask.- Parameters:
manifest_dirs –
goal_frames/directories, each with amanifest.csvand the harvested.pngfiles. Merged into one index (union of scenes).task – RoboCasa task name to serve (filters the manifest
taskcolumn); matchescfg.env.task.resolution – Target
(H, W)for the returned subgoal images, matchingcfg.resolution(the shapepreprocess_observationproduces for the livecamera{k}keys).num_cams – Number of camera slots to populate; cameras with index
>= num_camsin the manifest are ignored.decoder_preference – Decoder ranking when a seed has frames from more than one decoder; earlier = preferred.
- property num_scenes: int
Number of distinct scene seeds with a harvested success frame.
- start_episode(prompts: list[str], seeds: list[int] | None = None) None[source]
Pin the per-env scene seeds for this rollout.
- Parameters:
prompts – Task language per env (unused — task is fixed at construction; kept for protocol parity).
seeds – Per-env scene seed for this rollout.
None(rollout not manually seeded) means every env is served as padded (no subgoal), since without the seed we cannot identify the scene.
- class opentau.envs.subgoal.SubgoalImageGenerator(*args, **kwargs)[source]
Bases:
ProtocolEval-time contract for producing subgoal images.
Generators are constructed once at the top of
opentau.scripts.eval.eval()and plumbed through toopentau.scripts.eval.rollout(), which callsstart_episode()once at the start of each rollout (matching the “once per episode reset” cadence) and then__call__()on every step. Implementations are expected to memoise the per-rollout choice insidestart_episode()so the per-step call is cheap.- __init__(*args, **kwargs)
- start_episode(prompts: list[str], seeds: list[int] | None = None) None[source]
Re-sample the per-env subgoal source for a fresh rollout.
Called once at the top of
opentau.scripts.eval.rollout()afterenv.reset()andadd_envs_taskhave populatedobservation["prompt"].- Parameters:
prompts – Task language string per env in the vector, length
env.num_envs.seeds – Per-env scene seed used for
env.reset(seed=...)this rollout, lengthenv.num_envs(orNoneif the rollout was not manually seeded). Language-matched generators (e.g. LIBERO) ignore this; scene-seed-keyed generators (e.g.RoboCasaGoalFrameSubgoalGenerator) use it to serve the goal frame of the exact scene being run.