Skip to main content
Version: 2.0

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​


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,
)
ParameterDescription
pathDataset root (the directory containing metadata.yaml and episodes/).
metaOptional pre-loaded Metadata. Defaults to Metadata(path/"metadata.yaml").
camera_namesOptional whitelist of cameras to expose. Defaults to all in metadata.
kinematicsOptional 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.

ParameterDescription
episodeAn Episode from meta.episodes.
use_unixtimeIf True, DataFrame index is float64 Unix seconds; otherwise datetime64[ns].
cutoffCutoff frequency (Hz) of the smoothing filter. If None, falls back to the value set by set_smoothing. If 0, smoothing is disabled.
stateIf 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.

ParameterDescription
on_errorCallback invoked with one human-readable error string per violation.
update_metadataIf 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_thresholdIf set, flag qpos frame-to-frame deltas above this value (radians).
qpos_absmaxIf set, flag qpos values whose absolute value exceeds this value (radians).
min_durationIf set, flag episodes shorter than this value (seconds).
returnsTrue 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:

formatBehavior
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​

AttributeTypeDescription
namestrStream name (e.g. "ceiling").
base_pathpathlib.PathDirectory-style path of the camera. It holds the JPEGs for "dir" cameras, and doesn't exist for tar-backed cameras.
tar_pathpathlib.Path or NonePath of the tar archive for tar-backed cameras.
formatstr"dir" or "tar".
all_fileslist[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 / methodType / signatureDescription
pathpathlib.PathJPEG path. For tar-backed frames, this is a synthetic <archive>/<member> path, not a real file.
timestampfloatUnix seconds derived from the filename stem (ns / 1e9).
sizeintSize of the JPEG data in bytes.
load()() -> numpy.ndarrayDecode to an (H, W, 3) uint8 ndarray (RGB).
open_image()() -> PIL.Image.ImageOpen the image with PIL.
show()() -> NoneOpen the image with PIL's default viewer.
materialize(temp_dir)(os.PathLike) -> pathlib.PathReturn a real JPEG path on disk. Tar-backed frames are extracted into temp_dir.
__eq__(other) -> boolEqual 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​

PropertyTypeDescription
timestampfloatUnix seconds of this sample on the resampling grid.
obsdict[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.
actiondict[str, numpy.ndarray]Same shape/dtype as obs, keyed like load_action.
camerasdict[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​

PropertyTypeNotes
versionstr or NoneNone indicates a pre-versioned dataset.
operatorstr
operation_typestrDefaults to "teleop".
locationstr
taskslist[dict]Each task has prompt and description.
episodeslist[Episode]See Episode.
num_episodesintlen(self.episodes).
equipmentEquipmentRebuilt on each access.
frequenciesFrequencies
>>> 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 / methodDescription
idEpisode ID, which is also the directory name under episodes/.
successWhether the task succeeded.
task_indexIndex into Metadata.tasks.
validOptional 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)
AttributeTypeSource key in YAML
idstrequipment.id
versionstrequipment.version
embodimentsEmbodimentsequipment.embodiments
perceptionsPerceptionsequipment.perceptions
leaderLeaderequipment.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​

AttributeTypeNotes
camerasdict[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.

Subclassidcomponentsjoints
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).

AttributeTypeYAML key
actiondictfrequencies.action
camerasdictfrequencies.cameras
obsdictfrequencies.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.

ParameterDefaultDescription
fps30Resampling rate for both states and video. Must be > 0; otherwise ValueError.
train_split0.8Fraction of episodes assigned to train; remainder to val. Must be in [0, 1] or ValueError.
smoothing_cutoff1.0Cutoff frequency (Hz) of the zero-phase smoothing applied to obs/action before sampling (see set_smoothing). 0 or None disables smoothing.
success_onlyFalseIf 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_onlyFalseIf True, drop episodes marked invalid by openarm-dataset-validate.
arm_dynamicsFalseIf 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.parquet
  • videos/chunk-XXX/observation.images.<cam>/episode_XXXXXX.mp4
  • meta/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]
OptionApplies to
--fps, --success-onlylerobot_v2.1, lerobot_v3.0, gr00t, robot_eval_logger
--smoothing-cutoff, --train-split, --statelerobot_v2.1, lerobot_v3.0, gr00t
--arm-dynamicslerobot_v2.1, lerobot_v3.0
--valid-onlyAll formats
--camera-formatopenarm. 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-namerobot_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.