anvil.recorder
anvil.recorder
Records episodes of a running workcell into MCAP files, and compresses, annotates and labels them once finished.
An episode holds the loop's state, what it wrote, where each tick's time went, each controller's trace and command stream, the events, and every camera's JPEG with every stamp it got, in one MCAP file that opens in Foxglove as it is.
Episodes go into a Session, a directory <root>/<session>/ under a data root the caller names: each start is the
session's next episode, 0001.mcap, 0002.mcap and on, so a session lists in the order it was recorded, and beside
each is written <NNNN>.foxglove-layout.json, the layout that draws its robot, cameras and joints. Every file says
which session and episode it is, and the session's task, in its anvil.episode metadata.
A Recorder records on threads of its own and reads the runtime and the cameras the way any other reader does, so
the loop never waits on it and a camera window reading the same cameras still gets every frame. RecorderConfig says
which samples of each source go in. A Compression turns a finished episode's JPEGs into H.264 in place, and can be
cancelled from another thread while it runs. annotate says things about a finished episode, in place, annotations
reads them back, and label relabels how it went.
A DatasetExport turns finished episodes into one LeRobot dataset, in its v3.0 layout, for a policy to train on. Each
dataset frame is one frame of an anchor camera, carrying each slot's position as the loop measured it as
observation.state, as it commanded it as action, and each camera's image as video, encoded by ffmpeg. Encoding is
the cost, several core-seconds per second of 480p video per camera at LeRobot's encoder settings: its ffmpeg runs at
the lowest priority, and on the robot, cancel it the moment the robot needs the CPU. It needs ffmpeg on PATH, built
with libsvtav1 for AV1 or libx264 for H.264.
Classes:
| Name | Description |
|---|---|
RecordingMode |
Which samples of each source an episode keeps. |
Outcome |
How an episode went, as whoever stopped it judged; a failed episode is kept and labeled, not deleted. |
RecorderConfig |
How an episode is recorded; |
RecorderStats |
What an episode counted, as |
Session |
A directory of episodes, |
SessionSummary |
One session under a data root, as |
Annotation |
One thing said about an episode: a |
CameraCompressed |
One camera of a compressed episode, as |
CompressStats |
What |
Compression |
A compression of a finished episode in place, every camera's JPEGs to H.264, lossy and for good. |
Recorder |
Records episodes of a running workcell, one at a time, each into the next file of a |
VideoCodec |
Codec a dataset's videos are encoded with. |
LeRobotExport |
What |
DatasetExport |
An export of recorded episodes into a new LeRobot dataset. |
Functions:
| Name | Description |
|---|---|
sessions |
Lists every session under the data root |
layout_path |
Returns where the Foxglove layout that draws |
annotations |
Reads what was said about the finished |
annotate |
Replaces the finished |
label |
Relabels how the finished |
mesh |
Returns the bytes of a mesh a composed URDF names, from the copy shipped in the package. |
RecordingMode
Bases: StrEnum
Which samples of each source an episode keeps.
Attributes:
| Name | Type | Description |
|---|---|---|
RATED |
Each source at a rate of its own, keeping the newest sample of each period. |
|
FREE |
Every sample of every source: every frame and every tick, with the ticks the recorder missed counted. |
|
SYNCED |
One observation per frame of an anchor camera, the way a LeRobot dataset lays one out. |
RATED
class-attribute
instance-attribute
RATED
Each source at a rate of its own, keeping the newest sample of each period.
By default the loop at 100 Hz, or its own rate where that is slower, and each camera at its fps.
FREE
class-attribute
instance-attribute
FREE
Every sample of every source: every frame and every tick, with the ticks the recorder missed counted.
SYNCED
class-attribute
instance-attribute
SYNCED
One observation per frame of an anchor camera, the way a LeRobot dataset lays one out.
Each other source contributes its latest sample at or before the frame's stamp.
Outcome
Bases: StrEnum
How an episode went, as whoever stopped it judged; a failed episode is kept and labeled, not deleted.
Attributes:
| Name | Type | Description |
|---|---|---|
UNLABELED |
Nobody said: stopped without a judgment, or finished when its recorder was collected. |
|
SUCCESS |
It did what its task asks. |
|
FAILURE |
It did not do what its task asks. |
UNLABELED
class-attribute
instance-attribute
UNLABELED
Nobody said: stopped without a judgment, or finished when its recorder was collected.
SUCCESS
class-attribute
instance-attribute
SUCCESS
It did what its task asks.
FAILURE
class-attribute
instance-attribute
FAILURE
It did not do what its task asks.
RecorderConfig
dataclass
RecorderConfig(
mode: RecordingMode,
rates_hz: Mapping[str, float] = {},
anchor_camera: str | None = None,
staleness_ns: int | None = None,
)
How an episode is recorded; rated, free and synced build one of each mode.
Nothing is checked here: Recorder.start refuses a config its sources cannot meet, with
anvil.InvalidArgumentError, naming what is wrong. That covers a rate for a source the recording does not have, one
that is not a positive number or is above what its source produces, an anchor_camera that is not one of the
cameras recorded, a staleness_ns of 0, and a field the mode does not take, such as anchor_camera on a rated
config.
Methods:
| Name | Description |
|---|---|
rated |
Builds a rated config: each source at |
free |
Builds a free config, which keeps every sample of every source. |
synced |
Builds a synced config: one observation per frame of the camera |
Attributes:
| Name | Type | Description |
|---|---|---|
mode |
RecordingMode
|
Which samples of each source the episode keeps. |
rates_hz |
Mapping[str, float]
|
Rated only: the rate to record each source at, in hertz, by source. |
anchor_camera |
str | None
|
Synced only: the name of the camera whose frames make the observations. |
staleness_ns |
int | None
|
Synced only: how old another source's sample may be, in nanoseconds, before the observation is dropped. |
rates_hz
class-attribute
instance-attribute
rates_hz: Mapping[str, float] = {}
Rated only: the rate to record each source at, in hertz, by source.
"robot" for the loop and otherwise a camera's name; a source left out runs at its default, as
RecordingMode.RATED says.
anchor_camera
class-attribute
instance-attribute
anchor_camera: str | None = None
Synced only: the name of the camera whose frames make the observations.
staleness_ns
class-attribute
instance-attribute
staleness_ns: int | None = None
Synced only: how old another source's sample may be, in nanoseconds, before the observation is dropped.
A dropped observation is counted. None is one anchor frame period.
rated
classmethod
rated(rates_hz: Mapping[str, float] | None = None) -> Self
Builds a rated config: each source at rates_hz, or at its default where that leaves it out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rates_hz
|
Mapping[str, float] | None
|
The rate per source, in hertz, as |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
The config, checked when a recording starts with it. |
free
classmethod
free() -> Self
Builds a free config, which keeps every sample of every source.
synced
classmethod
synced(
anchor_camera: str, staleness_ns: int | None = None
) -> Self
Builds a synced config: one observation per frame of the camera anchor_camera.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
anchor_camera
|
str
|
The name of the camera whose frames make the observations; one of the cameras recorded. |
required |
staleness_ns
|
int | None
|
How old another source's sample may be before the observation is dropped and counted;
|
None
|
Returns:
| Type | Description |
|---|---|
Self
|
The config, checked when a recording starts with it. |
RecorderStats
dataclass
RecorderStats(
messages_written: int,
ticks_missed: int,
commands_missed: int,
frames_missed: dict[str, int],
frames_received: dict[str, int],
link_sent: int,
link_answered: int,
observations_written: int,
observations_dropped: int,
stale_by_source: dict[str, int],
)
What an episode counted, as Recorder.stop returns it and the file's anvil.stats metadata keeps it.
Attributes:
| Name | Type | Description |
|---|---|---|
messages_written |
int
|
Messages in the file, on every channel; 0 until the file is finished. |
ticks_missed |
int
|
Ticks the loop ran that the recorder never saw: gaps in the tick counter. |
commands_missed |
int
|
Commands the loop picked up that the recorder never saw: gaps in their sequence numbers. |
frames_missed |
dict[str, int]
|
Frames the recorder never got, by camera name. |
frames_received |
dict[str, int]
|
Frames the recorder got, by camera name. |
link_sent |
int
|
Frames the hw interface sent the drives over the episode, every slot's added up. |
link_answered |
int
|
Replies the drives sent back to |
observations_written |
int
|
Observations written, when synced; 0 otherwise. |
observations_dropped |
int
|
Observations dropped because some source had no sample recent enough, when synced; 0 otherwise. |
stale_by_source |
dict[str, int]
|
The dropped observations each source was too stale for, by source. |
messages_written
instance-attribute
messages_written: int
Messages in the file, on every channel; 0 until the file is finished.
ticks_missed
instance-attribute
ticks_missed: int
Ticks the loop ran that the recorder never saw: gaps in the tick counter.
commands_missed
instance-attribute
commands_missed: int
Commands the loop picked up that the recorder never saw: gaps in their sequence numbers.
frames_missed
instance-attribute
frames_missed: dict[str, int]
Frames the recorder never got, by camera name.
Gaps in the driver's sequence, whether the driver dropped them or the recorder read too slowly.
frames_received
instance-attribute
frames_received: dict[str, int]
Frames the recorder got, by camera name.
link_sent
instance-attribute
link_sent: int
Frames the hw interface sent the drives over the episode, every slot's added up.
link_answered
instance-attribute
link_answered: int
Replies the drives sent back to link_sent's frames.
observations_written
instance-attribute
observations_written: int
Observations written, when synced; 0 otherwise.
observations_dropped
instance-attribute
observations_dropped: int
Observations dropped because some source had no sample recent enough, when synced; 0 otherwise.
stale_by_source
instance-attribute
stale_by_source: dict[str, int]
The dropped observations each source was too stale for, by source.
One dropped observation counts against every source that was.
Session
Session(
root: Path,
name: str | None = None,
task: str | None = None,
operator: str | None = None,
)
A directory of episodes, <root>/<name>/, created or reopened when this is constructed.
Hand one to Recorder.start to record its next episode. The directory and its episodes outlive this object.
Opens the session name under the data root root, creating either directory as needed.
A session that exists is reused, its episodes continuing from the next free index. task and operator are
kept in the session's session.json and written into every episode recorded in it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The data root the session's directory sits under; nothing guesses it. |
required |
name
|
str | None
|
The session's directory name: not empty, starting with no |
None
|
task
|
str | None
|
What every episode in the session is of, e.g. "pick up the red cube"; |
None
|
operator
|
str | None
|
Who records the episodes; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
OperationError
|
|
Methods:
| Name | Description |
|---|---|
discard_last_episode |
Deletes the newest episode and the layout beside it, so the next episode recorded takes its index. |
remove |
Deletes the session, which holds no episode: its |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The session's name: its directory's under the data root. |
path |
The session's directory, |
|
task |
str | None
|
What every episode in the session is of; |
operator |
str | None
|
Who records the session's episodes; |
name
instance-attribute
name: str
The session's name: its directory's under the data root.
path
instance-attribute
path
The session's directory, <root>/<name>.
task
instance-attribute
task: str | None
What every episode in the session is of; None where it was never said.
operator
instance-attribute
operator: str | None
Who records the session's episodes; None where it was never said.
discard_last_episode
discard_last_episode() -> str | None
Deletes the newest episode and the layout beside it, so the next episode recorded takes its index.
Returns:
| Type | Description |
|---|---|
str | None
|
The deleted episode's name, e.g. |
Raises:
| Type | Description |
|---|---|
BusyError
|
The newest episode is still being recorded, by this process or another; stop that recording first. |
OperationError
|
The session's directory cannot be read, or the episode or its layout cannot be deleted. |
remove
remove() -> None
Deletes the session, which holds no episode: its session.json and its directory.
Raises:
| Type | Description |
|---|---|
OperationError
|
The session still holds an episode, or a file that is not its own, or its directory cannot be read or deleted. |
SessionSummary
dataclass
SessionSummary(
name: str,
path: Path,
task: str | None,
operator: str | None,
episodes: int,
modified_at: datetime,
)
One session under a data root, as sessions lists it.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The session's name: its directory's under the data root. |
path |
Path
|
The session's directory, |
task |
str | None
|
What every episode in the session is of; |
operator |
str | None
|
Who records the session's episodes; |
episodes |
int
|
How many episodes the session holds. |
modified_at |
datetime
|
When the session's newest episode was last written, else when its directory was, in UTC. |
name
instance-attribute
name: str
The session's name: its directory's under the data root.
path
instance-attribute
path: Path
The session's directory, <root>/<name>.
task
instance-attribute
task: str | None
What every episode in the session is of; None where it was never said.
operator
instance-attribute
operator: str | None
Who records the session's episodes; None where it was never said.
episodes
instance-attribute
episodes: int
How many episodes the session holds.
modified_at
instance-attribute
modified_at: datetime
When the session's newest episode was last written, else when its directory was, in UTC.
Annotation
dataclass
Annotation(
key: str, value: str, log_time_ns: int | None = None
)
One thing said about an episode: a key and its value, on the whole episode or pinned to one moment of it.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
What is said, e.g. |
value |
str
|
What it is, e.g. |
log_time_ns |
int | None
|
The moment it is pinned to, a log time on the episode's timeline, in nanoseconds on |
key
instance-attribute
key: str
What is said, e.g. "grasp"; never empty, which annotate refuses.
value
instance-attribute
value: str
What it is, e.g. "slipped"; may be empty.
log_time_ns
class-attribute
instance-attribute
log_time_ns: int | None = None
The moment it is pinned to, a log time on the episode's timeline, in nanoseconds on CLOCK_MONOTONIC.
None for the whole episode.
CameraCompressed
dataclass
CameraCompressed(
frames: int,
gop_frames: int,
jpeg_bytes: int,
h264_bytes: int,
)
One camera of a compressed episode, as CompressStats.cameras holds it.
The file's anvil.compression metadata keeps the same.
Attributes:
| Name | Type | Description |
|---|---|---|
frames |
int
|
The camera's frames, each a JPEG before and one H.264 frame after. |
gop_frames |
int
|
Frames from one keyframe to the next: about a second of them at the rate the episode recorded the camera at. |
jpeg_bytes |
int
|
The camera's JPEGs' size before, added up, in bytes. |
h264_bytes |
int
|
The camera's H.264 frames' size after, added up, in bytes. |
frames
instance-attribute
frames: int
The camera's frames, each a JPEG before and one H.264 frame after.
gop_frames
instance-attribute
gop_frames: int
Frames from one keyframe to the next: about a second of them at the rate the episode recorded the camera at.
jpeg_bytes
instance-attribute
jpeg_bytes: int
The camera's JPEGs' size before, added up, in bytes.
h264_bytes
instance-attribute
h264_bytes: int
The camera's H.264 frames' size after, added up, in bytes.
CompressStats
dataclass
CompressStats(
source_bytes: int,
compressed_bytes: int,
cameras: dict[str, CameraCompressed],
)
What Compression.run did: the file's size before and after, and each camera's.
Attributes:
| Name | Type | Description |
|---|---|---|
source_bytes |
int
|
The episode's size before, in bytes. |
compressed_bytes |
int
|
The episode's size after, in bytes. |
cameras |
dict[str, CameraCompressed]
|
Each camera compressed, by name. |
source_bytes
instance-attribute
source_bytes: int
The episode's size before, in bytes.
compressed_bytes
instance-attribute
compressed_bytes: int
The episode's size after, in bytes.
Compression
Compression(
path: Path,
crf: int | None = None,
preset: str | None = None,
)
A compression of a finished episode in place, every camera's JPEGs to H.264, lossy and for good.
run does it, blocking, and cancel, from any other thread, stops it. Nothing happens to the file until run.
Running it needs ffmpeg on PATH.
Prepares the compression of the finished episode at path; nothing is read until run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The episode's file. |
required |
crf
|
int | None
|
x264's constant rate factor, 0 to 51, lower keeping more; |
None
|
preset
|
str | None
|
x264's preset, |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
run |
Compresses the episode, blocking until done; the compressed file replaces it once written and read back. |
cancel |
Stops a |
Attributes:
| Name | Type | Description |
|---|---|---|
path |
The episode's file, as given. |
path
instance-attribute
path
The episode's file, as given.
run
run() -> CompressStats | None
Compresses the episode, blocking until done; the compressed file replaces it once written and read back.
It runs ffmpeg, one process per camera, and takes seconds to minutes. A failure or a cancel leaves the
episode as it was. Never run it on an episode being recorded: one with no summary yet is refused, but only the
caller knows whether a recording is about to finish into it.
Returns:
| Type | Description |
|---|---|
CompressStats | None
|
What the compression did; |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
There is no file at |
OperationError
|
|
cancel
cancel() -> None
Stops a run under way on another thread promptly, killing its encoders; a run after it does nothing.
A run that has already replaced the file by then finishes as if it was not cancelled.
Recorder
Recorder(
workcell: WorkcellConfig,
runtime: Runtime,
cameras: Mapping[str, Camera] | None = None,
config: RecorderConfig | None = None,
readiness: Report | None = None,
)
Records episodes of a running workcell, one at a time, each into the next file of a Session.
start begins an episode and stop finishes it and returns what it counted; a recorder then records the next
one the same way. Each episode reads the runtime and the cameras through readers of its own, on threads of its
own, so the loop never waits on it. An episode's file is never another's: two recorders starting in one session at
once get the next index each. An episode still recording when the recorder is garbage collected is finished then,
labeled Outcome.UNLABELED.
event, log, failed and stats may be called from any thread, a stop on another one included.
Holds what to record; nothing is checked or opened until start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workcell
|
WorkcellConfig
|
The workcell the runtime runs, written into every episode. |
required |
runtime
|
Runtime
|
The |
required |
cameras
|
Mapping[str, Camera] | None
|
The cameras to record, by the names |
None
|
config
|
RecorderConfig | None
|
How to record; |
None
|
readiness
|
Report | None
|
The |
None
|
Methods:
| Name | Description |
|---|---|
recording |
Returns whether an episode is being recorded: from a |
start |
Creates |
event |
Writes an event of the caller's into the episode being recorded, stamped now. |
annotate |
Pins |
log |
Writes |
failed |
Returns why the episode being recorded stopped being written before |
stats |
Returns what the episode being recorded has counted so far, as of a moment ago. |
stop |
Stops reading, writes what was read and finishes the file, blocking until it is written. |
Attributes:
| Name | Type | Description |
|---|---|---|
workcell |
The workcell recorded, as given. |
|
runtime |
The runtime recorded, as given. |
|
cameras |
The cameras recorded, by name. |
|
config |
How each episode is recorded. |
|
readiness |
The readiness report each episode records the hardware of; |
|
path |
Path | None
|
The file of the episode being recorded, |
workcell
instance-attribute
workcell
The workcell recorded, as given.
runtime
instance-attribute
runtime
The runtime recorded, as given.
cameras
instance-attribute
cameras
The cameras recorded, by name.
config
instance-attribute
config
How each episode is recorded.
readiness
instance-attribute
readiness
The readiness report each episode records the hardware of; None for none.
path
instance-attribute
path: Path | None
The file of the episode being recorded, <session>/<NNNN>.mcap; None while none is.
recording
recording() -> bool
Returns whether an episode is being recorded: from a start that returned until its stop is called.
start
start(session: Session) -> None
Creates session's next episode and its layout, and records into it until stop; path says where.
Recording runs on threads of its own; this returns once it has started. A start that fails leaves the recorder idle, to start again; one that fails writing the episode's file removes it and its layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
The session to record the episode into; it stays the caller's. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
NotFoundError
|
The session's directory is not there. |
OperationError
|
An episode is being recorded already; the runtime or a camera is closed; or the episode's file or layout cannot be created or written. |
event
event(
source: str,
kind: str,
fields: Mapping[str, str] | None = None,
) -> None
Writes an event of the caller's into the episode being recorded, stamped now.
An event is something the loop cannot know, such as who drives the arms, written to the episode's /events
channel with its fields in key order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
Who says it, e.g. |
required |
kind
|
str
|
What happened, dotted, e.g. |
required |
fields
|
Mapping[str, str] | None
|
What it says, strings by name, e.g. |
None
|
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
A field's value is not a string. |
OperationError
|
Nothing is being recorded, a |
annotate
annotate(key: str, value: str) -> None
Pins key and its value to now in the episode being recorded, as one of its annotations.
The episode holds them once stop finishes it, in the order they were pinned, each at the log time of the call,
and annotations reads them back as it does the ones annotate writes into a finished episode, which replaces
them.
Each is also written into /events as it is pinned, an annotation event from recorder with key and
value fields, so it shows on the episode's timeline and is kept by an episode that never stops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
What is said, e.g. |
required |
value
|
str
|
What it is, e.g. |
required |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
OperationError
|
Nothing is being recorded, a |
log
log(topic: str, message: object) -> None
Writes message on topic, a channel of the caller's own, into the episode being recorded, stamped now.
For what a part above the loop saw, such as a headset's raw input; it is written in every mode, as it comes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
topic
|
str
|
The channel, |
required |
message
|
object
|
The message, anything |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
OperationError
|
Nothing is being recorded, a |
failed
failed() -> str | None
Returns why the episode being recorded stopped being written before stop asked it to, once it has.
The disk filling is one reason. Nothing more goes into the episode from then, what was written before stays,
and stop raises saying the same.
Returns:
| Type | Description |
|---|---|
str | None
|
Why it stopped being written; |
Raises:
| Type | Description |
|---|---|
OperationError
|
Nothing is being recorded. |
stats
stats() -> RecorderStats
Returns what the episode being recorded has counted so far, as of a moment ago.
messages_written stays 0 until stop finishes the file.
Raises:
| Type | Description |
|---|---|
OperationError
|
Nothing is being recorded. |
stop
stop(
outcome: Outcome = UNLABELED, note: str | None = None
) -> RecorderStats
Stops reading, writes what was read and finishes the file, blocking until it is written.
How the episode went goes in the file's anvil.outcome metadata, and what it counted in its anvil.stats.
The recorder is idle afterwards, whatever this raises, ready to start the next episode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
outcome
|
Outcome
|
How the episode went; |
UNLABELED
|
note
|
str | None
|
Why, in the operator's words; |
None
|
Returns:
| Type | Description |
|---|---|
RecorderStats
|
What the episode counted. |
Raises:
| Type | Description |
|---|---|
OperationError
|
Nothing is being recorded, or writing the file failed, saying why; what was written before stays in it. |
VideoCodec
Bases: StrEnum
Codec a dataset's videos are encoded with.
Attributes:
| Name | Type | Description |
|---|---|---|
AV1 |
AV1 through SVT-AV1, LeRobot's default: small files, slow to encode. |
|
H264 |
H.264 through x264: larger files, several times faster to encode. |
AV1
class-attribute
instance-attribute
AV1
AV1 through SVT-AV1, LeRobot's default: small files, slow to encode.
H264
class-attribute
instance-attribute
H264
H.264 through x264: larger files, several times faster to encode.
LeRobotExport
dataclass
LeRobotExport(
path: Path,
num_episodes: int,
num_frames: int,
fps: int,
cameras: list[str],
tasks: list[str],
)
What DatasetExport.run wrote.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Path
|
Dataset's root directory, what LeRobot's |
num_episodes |
int
|
Number of episodes written, one per episode given. |
num_frames |
int
|
Number of frames written across every episode. |
fps |
int
|
Dataset's frame rate, in frames per second: the anchor camera's, as its episodes recorded it. |
cameras |
list[str]
|
Each camera's name, in feature order; its video is the feature |
tasks |
list[str]
|
Each distinct task, at its task index. |
path
instance-attribute
path: Path
Dataset's root directory, what LeRobot's LeRobotDataset(root=...) is given.
num_episodes
instance-attribute
num_episodes: int
Number of episodes written, one per episode given.
num_frames
instance-attribute
num_frames: int
Number of frames written across every episode.
fps
instance-attribute
fps: int
Dataset's frame rate, in frames per second: the anchor camera's, as its episodes recorded it.
cameras
instance-attribute
cameras: list[str]
Each camera's name, in feature order; its video is the feature observation.images.<camera>.
tasks
instance-attribute
tasks: list[str]
Each distinct task, at its task index.
DatasetExport
DatasetExport(
episodes: Sequence[Path],
out_dir: Path,
anchor_camera: str | None = None,
codec: VideoCodec | None = None,
fallback_task: str | None = None,
)
An export of recorded episodes into a new LeRobot dataset.
run does it, blocking for minutes while it encodes video, and cancel, from any other thread, stops it. Nothing
is read or written until run. Running it needs ffmpeg on PATH.
Prepares the export of episodes into out_dir; nothing is read until run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
episodes
|
Sequence[Path]
|
Paths of the episodes, in the order the dataset numbers them. |
required |
out_dir
|
Path
|
Directory to write the dataset into; it must not exist. Its missing parent directories are created, and stay on a failure. |
required |
anchor_camera
|
str | None
|
Name of the camera whose frames the dataset's are, and whose recorded rate is the dataset's;
it must have been recorded at a whole number of frames per second. |
None
|
codec
|
VideoCodec | None
|
Codec to encode every video with; |
None
|
fallback_task
|
str | None
|
Task of an episode whose session named none ( |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
Methods:
| Name | Description |
|---|---|
run |
Writes the dataset, blocking until done; a failure or a cancel removes |
cancel |
Stops a |
Attributes:
| Name | Type | Description |
|---|---|---|
out_dir |
The directory the dataset is written into, as given. |
out_dir
instance-attribute
out_dir
The directory the dataset is written into, as given.
run
run() -> LeRobotExport | None
Writes the dataset, blocking until done; a failure or a cancel removes out_dir again.
The episodes are MCAP files as the recorder writes them, compressed or not, in any mix, and must be one
workcell's: episodes whose workcell, slots, cameras or frame rate differ are refused, naming the first
difference. There is one dataset frame per period of the anchor camera's recorded rate, each the anchor's frame
nearest it and every other source's latest sample at or before that frame, so a camera that delivers fewer
frames than its rate repeats one rather than playing back fast. Frames before every source has a sample are left
out, and so is any frame where the loop commanded no position, as when it holds the arms limp while idle, so
LeRobotExport.num_frames can be far fewer than the frames recorded. Each camera is encoded on a thread of its
own, by an ffmpeg at the lowest priority. out_dir is created before any episode is read, so one already there
is refused at once.
Returns:
| Type | Description |
|---|---|
LeRobotExport | None
|
What was written, with |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
|
NotFoundError
|
An episode is not there, or ffmpeg is not on |
OperationError
|
An episode cannot be read, holds nothing yet, as one still being recorded does, or
differs from the first in workcell, slots, cameras or frame rate; |
cancel
cancel() -> None
Stops a run under way on another thread at the next record it reads, killing its ffmpeg.
A run after it does nothing.
sessions
sessions(root: Path) -> list[SessionSummary]
Lists every session under the data root root, the most recently written first.
Every directory under root whose name a session could have is a session; anything else there, a hidden directory
among them, is passed over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The data root. |
required |
Returns:
| Type | Description |
|---|---|
list[SessionSummary]
|
A summary of each session, the most recently written first; empty for a root that does not exist yet. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
A session's |
OperationError
|
|
layout_path
layout_path(episode: Path) -> Path
Returns where the Foxglove layout that draws episode is written, beside it; whether either exists is not asked.
Foxglove opens the episode with this layout to draw its robot, cameras and joints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
episode
|
Path
|
The episode's file, |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The layout's path, |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
|
annotations
annotations(episode: Path) -> list[Annotation]
Reads what was said about the finished episode: its newest annotations, as annotate last wrote them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
episode
|
Path
|
The episode's file. |
required |
Returns:
| Type | Description |
|---|---|
list[Annotation]
|
The annotations; empty where nothing was said. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
The file is not a finished episode, or its annotations cannot be read. |
NotFoundError
|
There is no file at |
OperationError
|
The file cannot be opened. |
annotate
annotate(
episode: Path, annotations: Sequence[Annotation]
) -> None
Replaces the finished episode's annotations with annotations, in place, keeping the ones before as history.
It appends to the file, so it takes as long on an episode of any size. Never call it on an episode being recorded
or compressed: one with no summary yet is refused, but only the caller knows whether a recording or a compression
is about to finish into it. A failure leaves the file as it was, except one partway through writing, which leaves
its messages whole and its summary cut; mcap recover rebuilds it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
episode
|
Path
|
The episode's file. |
required |
annotations
|
Sequence[Annotation]
|
The annotations it holds from now on; empty clears them. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
An annotation has an empty key, or the file is not a finished episode, one still being recorded included. |
NotFoundError
|
There is no file at |
OperationError
|
The file cannot be read or written. |
label
label(
episode: Path, outcome: Outcome, note: str | None = None
) -> None
Relabels how the finished episode went, in place, keeping the labels before as history.
The new label is a new anvil.outcome record, the one Recorder.stop wrote and any since kept behind it. As with
annotate, it takes as long on an episode of any size, and is never for an episode being recorded or compressed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
episode
|
Path
|
The episode's file. |
required |
outcome
|
Outcome
|
How it went. |
required |
note
|
str | None
|
Why, in the operator's words; |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
InvalidArgumentError
|
The file is not a finished episode, one still being recorded included. |
NotFoundError
|
There is no file at |
OperationError
|
The file cannot be read or written. |
mesh
mesh(filename: str) -> bytes
Returns the bytes of a mesh a composed URDF names, from the copy shipped in the package.
So drawing the robot, or carrying a copy of it off the box, needs no description tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
The mesh's filename as a composed URDF gives it, its path in |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The mesh file's contents. |
Raises:
| Type | Description |
|---|---|
InvalidArgumentError
|
No shipped URDF names a mesh |