Datastream#
The Datastream is the single read interface through which the data generator — and
every other consumer, such as motion planners and the annotation tool — observes the world.
It composes four things behind one object:
the live environment,
the task descriptor (subtasks, signals, constraints, generation policy),
the embodiment adapter (pose reads and pose ↔ action transforms),
the source demonstration pool (the annotated demonstrations to generate from).
See datastream.py for the full API.
Why a Read Facade?#
The generation machinery should not know the simulator or the robot directly. By funneling
every read through one object, the generator stays free of isaaclab imports, motion
planners stay simulator-agnostic, and there is exactly one place to look when a value seems
wrong. Writes deliberately stay out: the Datastream never steps, resets, or records.
Construction#
This is how the generation entry point composes one (from scripts/generate_dataset.py):
from autodata_interfaces.datastream import Datastream
from autodata_interfaces.embodiments import embodiment_adapter_from_yaml
from autodata_interfaces.tasks.task_descriptor import TaskDescriptor
task_descriptor = TaskDescriptor.from_yaml("franka_cube_stack.yaml")
embodiment_adapter = embodiment_adapter_from_yaml("franka_ik_rel.yaml")
datastream = Datastream(
env=env, # the live, already-created env
task_descriptor=task_descriptor,
embodiment_adapter=embodiment_adapter,
source_dataset_path="annotated_dataset.hdf5",
uses_start_signals=False, # True for SkillGen datasets
)
The constructor is where the pieces are checked against each other, so mismatches surface immediately instead of mid-generation:
The task descriptor and embodiment adapter are bound to the same env as the Datastream (both are bound automatically if not already).
Both must declare the same end-effector names — a task descriptor written for
frankacannot run on an embodiment exposingleft/right.Exactly one source of demonstrations is given:
source_dataset_path(an HDF5 file the Datastream loads into a pool) or a pre-builtsource_pool.uses_start_signalstells the pool how to parse subtask boundaries: from start and termination signals (SkillGen) or from termination signals alone. The CLI derives it from the chosen algorithm.
Frames and Conventions#
Every consumer of the Datastream sees the same conventions; when a pose looks wrong, check against this list first:
Poses are 4×4 homogeneous matrices (
torch.Tensor), on the environment’s compute device (datastream.device).Object and robot-root poses are env-relative: each environment’s origin is subtracted, so poses are comparable across parallel envs and match the frame recorded in datasets. Motion planners exchange poses with the framework in this frame too.
Quaternions are (x, y, z, w) wherever they appear — see the conventions note in Embodiments.
Reads are batched: methods take
env_ids(a sequence of env indices) and return tensors shaped(len(env_ids), ...); passingNonereads all envs.
What It Exposes#
Query group |
Representative methods |
|---|---|
Task semantics |
|
Embodiment |
|
Live scene reads |
|
Collision-world source |
|
Source demonstration pool |
|
The Source Demonstration Pool#
The pool (DataGenInfoPool) holds the annotated source demonstrations in the form the
generator consumes. For each episode it keeps one per-step record with these fields:
Field |
Shape (per entry) |
Meaning |
|---|---|---|
|
|
Recorded end-effector poses, step by step. |
|
|
The controller target poses recovered from the recorded actions (via the embodiment adapter’s inverse transform). |
|
|
Recorded object poses; subtask segments are re-expressed relative to their reference object using these. |
|
|
Boolean per-step completion flags, written by annotation. |
|
|
Boolean per-step start flags — present only in SkillGen datasets. |
|
|
The non-pose action channels (grippers, hands, extra channels), replayed verbatim. |
These records are produced by the annotation tool, which replays each episode and records
them under obs/datagen_info in the output HDF5 — see the
annotation step.
Alongside the records, the pool stores each episode’s subtask boundaries — per
end-effector, a (start, end) index pair per subtask. Boundaries are derived from the
signals: a termination signal’s rising edge ends its subtask (the final subtask ends with the
trajectory), and with start signals enabled (SkillGen), each subtask’s start comes from its
own start-signal edge instead of the previous subtask’s end. At load time the pool checks
that the boundaries stay valid under the descriptor’s worst-case randomization offsets, so a
mis-annotated episode is rejected immediately with a named reason.
The pool can also grow during generation (add_episode): an asyncio lock guards
concurrent growth across the per-env generation tasks, and it is skipped entirely for the
common case of a static, fully pre-loaded pool.
What It Deliberately Does Not Wrap#
Controller-side operations stay on the env and are reached through the get_env() escape
hatch:
env.step/env.reset— stepping is the simulator loop’s job.env.recorder_manager— success flags and episode export are controller-side.The async action/reset queues used by multi-env generation.
Keeping mutation off the facade keeps the read surface honest: any code that changes the
world is easy to find, because it must go through get_env().
Who Reads It#
The data generator reads all task, embodiment, and scene state through it — it never imports the simulator.
Motion planners (SkillGen) read their collision geometry, obstacle poses, robot root pose, and joint start state through it — see Motion Planners.
The annotation tool (
scripts/annotate_demos.py) records per-step datagen info through it, and in--automode samplesget_subtask_term_signalseach replay step to find subtask boundaries without a human in the loop.