The AY-Robots public dataset directory showing LeRobot datasets recorded on SO-100 class arms
DatasetsOpen X-EmbodimentDROIDSO-100LeRobot

Using DROID, BridgeData V2 and Open X on an SO-100

AY-Robots ResearchAugust 23, 202618 min read

DROID, BridgeData V2 and Open X-Embodiment convert to 7-D end-effector actions on 6 and 7-DoF arms. An SO-100 takes 6 joint positions. What transfers, what does not, what to do instead.

The short version

  • The LeRobot builds of all three share one convention: a 7-D end-effector action [x, y, z, roll, pitch, yaw, gripper] and an 8-D state. An SO-100 takes six absolute joint positions.
  • That 7-D vector is the converter's artefact: DROID's own RLDS action field is 6 joint velocities plus a gripper position, with the Cartesian view in action_dict.
  • Four clocks: DROID 15 fps, BridgeData V2 5 fps, the google_robot slice 3 fps, an SO-100 recording at 30 fps.
  • You cannot merge them with your own data. validate_all_metadata raises on the first of fps, robot_type or features that differs, and all three differ.
  • What transfers is pretrained weights, not episodes. Open-source data is 9.1 percent of pi0's pre-training mixture.
  • Their cheapest real use is a test fixture: a known-good 2 GB, 100-episode DROID sample that proves your pipeline before you record for a weekend.

There is a million-trajectory public dataset on a Google Cloud bucket and a SO-100 on the desk that cost 110 to 150 EUR in parts. Why can the first not teach the second? It partly can, but almost none of the transfer happens where people expect, and the part that looks easiest does not work at all.

What follows: what is inside DROID, BridgeData V2 and Open X-Embodiment, where each collides with a low-cost 5-DoF arm, and what to do instead. Every number below came from the paper, dataset card or source file it belongs to.

What the three datasets actually contain

DROIDBridgeData V2Open X-Embodiment
RobotFranka Panda, 7 DoF, Robotiq 2F-85WidowX 250, 6 DoF, ~4,000 USD rig22 embodiments, 60 datasets, 34 labs
Scale76k trajectories, 350 hours60,096 trajectories1M+ trajectories, 527 skills
Diversity564 scenes, 86 tasks, 50 collectors24 environments, 13 skills160,266 tasks, 21 institutions
Compositionall teleoperated50,365 teleoperated, 9,731 scriptedper source lab
Control rate15 Hz5 Hzvaries by source dataset
Cameras2 x ZED 2 exterior, 1 x ZED Mini wristup to 4, most episodes only the fixed onewhatever the lab used
Raw download1.7 TB RLDS, 8.7 TB raw stereoJPEG archivesper-dataset TFDS buckets
The entry point is the LeRobot conversion, not the original bucket

Few people still download 1.7 TB of RLDS TFRecords. The community org IPEC-COMMUNITY has republished most of Open X-Embodiment in LeRobot dataset form with AV1 video, where DROID lands at 392 GB. That is the version you will work with, and its meta/info.json is what to read first.

DROID

The most standardised of the three. One rig everywhere: a Franka Panda with a Robotiq 2F-85 gripper, two adjustable ZED 2 stereo cameras and a wrist ZED Mini, teleoperated with Meta Quest 2 controllers, recorded through Polymetis at 15 Hz in both joint and end-effector space. Language labels came later via tasq.ai, up to three per episode.

  • 76k trajectories, 350 hours, 564 scenes, 86 tasks, 50 collectors on three continents.
  • The headline result is co-training, not standalone training: batches mixed 50/50 with in-domain demonstrations beat the next best method by 22 percent absolute success in distribution, 17 percent out of it.
  • IPEC-COMMUNITY/droid_lerobot: 92,233 episodes, 27,044,326 frames, franka, 15 fps, codebase_version v2.0, three AV1 streams at 180x320, 392 GB.
  • A 2 GB, 100-episode debugging sample sits at gs://gresearch/robotics/droid_100. Start there.

BridgeData V2

The closest to a hobby setup: a WidowX 250 6-DoF arm, 60,096 trajectories across 24 environments and 13 skills at 5 Hz. Note the composition: 50,365 expert teleoperated demonstrations plus 9,731 from a randomised scripted pick-and-place policy, so about 16 percent is not human demonstration, which matters for imitation learning quality. The usual download, IPEC-COMMUNITY/bridge_orig_lerobot, reports 53,192 episodes and 1,893,026 frames at 5 fps, robot_type widowx: fewer than the paper's 60,096, so read the count from meta/info.json rather than quoting either.

Open X-Embodiment

Not a dataset in the same sense: 60 existing robot datasets from 34 labs pooled into one RLDS collection covering 22 embodiments and over a million trajectories. BridgeData V2 sits inside it as bridge_orig; the google_robot slice, fractal20220817_data, converts to 87,212 episodes at 3 fps.

The pooling carries a caveat the paper states outright. For the RT-X experiments the authors convert each source into a 7-DoF end-effector action, but do not align coordinate frames across datasets, and allow action values to be absolute or relative positions or velocities, per each robot's original control scheme. Their conclusion: the same action vector may induce very different motions for different robots.

The mismatch, in four parts

Embodiment mismatch is usually treated as one vague problem. It is four, they fail differently, and two are not fixable by scripting.

1. Degrees of freedom

An SO-100 has five arm joints plus a gripper. Counted as motors that is a 6-DoF arm, and the SmolVLA paper calls it that; counted as a positioning mechanism it is 5-DoF, and LeRobot calls it that in its inverse-kinematics docstring, which describes soft-orientation IK on the 5-DOF SO-101 where the wrist tracks orientation only partially. A Franka has seven positioning joints. That gap decides which poses exist: a 5-DoF arm cannot generally reach an arbitrary position and orientation at once, so the solver returns the closest it can, a different motion from the one demonstrated. Background: degrees of freedom.

python
# src/lerobot/robots/so_follower/so_follower.py
motors = {
    "shoulder_pan":  Motor(1, "sts3215", norm_mode_body),
    "shoulder_lift": Motor(2, "sts3215", norm_mode_body),
    "elbow_flex":    Motor(3, "sts3215", norm_mode_body),
    "wrist_flex":    Motor(4, "sts3215", norm_mode_body),
    "wrist_roll":    Motor(5, "sts3215", norm_mode_body),
    "gripper":       Motor(6, "sts3215", MotorNormMode.RANGE_0_100),
}
# action keys are "<motor>.pos"; send_action sync_writes them to
# "Goal_Position"  ->  a 6-D ABSOLUTE JOINT POSITION command


# openpi/src/openpi/policies/droid_policy.py
def make_droid_example() -> dict:
    return {
        "observation/exterior_image_1_left": np.random.randint(256, size=(224, 224, 3), dtype=np.uint8),
        "observation/wrist_image_left":      np.random.randint(256, size=(224, 224, 3), dtype=np.uint8),
        "observation/joint_position":        np.random.rand(7),   # seven Franka joints
        "observation/gripper_position":      np.random.rand(1),
        "prompt": "do something",
    }
# state = concat(joint_position, gripper_pos)  ->  8-D
Top: LeRobot's SO follower, from src/lerobot/robots/so_follower/so_follower.py. Bottom: openpi's DROID policy input. Six against eight.

So a ready-made DROID checkpoint is no shortcut. Physical Intelligence ships pi05_droid at gs://openpi-assets/checkpoints/pi05_droid, and the openpi README is plain about what those expert checkpoints are: fine-tuned from a base model, intended to run directly on the target robot, and they may or may not work on your particular robot. Its state is seven Franka joint positions plus a gripper value, eight numbers in all, and its image keys are exterior_image_1_left and wrist_image_left. No flag turns that into a six-motor SO-100 command.

2. What the action vector actually says

Deeper than dimensionality. In the LeRobot conversions all three say where the gripper should go, in Cartesian space. An SO-100 says where six servos should go. Converting needs a kinematic model and a solver, not a reshape.

PropertyOXE, DROID and Bridge in LeRobot formSO-100 in LeRobot
Action vector7-D: x, y, z, roll, pitch, yaw, gripper6-D: one goal position per motor
State vector8-D, with a pad slot (google_robot uses a quaternion)6-D, one per motor
FrameCartesian, unaligned across datasetsjoint space, per-arm calibration
Absolute or relativeeither, decided by the source lababsolute goal positions
Unitsfloat32, in each source robot's own scaledegrees by default (use_degrees=True), else -100 to 100
Silent failurea delta read as an absolutean uncalibrated arm
The 7-D Cartesian vector is the converter's convention, not DROID's

The openx2lerobot README documents a unified 8-dim state and 7-dim action for every dataset it converts, which is where the pad slot comes from. DROID's own RLDS schema differs: its top-level action is a 7-vector of 6 joint velocities plus 1 gripper position, with cartesian_position, cartesian_velocity, joint_position and joint_velocity under action_dict. openpi reads the joint-space view, the LeRobot build hands you the Cartesian one. Neither is six absolute servo angles.

LeRobot does ship the missing piece: the SO follower has a kinematics processor with InverseKinematicsEEToJoints and ForwardKinematicsJointsToEE steps. Its keys are ee.x, ee.y, ee.z plus a rotation vector ee.wx, ee.wy, ee.wz and ee.gripper_pos, so even the orientation encoding differs from the roll-pitch-yaw in the files. The IK step takes an orientation_weight, default 0.01, whose docstring says to set 0.0 for position-only IK on under-actuated arms. You can build the bridge, but the orientation half of every borrowed action stays approximated.

3. Control rate

DROID is 15 Hz, BridgeData V2 5 Hz, the google_robot slice 3 fps; pi0's authors describe the open-source part of their mixture as low-frequency control between 2 and 10 Hz. LeRobot's DatasetRecordConfig defaults to fps 30, episode_time_s 60, reset_time_s 60, num_episodes 50. A policy trained on 5 Hz data learned that one action covers 200 ms. Replay it at 30 Hz and the arm crawls; resample naively and you smear the frame where the gripper closes. It also interacts badly with action chunking: a 100-step chunk is 20 seconds at 5 Hz, 3.3 at 30 Hz.

4. Cameras

BridgeData V2 randomised two camera poses every 50 trajectories, and its project page notes most of the data carries only the fixed view anyway. DROID used adjustable ZED 2 mounts plus a wrist ZED Mini. You have two USB webcams positioned by eye. Camera pose is not a nuisance variable for a vision-language-action model; it is much of what the visual encoder keyed on, and nothing in the file format tells you the poses differ.

The trap that eats a day

The pieces fit together well enough to run. The dataset loads, training starts, the loss falls, checkpoints appear, nothing errors. Then the policy does nothing recognisable on the arm and you spend a day hunting a bug in your training script. There is no bug: the model learned a Cartesian action distribution for a robot that does not exist in your room. Start at loss falls, policy does nothing, not at your hyperparameters.

What happens when you try to merge the data anyway

The obvious plan is to concatenate: a few thousand DROID episodes plus your 50. LeRobot refuses, and the refusal names the three things that differ.

  1. 1
    Pull the 100-episode sample, not the full 1.7 TB

    2 GB is enough to see the structure.

    bash
    pip install gsutil tensorflow tensorflow-datasets
    gsutil -m cp -r gs://gresearch/robotics/droid_100 ~/tensorflow_datasets/
  2. 2
    Convert RLDS to LeRobot form

    openx2lerobot wraps the OXE standard transformations and annotates robot type and control frequency. The README puts this in convert.sh.

    bash
    git clone https://github.com/Tavish9/any4lerobot.git
    cd any4lerobot/openx2lerobot
    
    python openx_rlds.py \
        --raw-dir ~/tensorflow_datasets/droid_100/1.0.0 \
        --local-dir ~/lerobot_droid100 \
        --repo-id you/droid100_lerobot \
        --use-videos
  3. 3
    Read meta/info.json before anything else

    This file decides whether the rest of your day works.

    bash
    python -c "import json;d=json.load(open('meta/info.json'));\
    print(d['codebase_version'], d['robot_type'], d['fps']);\
    print(d['features']['action']['shape'], d['features']['observation.state']['shape'])"
  4. 4
    Try the merge and read the error

    merge loads every dataset, then validate_all_metadata checks fps, robot_type and features against the first in the list, raising on the first mismatch.

    bash
    lerobot-edit-dataset \
        --new_repo_id you/mixed \
        --operation.type merge \
        --operation.repo_ids "['you/droid100_lerobot', 'you/my_so100_task']"
    
    # ValueError: Same fps is expected, but got fps=30 instead of 15.

The reference values come from whichever dataset you listed first, which is why the message complains about your 30 fps rather than DROID's 15. Fix the fps and you hit the robot_type check; fix that and you hit the feature check, 7 against 6 for the action. No ordering gets through, and the same guard runs at record time via sanity_check_dataset_robot_compatibility.

Do not hardcode robot_type to defeat the check

lerobot-record writes robot_type=robot.name, and on current LeRobot main the SO-100 and SO-101 followers are the same class: SO100Follower and SO101Follower are aliases of SOFollower, whose name is so_follower, and both so100_follower and so101_follower register onto one shared SOFollowerRobotConfig. So the string in your dataset is not necessarily the one you typed on the command line. Read it from your own meta/info.json, and treat a check you had to disable as a check that was telling you something.

So what actually transfers?

Weights, not episodes. Every modern generalist policy absorbed some of it in pretraining, and when you fine-tune from a released checkpoint you inherit it already reconciled by people with the compute to do it properly. pi0's paper is candid about the proportion: 9.1 percent of its pre-training mixture, counted in timesteps, is open-source data including OXE, Bridge v2 and DROID. That figure is pi0's; each vendor's mixture differs.

Public cross-embodiment data on an SO-100 project
Advantages
  • Visual and language priors: the encoder has seen thousands of kitchens and mugs and knows what "the red block" refers to.
  • A prior over manipulation structure: approach, close, lift, transport, release, embodiment-independent even when the numbers are not.
  • A known-good dataset for testing. If your job cannot overfit 100 DROID episodes, the problem is your setup.
  • Reference points: on small-scale dataset domains RT-1-X reached a 50 percent higher mean success rate than the original method or RT-1, and RT-2-X beat RT-2 by about 3x on emergent skills.
Trade-offs
  • No usable action supervision. A 7-D Cartesian target is not a 6-D joint command.
  • No camera-pose transfer, and nothing in the data tells you the poses differ.
  • No timing transfer: 3, 5 and 15 fps sources against a 30 fps recorder.
  • No gripper transfer. A Robotiq 2F-85 and a printed jaw on an STS3215 differ in force, stroke and dynamics.
  • Scale alone was not enough even for its authors: in the large-dataset domains RT-1-X did not beat an RT-1 trained on that dataset alone.
  • No reduction in how many of your own episodes you need.
Layer of the modelTransfers?Why
Vision encoderYes, stronglyObjects and scenes are embodiment-independent
Language groundingYesInstructions are text, not geometry
Cross-modal fusionMostlyAttends to the object named in the prompt
Proprioception encoderNoInput dimension and joint semantics differ
Action headNoTrained on a 7-D Cartesian space you are not in
Normalisation statisticsNo, and dangerousForeign stats shift every command

This is why SmolVLA behaves differently on a low-cost arm. Its paper selects 481 community datasets from Hugging Face, filtered by embodiment type, episode count, data quality and frame coverage: 22.9K episodes, 10.6M frames, evaluated on real SO-100 and SO-101 arms. In the paper's own ablation that pretraining is worth a jump from 51.7 to 78.3 percent average success across three real SO-100 tasks, against the same model trained without it. Compare on ACT against SmolVLA.

Three paths worth taking

Path A: fine-tune from a checkpoint that already ate the data

Most people should take this one. You never touch DROID or Open X-Embodiment: pick a policy whose pretraining already absorbed cross-embodiment data, record your own episodes, fine-tune.

PolicyParamsMin episodesDataset formatGPU tierInferenceBase checkpoint
GR00T N1.7~3 B, ~40 M trained in fine-tuning50LeRobot v2.0 or v2.1A100 or H100 80 GB152 ms per stepnvidia/GR00T-N1.7-3B
GR00T N1.5~3 B50LeRobot v2.0 or v2.1A100 or H100 80 GB165 msnvidia/GR00T-N1.5-3B
Pi0.5~3 B, PaliGemma backbone50LeRobot v3.0A100 or H100 80 GB485 mslerobot/pi05_base
SmolVLA~450 M30LeRobot v3.0RTX 4090 or any 24 GB245 mslerobot/smolvla_base
ACT~80 M50LeRobot v3.0RTX 4090 or any 24 GB20 msnone, from scratch

The platform names the vendors' own base checkpoints for GR00T N1.7, GR00T N1.5 and Pi0.5; SmolVLA fine-tunes from lerobot/smolvla_base on the Hub. ACT is the honest edge case: no base model at all, so none of the public data ever reaches it. Not automatically a disadvantage, since at 20 ms per action step it is the only one of the five that can close a fast loop, as the ACT page sets out. Pick by task using all five compared, GR00T N1.7 against Pi0.5, and the 332 benchmark results across 85 models in the arena.

The AY-Robots policies page comparing the five trainable policies by parameter count, GPU tier, inference latency and minimum episodes
The comparison at /policies: the five policies you can actually train, with the GPU tier and the minimum episode count each one needs.

Path B: use DROID as a test fixture

The 100-episode sample is the best 2 GB you will download this month, and not for training. It is a dataset you know is correct. Run your converter, loader and a short GPU job on it; anything that fails is an infrastructure bug found while it was cheap. NVIDIA does the same at scale: the GR00T N1.7 card lists four post-trained variants, for Bridge and Fractal in SimplerEnv, DROID and LIBERO.

Path C: record your own, deliberately

Thirty to fifty episodes sounds small next to 76,000 until you remember yours are the only ones with your arm, your cameras and your table. At LeRobot's defaults, 50 episodes is 100 minutes of wall clock. See record your first dataset, SO-100 data collection and the data quality guide. Public LeRobot datasets that already match a supported arm are listed in the dataset directory.

The AY-Robots recording tutorial page showing the steps to capture a LeRobot dataset from a teleoperation session
The recording walkthrough at /learn/record-your-first-dataset: the step public data cannot replace.

Two ways to get from public data to a working policy

All of this runs on your own machine plus a rented GPU. Knowing the manual path matters because when something breaks you will know which layer broke.

  1. 1
    Install LeRobot with the extras the scripts need

    The record and train entry points each declare their own extra.

    bash
    pip install 'lerobot[core_scripts]'   # lerobot-record
    pip install 'lerobot[training]'      # lerobot-train
    pip install gsutil tensorflow tensorflow-datasets
  2. 2
    Get a small, known-good slice

    100 DROID episodes, 2 GB.

    bash
    gsutil -m cp -r gs://gresearch/robotics/droid_100 ~/tensorflow_datasets/
  3. 3
    Convert to LeRobot form

    Writes the unified 8-D state and 7-D action, annotating robot type and control frequency.

    bash
    python openx_rlds.py \
        --raw-dir ~/tensorflow_datasets/droid_100/1.0.0 \
        --local-dir ~/lerobot_droid100 \
        --repo-id you/droid100_lerobot \
        --use-videos
  4. 4
    Check the version against your trainer

    The converter README documents v3.0 output; the published IPEC-COMMUNITY datasets report v2.0. GR00T wants v2.0 or v2.1 and crashes on v3.0; Pi0.5, SmolVLA and ACT want v3.0. Mind the gap: the only conversion script on LeRobot main goes 2.1 to 3.0.

    bash
    python src/lerobot/scripts/convert_dataset_v21_to_v30.py \
        --repo-id=you/droid100_lerobot
  5. 5
    Record your own episodes

    Defaults: 30 fps, 60 s per episode, 60 s reset, 50 episodes. The teleoperator type is so100_leader.

    bash
    lerobot-record \
      --robot.type=so100_follower \
      --robot.port=/dev/ttyACM0 \
      --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
      --teleop.type=so100_leader \
      --teleop.port=/dev/ttyACM1 \
      --dataset.repo_id=you/so100_pick_block \
      --dataset.num_episodes=50 \
      --dataset.single_task="Pick up the red block and put it in the bowl"
  6. 6
    Fine-tune on your data only

    Weights from public data, actions from your own. Do not mix the datasets.

    bash
    lerobot-train \
      --policy.path=lerobot/smolvla_base \
      --dataset.repo_id=you/so100_pick_block \
      --policy.device=cuda \
      --batch_size=4 \
      --steps=20000
Where the time actually goes

Not in training; the GPU job is hours. The conversion, the version mismatch and the moment you find your dataset is v2.0 and your trainer wants v3.0 are days. Read dataset rejected as v3 first.

The AY-Robots desktop client download page, the client that records LeRobot-format datasets from a teleoperation session
The desktop client at /download writes LeRobot datasets directly from a teleop session, sidestepping RLDS conversion.

What each path costs

PathStorageHuman timeGPU costChance it moves your arm
Converted DROID alone392 GBdays of conversion4 to 12 USDvery low, wrong action space
DROID merged with your episodesbothblocked by validate_all_metadatan/anone, it does not run
SmolVLA, 30 to 50 own episodesa few GB100 min recording1 to 3 USDhigh
GR00T N1.7, 50 own episodesa few GB100 min recording4 to 12 USDhigh
ACT from scratch, 50 own episodesa few GB100 min recording1 to 3 USDhigh, 20 ms inference
DROID sample as a test fixture2 GBan afternoonone short runhigh, as validation

The asymmetry is the point: the path that borrows the most data is the most expensive and least likely to move your arm. Under two hours of your own teleoperation beats a terabyte of somebody else's Franka. No arm yet? /live streams a physical SO-100 with no signup. Then train your first policy, and SmolVLA on SO-100 for the specific guide.

A reasonable default plan

Download the 2 GB DROID sample and use it to prove your pipeline. Ignore the other 1.7 TB. Record 50 episodes of one task with fixed cameras. Fine-tune SmolVLA first, because at 30 minimum episodes on a 24 GB card it is cheapest to iterate on, then try GR00T N1.7 on the same data. Compare on your task, not on a benchmark.

Record datasets that already match your arm

The desktop client writes LeRobot-format datasets straight from a teleop session: right arm, right frame rate, right action space. No RLDS conversion, no remapping.

Get the desktop client
Can I train a policy on DROID and run it on my SO-100?

Not directly. In the LeRobot build DROID actions are 7-D end-effector commands on a Franka Panda at 15 fps; in the raw RLDS they are 6 joint velocities plus a gripper position. An SO-100 takes 6 absolute joint positions. You would need an inverse-kinematics layer, and even then a 5-DoF wrist cannot reproduce arbitrary 6-DoF poses.

Can I mix DROID or Bridge episodes with my own SO-100 episodes?

No. validate_all_metadata requires identical fps, robot_type and feature schema and raises ValueError on the first mismatch. All three differ: 15 or 5 fps against 30, franka or widowx against your arm, action shapes of 7 against 6. Rewriting metadata to pass the check does not fix the semantics.

Is Open X-Embodiment useless for a low-cost arm then?

No, but its value reaches you through pretrained weights, not episodes. Open-source datasets including OXE, Bridge v2 and DROID are 9.1 percent of pi0's pre-training mixture, and NVIDIA ships GR00T N1.7 variants post-trained on Bridge, Fractal, DROID and LIBERO. What you cannot do is append those episodes to your own recording.

Which policy benefits most from public cross-embodiment data?

Pi0.5 and the GR00T models are the large cross-embodiment foundation models of the five, and their vendors publish base checkpoints you fine-tune from. SmolVLA took the opposite route: its pretraining set is 481 community datasets, 22.9K episodes and 10.6M frames, and its paper reports that this pretraining lifts real SO-100 success from 51.7 to 78.3 percent on average. ACT is the third case: no base model, nothing pretrained, 20 ms per action step.

How many of my own episodes do I actually need?

30 for SmolVLA, 50 for GR00T N1.7, GR00T N1.5, Pi0.5 and ACT. At LeRobot's defaults of 60 s per episode and 60 s reset, 50 episodes is 100 minutes of wall clock. Borrowed cross-embodiment data does not lower those numbers.

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started