OpenArm Dataset API
API Reference for OpenArm Dataset.
The examples on this page use tests/fixture/dataset_0.4.0_qpos in the openarm_dataset repository.
Table of Contents
DatasetCamera(camera frames)FrameSampleSamplerMetadataEpisodeEquipment/Embodiments/Perceptions/LeaderEmbodiment,OpenArm,OpenArmCellLifterFrequencies- LeRobot and GR00T conversion
- Command-line tools
Dataset
Top-level entry point. A Dataset is a thin handle to a directory tree on
disk; nothing is read eagerly except metadata.yaml.
Dataset.__init__
Dataset(
path: str | os.PathLike,
meta: Metadata | None = None,
camera_names: list[str] | None = None,
kinematics: dict | None = None,
)
| Parameter | Description |
|---|---|
path | Dataset root (the directory containing metadata.yaml and episodes/). |
meta | Optional pre-loaded Metadata. Defaults to Metadata(path/"metadata.yaml"). |
camera_names | Optional whitelist of cameras to expose. Defaults to all in metadata. |
kinematics | Optional mapping of arm component ("right"/"left") to a kinematics engine used for the state conversion. Built lazily if None. |
>>> import openarm_dataset
>>> ds = openarm_dataset.Dataset("tests/fixture/dataset_0.4.0_qpos")
>>> episode = ds.meta.episodes[0]
Dataset.num_episodes
Property. Number of episodes declared in metadata.
>>> ds.num_episodes
2
Dataset.camera_names
Property. List of camera stream names, in declaration order. Either the
override passed to __init__ or list(meta.equipment.perceptions.cameras).
>>> ds.camera_names
['ceiling', 'head_left', 'head_right', 'wrist_left', 'wrist_right']
Dataset.camera_format
Property. The camera storage format shared by all cameras: "dir" (a directory of JPEG files per camera) or "tar" (a tar archive per camera).
Raises ValueError if the cameras use a mix of both.
>>> ds.camera_format
'dir'
Dataset.meta
Attribute. The Metadata instance backing this dataset.
>>> ds.meta.version
'0.4.0'
>>> ds.meta.tasks
[{'description': 'Longer task description if need.', 'prompt': 'Run test.'}]
Dataset.root_path
Attribute. pathlib.Path of the dataset root.
Dataset.episode_path
Dataset.episode_path(episode: Episode | None = None) -> pathlib.Path
Returns root_path / "episodes" / episode["id"], or root_path if episode is None.
Dataset.load_obs
Dataset.load_obs(
episode: Episode,
use_unixtime: bool = False,
cutoff: float | None = None,
state: str | None = None,
) -> dict[str, pandas.DataFrame]
Loads all observation signals for one episode. Returns a dict keyed by
{embodiment}/{component}/{attribute} (or {embodiment}/{attribute} for
component-less embodiments such as the cell lifter).
The keys reflect the attributes actually recorded. For arms, state.parquet may contain any of qpos, qvel, qtorque and pose.
Embodiments that were not recorded are absent.
| Parameter | Description |
|---|---|
episode | An Episode from meta.episodes. |
use_unixtime | If True, DataFrame index is float64 Unix seconds; otherwise datetime64[ns]. |
cutoff | Cutoff frequency (Hz) of the smoothing filter. If None, falls back to the value set by set_smoothing. If 0, smoothing is disabled. |
state | If set to "qpos", "pose" or "rot6d", arm states are returned in this representation. The recorded data is converted on the fly when needed (qpos to pose via FK, pose to qpos via IK). |
Each DataFrame's columns are the embodiment's joint names. For OpenArm
qpos/qvel/qtorque that is ('joint1', …, 'joint7', 'gripper'); for pose
it is ('x', 'y', 'z', 'qw', 'qx', 'qy', 'qz', 'gripper'); for rot6d it is
('x', 'y', 'z', 'r11', 'r21', 'r31', 'r12', 'r22', 'r32', 'gripper'); for
OpenArmCellLifter it is ('elevation',).
>>> obs = ds.load_obs(episode)
>>> list(obs.keys())
['arms/right/qpos', 'arms/right/qvel', 'arms/right/qtorque',
'arms/left/qpos', 'arms/left/qvel', 'arms/left/qtorque',
'lifter/elevation']
>>> df = obs["arms/right/qpos"]
>>> df.shape
(746, 8)
>>> list(df.columns)
['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6', 'joint7', 'gripper']
>>> df.index.dtype
dtype('<M8[ns]')
>>> ds.load_action(episode, use_unixtime=True)["arms/right/qpos"].index.dtype
dtype('float64')
>>> obs["lifter/elevation"].columns.tolist()
['elevation']
>>> ds.load_obs(episode, state="pose")["arms/right/pose"].columns.tolist()
['x', 'y', 'z', 'qw', 'qx', 'qy', 'qz', 'gripper']
Dataset.load_action
Same signature and return type as load_obs, but reads from the action/
subtree. Arm actions are qpos, or pose if the dataflow recorded
end-effector targets directly.
>>> list(ds.load_action(episode).keys())
['arms/right/qpos', 'arms/left/qpos', 'lifter/elevation']
>>> pose_ds = openarm_dataset.Dataset("tests/fixture/dataset_0.4.0_pose")
>>> list(pose_ds.load_action(pose_ds.meta.episodes[0]).keys())
['arms/right/pose', 'arms/left/pose', 'lifter/elevation']
Dataset.load_cameras
Dataset.load_cameras(episode: Episode) -> dict[str, Camera]
Returns one Camera per stream in
Dataset.camera_names.
>>> cams = ds.load_cameras(episode)
>>> list(cams)
['ceiling', 'head_left', 'head_right', 'wrist_left', 'wrist_right']
Dataset.load_camera
Dataset.load_camera(name: str, episode: Episode) -> Camera
Raises KeyError if name is not in Dataset.camera_names.
>>> ceiling = ds.load_camera("ceiling", episode)
>>> ceiling.num_frames
3
Dataset.sample
Dataset.sample(
hz: float,
episode: Episode,
state: str | None = None,
) -> list[Sample]
Resamples obs/action/cameras onto a uniform grid at hz Hz, clipped to the
intersection of all streams' time spans, and returns a list of
Sample objects (one per timestep). state is passed to
load_obs and load_action.
For each modality, the first element whose timestamp is >= the sample's
timestamp is selected (via np.searchsorted).
>>> samples = ds.sample(hz=30, episode=episode)
>>> len(samples)
2
>>> samples[0]
Sample(timestamp=1772010251.6202147)
>>> samples[0].obs["arms/right/qpos"].dtype, samples[0].obs["arms/right/qpos"].shape
(dtype('float32'), (8,))
>>> samples[0].cameras["ceiling"] # cameras are Frame objects, not arrays
<openarm_dataset.camera.Frame object at 0x...>
>>> samples[0].cameras["ceiling"].load().shape # decode on demand
(600, 960, 3)
Dataset.get_embodiment_attributes / Dataset.load_embodiment_value
Dataset.get_embodiment_attributes(type_: str, episode: Episode) -> list[dict]
Dataset.load_embodiment_value(attribute: dict, use_unixtime: bool = False) -> pandas.DataFrame
Lower-level access to the recorded files. get_embodiment_attributes("obs" | "action", episode) lists the recorded attributes, each as a dict with key, embodiment, component, name and path.
load_embodiment_value loads one of them as recorded, without smoothing or state conversion.
>>> attrs = ds.get_embodiment_attributes("obs", episode)
>>> [(a["key"], a["name"]) for a in attrs][:3]
[('arms/right/qpos', 'qpos'), ('arms/right/qvel', 'qvel'), ('arms/right/qtorque', 'qtorque')]
>>> ds.load_embodiment_value(attrs[0]).shape
(746, 8)
Dataset.set_smoothing
Dataset.set_smoothing(cutoff: float | None) -> None
Sets a default cutoff frequency used by subsequent load_obs / load_action
calls when no explicit cutoff argument is passed. None or 0 disables
smoothing.
The smoothing is a zero-phase low-pass filter: a 4th-order Butterworth filter
applied forward and backward (scipy.signal.filtfilt), so it does not delay the
signal. It uses the whole episode, so it is for offline processing only. The
filter assumes 250 Hz data, and signals with 15 or fewer samples are not smoothed. With smoothing, the values are float64 instead of float32.
The LeRobot and GR00T conversions also call this with their smoothing_cutoff.
>>> ds.set_smoothing(1.0)
>>> ds.set_smoothing(None) # disable again
Dataset.validate
Dataset.validate(
on_error: Callable[[str], None] | None = None,
update_metadata: bool = False,
qpos_jump_threshold: float | None = None,
qpos_absmax: float | None = None,
min_duration: float | None = None,
) -> bool
Validates every episode. It always checks for null and NaN values in the obs and action parquet files and for invalid pose quaternions (non-finite or zero-norm). The other checks are enabled by their parameters.
| Parameter | Description |
|---|---|
on_error | Callback invoked with one human-readable error string per violation. |
update_metadata | If True, record the result as a valid flag per episode in the metadata. metadata.yaml is rewritten in place in the latest format (0.4.0), so convert older datasets with openarm-dataset-convert first. Ignored for unversioned datasets. |
qpos_jump_threshold | If set, flag qpos frame-to-frame deltas above this value (radians). |
qpos_absmax | If set, flag qpos values whose absolute value exceeds this value (radians). |
min_duration | If set, flag episodes shorter than this value (seconds). |
| returns | True if no errors were reported, False otherwise. |
>>> errors = []
>>> ds.validate(on_error=errors.append, min_duration=2.0)
False
>>> errors
['episodes/3: duration=0.81s < 2.0s']
Dataset.write
Dataset.write(
output: str | os.PathLike,
format: str | None = None,
**options,
) -> None
Persists the dataset to output. format is one of:
format | Behavior |
|---|---|
None or "openarm" | Write as the latest OpenArm format (0.4.0). Older layouts are upgraded. Options: camera_format ("dir" or "tar", default "dir") and valid_only (default False). |
"lerobot_v2.1" | Convert to the LeRobot v2.1 layout. See LeRobot and GR00T conversion for **options. |
"lerobot_v3.0" | Convert to the LeRobot v3.0 layout. Same options as "lerobot_v2.1". |
"gr00t" | Convert to the GR00T LeRobot layout (LeRobot v2.1 plus meta/modality.json). |
"robot_eval_logger" | Convert to the robot_eval_logger format. Requires the robot-eval-logger extra. |
"rrd" | Export to the Rerun .rrd format. Requires the rerun extra. |
Any other value raises ValueError("Unsupported format: …").
>>> import tempfile, os
>>> out = tempfile.mkdtemp()
>>> ds.write(out, camera_format="tar") # native openarm format
>>> sorted(os.listdir(out))
['episodes', 'metadata.yaml']
>>> sorted(os.listdir(os.path.join(out, "episodes", "0", "cameras")))
['ceiling.tar', 'head_left.tar', 'head_right.tar', 'wrist_left.tar', 'wrist_right.tar']
Camera (camera frames)
openarm_dataset.camera.Camera
A Camera is a lazy view of one camera stream. The frames are stored either
as a directory of JPEG files (cameras/<name>/) or as a single tar archive
(cameras/<name>.tar). Filenames are nanosecond timestamps, so lexicographic
order matches chronological order.
Constructor
Camera(name: str, base_path: str | os.PathLike)
base_path is the directory-style path (e.g. .../cameras/ceiling). If that
directory does not exist but .../cameras/ceiling.tar does, the camera is read
from the archive.
You almost never construct this directly; use Dataset.load_camera /
Dataset.load_cameras.
Attributes
| Attribute | Type | Description |
|---|---|---|
name | str | Stream name (e.g. "ceiling"). |
base_path | pathlib.Path | Directory-style path of the camera. It holds the JPEGs for "dir" cameras, and doesn't exist for tar-backed cameras. |
tar_path | pathlib.Path or None | Path of the tar archive for tar-backed cameras. |
format | str | "dir" or "tar". |
all_files | list[pathlib.Path] | All JPEG paths, sorted. Empty for tar-backed cameras; use frames() instead. |
Camera.num_frames
Property; the number of frames.
>>> ceiling = ds.load_camera("ceiling", episode)
>>> ceiling.num_frames
3
>>> ceiling.format
'dir'
>>> ceiling.base_path
PosixPath('tests/fixture/dataset_0.4.0_qpos/episodes/0/cameras/ceiling')
>>> ceiling.all_files[0].name
'1772010251619682157.jpeg'
Camera.get_frame
Camera.get_frame(index: int) -> Frame
Returns the Frame at index. Raises IndexError for out-of-range indices.
>>> ceiling.get_frame(0)
<openarm_dataset.camera.Frame object at 0x...>
>>> ceiling.get_frame(0).timestamp
1772010251.619682
Camera.frames
Camera.frames() -> Iterator[Frame]
Generator yielding one Frame per image in order.
>>> [f.timestamp for f in ceiling.frames()]
[1772010251.619682, 1772010251.6290832, 1772010251.6632507]
Camera.load_timestamps
Camera.load_timestamps() -> list[float]
Returns the Unix-second timestamp for every frame, in order. Timestamps are
decoded from the filenames ({ns}.jpeg).
>>> ceiling.load_timestamps()
[1772010251.619682, 1772010251.6290832, 1772010251.6632507]
Frame
openarm_dataset.camera.Frame
A lightweight handle to a single image. Decoding only happens on .load(),
.open_image() or .show().
| Attribute / method | Type / signature | Description |
|---|---|---|
path | pathlib.Path | JPEG path. For tar-backed frames, this is a synthetic <archive>/<member> path, not a real file. |
timestamp | float | Unix seconds derived from the filename stem (ns / 1e9). |
size | int | Size of the JPEG data in bytes. |
load() | () -> numpy.ndarray | Decode to an (H, W, 3) uint8 ndarray (RGB). |
open_image() | () -> PIL.Image.Image | Open the image with PIL. |
show() | () -> None | Open the image with PIL's default viewer. |
materialize(temp_dir) | (os.PathLike) -> pathlib.Path | Return a real JPEG path on disk. Tar-backed frames are extracted into temp_dir. |
__eq__ | (other) -> bool | Equal iff other.path == self.path. |
>>> f = ceiling.get_frame(0)
>>> f.path.name
'1772010251619682157.jpeg'
>>> f.timestamp
1772010251.619682
>>> img = f.load()
>>> img.shape, img.dtype
((600, 960, 3), dtype('uint8'))
Sample
openarm_dataset.sampler.Sample
A single resampled timestep. Sample implements collections.abc.Mapping
over the four fixed keys "timestamp" | "obs" | "action" | "cameras", so it
can be both attribute- and dict-accessed.
Constructor
Sample(
timestamp: float,
obs: dict[str, numpy.ndarray],
action: dict[str, numpy.ndarray],
cameras: dict[str, Frame],
)
You normally get these from Dataset.sample.
Properties
| Property | Type | Description |
|---|---|---|
timestamp | float | Unix seconds of this sample on the resampling grid. |
obs | dict[str, numpy.ndarray] | 1-D arrays (float32, or float64 with smoothing), keyed like load_obs. The arm positions follow state: joint values (qpos) for "qpos", or a Cartesian pose and the gripper (pose/rot6d) for "pose" and "rot6d". qvel and qtorque are always joint values. |
action | dict[str, numpy.ndarray] | Same shape/dtype as obs, keyed like load_action. |
cameras | dict[str, Frame] | One Frame per camera stream — not decoded. |
Per-modality vectors carry the values in the same order as the DataFrame
columns of load_obs / load_action (e.g. joint1…joint7, gripper for
OpenArm qpos). For each camera, the selected Frame is the one with the
smallest timestamp >= the sample's timestamp (via np.searchsorted).
>>> s = ds.sample(hz=30, episode=episode)[0]
>>> s
Sample(timestamp=1772010251.6202147)
>>> list(s.keys()) # Mapping interface
['timestamp', 'obs', 'action', 'cameras']
>>> s["timestamp"] == s.timestamp
np.True_
>>> s.obs["arms/right/qpos"].shape, s.obs["arms/right/qpos"].dtype
((8,), dtype('float32'))
Sampler
openarm_dataset.sampler.Sampler
The class behind Dataset.sample. It is stateless; you usually do not need
to instantiate it directly.
Sampler().sample(
dataset: Dataset,
episode: Episode,
hz: float,
state: str | None = None,
) -> Iterator[Sample]
Dataset.sample materializes the iterator into a list. Note the argument
order differs between Sampler.sample(dataset, episode, hz) and
Dataset.sample(hz, episode).
>>> from openarm_dataset.sampler import Sampler
>>> next(Sampler().sample(ds, episode=episode, hz=30)).timestamp
np.float64(1772010251.6202147)
Metadata
openarm_dataset.metadata.Metadata
Wraps a metadata.yaml file and (for unversioned datasets) an
episodes.jsonl sibling. All properties read from self.data, which is the
parsed YAML dict.
Constructor
Metadata(path: str | os.PathLike)
For legacy ("unversioned") datasets, the constructor merges the contents of
<path-dir>/episodes.jsonl into self.data["episodes"].
Properties
| Property | Type | Notes |
|---|---|---|
version | str or None | None indicates a pre-versioned dataset. |
operator | str | |
operation_type | str | Defaults to "teleop". |
location | str | |
tasks | list[dict] | Each task has prompt and description. |
episodes | list[Episode] | See Episode. |
num_episodes | int | len(self.episodes). |
equipment | Equipment | Rebuilt on each access. |
frequencies | Frequencies |
>>> ds.meta.version, ds.meta.operator, ds.meta.location
('0.4.0', 'Tester', 'Test')
>>> ds.meta.tasks
[{'description': 'Longer task description if need.', 'prompt': 'Run test.'}]
>>> [dict(e) for e in ds.meta.episodes]
[{'id': '0', 'success': False, 'task_index': 0},
{'id': '3', 'success': True, 'task_index': 0}]
>>> ds.meta.num_episodes
2
Method
Metadata.write
Metadata.write(output: str | os.PathLike, valid_only: bool = False) -> None
Writes a normalized 0.4.0 metadata.yaml to the directory output,
upgrading legacy field names (equipment_id/equipment_version →
id/version; left_wrist/right_wrist → wrist_left/wrist_right).
Creates the directory if needed. If valid_only is True, episodes marked
invalid are omitted.
>>> import tempfile
>>> outdir = tempfile.mkdtemp()
>>> ds.meta.write(outdir)
>>> sorted(__import__('os').listdir(outdir))
['metadata.yaml']
Episode
openarm_dataset.metadata.Episode
A MutableMapping view of one episode entry in the metadata. Changes are
written through to the metadata. Pass it to the Dataset loading methods.
| Key / method | Description |
|---|---|
id | Episode ID, which is also the directory name under episodes/. |
success | Whether the task succeeded. |
task_index | Index into Metadata.tasks. |
valid | Optional validity flag written by openarm-dataset-validate. |
valid() | Returns False only if the episode is marked invalid. Episodes without a valid flag are valid. |
>>> episode = ds.meta.episodes[0]
>>> episode["id"], episode["success"], episode.valid()
('0', False, True)
Equipment / Embodiments / Perceptions / Leader
Equipment
Equipment(data: dict)
| Attribute | Type | Source key in YAML |
|---|---|---|
id | str | equipment.id |
version | str | equipment.version |
embodiments | Embodiments | equipment.embodiments |
perceptions | Perceptions | equipment.perceptions |
leader | Leader | equipment.leader |
>>> eq = ds.meta.equipment
>>> eq.id, eq.version
('Test', '1.0')
Embodiments
A Mapping[str, Embodiment] keyed by embodiment name as declared in YAML.
>>> list(ds.meta.equipment.embodiments)
['arms', 'lifter']
>>> ds.meta.equipment.embodiments["arms"].id
'OpenArm'
Perceptions
| Attribute | Type | Notes |
|---|---|---|
cameras | dict[str, metadata.Camera] | One entry per declared camera. |
metadata.Camera is a tiny holder with a .name and the raw YAML dict; it
is not the same class as camera.Camera used for frames.
>>> list(ds.meta.equipment.perceptions.cameras)
['ceiling', 'head_left', 'head_right', 'wrist_left', 'wrist_right']
Leader
A Mapping[str, LeaderDevice] of the leader devices used for teleoperation
(e.g. KER), keyed by the name declared in YAML. Each LeaderDevice has id,
firmware_version and hardware_version (each None if not recorded).
>>> ker = ds.meta.equipment.leader["ker"]
>>> ker.id, ker.firmware_version, ker.hardware_version
('OpenArmKER', '1.2.3', '1.0')
Embodiment, OpenArm, OpenArmCellLifter
Embodiment is the base class; concrete subclasses are returned by
Embodiments based on the id field in YAML.
| Subclass | id | components | joints |
|---|---|---|---|
OpenArm | "OpenArm" | ("right", "left") | ("joint1", … "joint7", "gripper") |
OpenArmCellLifter | "OpenArmCellLifter" | () | ("elevation",) |
All subclasses also expose .name (the YAML key), .id, .version.
The recorded attributes (qpos, qvel, qtorque, pose) are not declared
in the metadata. They are read from the columns of each state.parquet.
Unknown ids raise ValueError.
Frequencies
Frequencies(data: dict)
A passive holder for the frequencies section of metadata.yaml. Each
attribute is just the corresponding nested dict (or {} if absent).
| Attribute | Type | YAML key |
|---|---|---|
action | dict | frequencies.action |
cameras | dict | frequencies.cameras |
obs | dict | frequencies.obs |
>>> ds.meta.frequencies.cameras
{'ceiling': 30.303030303030305, 'head_left': 30.303030303030305,
'head_right': 30.303030303030305, 'wrist_left': 30.303030303030305,
'wrist_right': 30.303030303030305}
>>> ds.meta.frequencies.action
{'arms': {'left': 250.0, 'right': 250.0}, 'lifter': 250.0}
LeRobot and GR00T conversion
openarm_dataset.lerobot_v21.to_lerobotv21, openarm_dataset.lerobot_v30.to_lerobotv30 and openarm_dataset.lerobot_v21.to_gr00t
Conversion entry points used by Dataset.write(format=..., **opts) and the
openarm-dataset-convert CLI. They require ffmpeg on PATH for video
encoding and raise RuntimeError if it is not found.
to_lerobotv21(
dataset: Dataset,
output_dir: str | pathlib.Path,
fps: int = 30,
train_split: float = 0.8,
smoothing_cutoff: float = 1.0,
success_only: bool = False,
state: str = "qpos",
valid_only: bool = False,
arm_dynamics: bool = False,
) -> None
to_lerobotv30 has the same signature. to_gr00t has the same signature
without arm_dynamics, and also writes meta/modality.json describing the
state/action layout.
| Parameter | Default | Description |
|---|---|---|
fps | 30 | Resampling rate for both states and video. Must be > 0; otherwise ValueError. |
train_split | 0.8 | Fraction of episodes assigned to train; remainder to val. Must be in [0, 1] or ValueError. |
smoothing_cutoff | 1.0 | Cutoff frequency (Hz) of the zero-phase smoothing applied to obs/action before sampling (see set_smoothing). 0 or None disables smoothing. |
success_only | False | If True, drop episodes whose success is falsy. Re-indexes episodes/tasks. |
state | "qpos" | Arm state representation of the output: "qpos", "pose" or "rot6d". Converted on the fly when the dataset recorded another representation. |
valid_only | False | If True, drop episodes marked invalid by openarm-dataset-validate. |
arm_dynamics | False | If True, also export the recorded arm joint velocity and torque as observation.velocity and observation.torque (16 values each: right arm 7 joints + gripper, left arm 7 joints + gripper). Each is exported only when every episode recorded it. |
If, after filtering, no episodes remain, raises ValueError("No episodes to write.").
The output layout of to_lerobotv21 under output_dir/:
data/chunk-XXX/episode_XXXXXX.parquetvideos/chunk-XXX/observation.images.<cam>/episode_XXXXXX.mp4meta/info.json,meta/episodes.jsonl,meta/episodes_stats.jsonl,meta/tasks.jsonl,meta/stats.json
Command-line tools
Installed via pyproject.toml's [project.scripts]:
openarm-dataset-validate
openarm-dataset-validate <input>
[--no-update-metadata]
[--qpos-jump-threshold RADIAN] # default 1.0
[--qpos-absmax RADIAN] # default 6.28
[--min-duration SECOND] # default 2.0
Validates the dataset with Dataset.validate, prints the errors to stderr and
exits with status 1 if any errors are reported. Pass none to
--qpos-jump-threshold, --qpos-absmax or --min-duration to disable the check.
By default, the result is recorded as a valid flag per episode in
metadata.yaml. The file is rewritten in the latest format (0.4.0), so convert
older datasets with openarm-dataset-convert first. Pass --no-update-metadata
to keep metadata.yaml unchanged.
openarm-dataset-convert
openarm-dataset-convert <input> <output>
[--format {openarm,lerobot_v2.1,lerobot_v3.0,gr00t,robot_eval_logger}]
[--fps INT] # default 30
[--smoothing-cutoff FLOAT] # default 1.0, 0 disables smoothing
[--train-split FLOAT] # default 0.8
[--success-only]
[--valid-only]
[--state {qpos,pose,rot6d}] # default qpos
[--arm-dynamics]
[--camera-format {dir,tar}] # default dir
[--gripper-component COMPONENT]
[--eval-id ID] [--eval-name NAME]
[--location LOCATION] [--evaluator-name NAME]
| Option | Applies to |
|---|---|
--fps, --success-only | lerobot_v2.1, lerobot_v3.0, gr00t, robot_eval_logger |
--smoothing-cutoff, --train-split, --state | lerobot_v2.1, lerobot_v3.0, gr00t |
--arm-dynamics | lerobot_v2.1, lerobot_v3.0 |
--valid-only | All formats |
--camera-format | openarm. tar stores each camera as a single tar archive, which is recommended for the Hugging Face Hub file-count limits. |
--gripper-component, --eval-id, --eval-name, --location, --evaluator-name | robot_eval_logger |
Implementation: openarm_dataset.convert.main.
openarm-dataset-merge
openarm-dataset-merge -o OUTPUT [--symlink] <inputs>...
Merges multiple OpenArm datasets into one. With --symlink, episode data is
symlinked instead of copied.
The inputs must have exactly the same version, equipment and frequencies
in metadata.yaml, and unversioned datasets are not supported. Convert older
datasets with openarm-dataset-convert --format openarm first. Tasks with the
same prompt are merged into one task.
openarm-dataset-repair
openarm-dataset-repair [-o OUTPUT] <input>
Repairs a dataset by filling isolated single-frame gaps with the average of the
neighboring frames. Repairs in place if -o is omitted.
openarm-dataset-upload
openarm-dataset-upload --repo-id REPO_ID [--private] [--licence LICENCE] [--large-folder] <input>
Uploads an OpenArm dataset to the Hugging Face Hub. --large-folder uses a
resumable, multi-threaded upload, which is recommended for datasets larger than
1 GB. Cameras stored as JPEG directories are repacked in place into .tar
archives before uploading.