diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 2fb23051c11..7423495de42 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -25,7 +25,7 @@ body:
id: system-info
attributes:
label: System Info
- description: If needed, you can share your lerobot configuration with us by running `python -m lerobot.scripts.display_sys_info` and copy-pasting its outputs below
+ description: Please share your LeRobot configuration by running `lerobot-info` (if installed) or `python -m lerobot.scripts.display_sys_info` (if not installed) and pasting the output below.
render: Shell
placeholder: lerobot version, OS, python version, numpy version, torch version, and lerobot's configuration
validations:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..af91c9f582e
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,68 @@
+# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This workflow handles closing stale issues and PRs.
+name: Stale
+on:
+ # Allows running this workflow manually from the Actions tab
+ workflow_dispatch:
+
+ # Runs at 02:00
+ schedule:
+ - cron: "0 2 * * *"
+
+env:
+ CLOSE_ISSUE_MESSAGE: >
+ This issue was closed because it has been stalled for 14 days with no activity.
+ Feel free to reopen if is still relevant, or to ping a collaborator if you have any questions.
+ CLOSE_PR_MESSAGE: >
+ This PR was closed because it has been stalled for 14 days with no activity.
+ Feel free to reopen if is still relevant, or to ping a collaborator if you have any questions.
+ WARN_ISSUE_MESSAGE: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity (6 months). It will be closed if no further activity occurs.
+ Thank you for your contributions.
+ WARN_PR_MESSAGE: >
+ This PR has been automatically marked as stale because it has not had
+ recent activity (6 months). It will be closed if no further activity occurs.
+ Thank you for your contributions.
+
+jobs:
+ # This job runs the actions/stale action to close stale issues and PRs.
+ stale:
+ name: Close Stale Issues and PRs
+ runs-on: ubuntu-latest
+ permissions:
+ actions: write
+ contents: write # only for delete-branch option
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v10
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-label: stale
+ stale-pr-label: stale
+ exempt-issue-labels: never-stale
+ exempt-pr-labels: never-stale
+ days-before-issue-stale: 180 # TODO(Steven): Will modify this to 90 after initial cleanup
+ days-before-issue-close: 14
+ days-before-pr-stale: 180
+ days-before-pr-close: 14
+ delete-branch: true
+ close-issue-message: ${{ env.CLOSE_ISSUE_MESSAGE }}
+ close-pr-message: ${{ env.CLOSE_PR_MESSAGE }}
+ stale-issue-message: ${{ env.WARN_ISSUE_MESSAGE }}
+ stale-pr-message: ${{ env.WARN_PR_MESSAGE }}
+ operations-per-run: 500
diff --git a/.github/workflows/unbound_deps_tests.yml b/.github/workflows/unbound_deps_tests.yml
new file mode 100644
index 00000000000..902074a8381
--- /dev/null
+++ b/.github/workflows/unbound_deps_tests.yml
@@ -0,0 +1,183 @@
+# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This workflow handles full testing with unboud dependencies versions.
+name: Unbound Dependency Tests
+
+on:
+ # Allows running this workflow manually from the Actions tab
+ workflow_dispatch:
+
+ # Run on the 1st and 15th of every month at 09:00 UTC
+ schedule:
+ - cron: '0 2 1,15 * *'
+
+permissions:
+ contents: read
+
+# Sets up the environment variables
+env:
+ UV_VERSION: "0.8.0"
+ PYTHON_VERSION: "3.10"
+ DOCKER_IMAGE_NAME: huggingface/lerobot-gpu:unbound
+
+# Ensures that only the latest action is built, canceling older runs.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+
+ # This job runs the E2E tests + pytest with all unbound extras
+ full-tests:
+ name: Full Unbound Tests
+ runs-on: ubuntu-latest
+ env:
+ MUJOCO_GL: egl
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ lfs: true
+ persist-credentials: false
+
+ - name: Install apt dependencies
+ run: |
+ sudo apt-get update && sudo apt-get install -y build-essential \
+ git curl libglib2.0-0 libegl1-mesa-dev ffmpeg libusb-1.0-0-dev \
+ speech-dispatcher libgeos-dev portaudio19-dev
+
+ - name: Setup uv and Python
+ uses: astral-sh/setup-uv@v6 # zizmor: ignore[unpinned-uses]
+ with:
+ enable-cache: true
+ version: ${{ env.UV_VERSION }}
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Unbound dependencies
+ run: |
+ sed -i 's/,[[:space:]]*<[0-9\.]*//g' pyproject.toml
+ echo "Dependencies unbound:" && cat pyproject.toml
+
+ - name: Install lerobot with all extras
+ run: uv sync --all-extras
+
+ - name: Run pytest (all extras)
+ run: uv run pytest tests -vv
+
+ - name: Run end-to-end tests
+ run: uv run make test-end-to-end
+
+ # This job builds a GPU enabled image for testing
+ build-and-push-docker:
+ name: Build and Push Docker
+ runs-on:
+ group: aws-general-8-plus
+ outputs:
+ image_tag: ${{ env.DOCKER_IMAGE_NAME }}
+ env:
+ GITHUB_REF: ${{ github.ref }}
+ steps:
+ - name: Install Git LFS
+ run: |
+ sudo apt-get update
+ sudo apt-get install git-lfs
+ git lfs install
+ - uses: actions/checkout@v4
+ with:
+ lfs: true
+ persist-credentials: false
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3 # zizmor: ignore[unpinned-uses]
+ with:
+ cache-binary: false
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3 # zizmor: ignore[unpinned-uses]
+ with:
+ username: ${{ secrets.DOCKERHUB_LEROBOT_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_LEROBOT_PASSWORD }}
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v6 # zizmor: ignore[unpinned-uses]
+ with:
+ context: .
+ file: ./docker/Dockerfile.internal
+ push: true
+ tags: ${{ env.DOCKER_IMAGE_NAME }}
+ build-args: |
+ UNBOUND_DEPS=true
+
+ # This job runs pytest with all unbound extras in a GPU enabled host
+ # It runs everytime a test image is created
+ gpu-tests:
+ name: GPU Unbound Tests
+ needs: [build-and-push-docker]
+ runs-on:
+ group: aws-g6-4xlarge-plus
+ env:
+ HF_HOME: /home/user_lerobot/.cache/huggingface
+ HF_LEROBOT_HOME: /home/user_lerobot/.cache/huggingface/lerobot
+ TORCH_HOME: /home/user_lerobot/.cache/torch
+ TRITON_CACHE_DIR: /home/user_lerobot/.cache/triton
+ container:
+ image: ${{ needs.build-and-push-docker.outputs.image_tag }} # zizmor: ignore[unpinned-images]
+ options: --gpus all --shm-size "16gb"
+ credentials:
+ username: ${{ secrets.DOCKERHUB_LEROBOT_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_LEROBOT_PASSWORD }}
+ defaults:
+ run:
+ shell: bash
+ working-directory: /lerobot
+ steps:
+ - name: Run pytest on GPU
+ run: pytest tests -vv
+ - name: Run end-to-end tests
+ run: make test-end-to-end
+
+ # This job deletes the test image recently created
+ # It runs everytime after the gpu-tests have finished
+ delete-unbound-image:
+ name: Delete Unbound Image
+ needs: [gpu-tests, build-and-push-docker]
+ if: always() && needs.build-and-push-docker.result == 'success'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Get Docker Hub Token and Delete Image
+ # zizmor: ignore[template-injection]
+ run: |
+ IMAGE_NAME=$(echo "${{ needs.build-and-push-docker.outputs.image_tag }}" | cut -d':' -f1)
+ IMAGE_TAG=$(echo "${{ needs.build-and-push-docker.outputs.image_tag }}" | cut -d':' -f2)
+
+ echo "Attempting to delete image: $IMAGE_NAME:$IMAGE_TAG"
+
+ TOKEN=$(curl -s -H "Content-Type: application/json" \
+ -X POST \
+ -d '{"username": "${{ secrets.DOCKERHUB_LEROBOT_USERNAME }}", "password": "${{ secrets.DOCKERHUB_LEROBOT_PASSWORD }}"}' \
+ https://hub.docker.com/v2/users/login/ | jq -r .token)
+
+ if [ "$TOKEN" == "null" ] || [ -z "$TOKEN" ]; then
+ echo "::error::Failed to get Docker Hub token."
+ exit 1
+ fi
+
+ HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
+ -H "Authorization: JWT ${TOKEN}" \
+ -X DELETE \
+ https://hub.docker.com/v2/repositories/${IMAGE_NAME}/tags/${IMAGE_TAG}/)
+
+ if [ "$HTTP_RESPONSE" -eq 204 ]; then
+ echo "Successfully deleted Docker image tag: $IMAGE_NAME:$IMAGE_TAG"
+ else
+ echo "::error::Failed to delete Docker image. HTTP status: $HTTP_RESPONSE"
+ exit 1
+ fi
diff --git a/.gitignore b/.gitignore
index c4d1f769f18..b47e22cbfad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -173,3 +173,7 @@ outputs/
# Dev folders
.cache/*
+*.stl
+*.urdf
+*.xml
+*.part
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f09017991f0..7f5beff801e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -86,11 +86,12 @@ repos:
# TODO(Steven): Uncomment when ready to use
##### Static Analysis & Typing #####
- # - repo: https://github.com/pre-commit/mirrors-mypy
- # rev: v1.16.0
- # hooks:
- # - id: mypy
- # args: [--python-version=3.10]
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.16.0
+ hooks:
+ - id: mypy
+ args: [--config-file=pyproject.toml]
+ exclude: ^(examples|benchmarks|tests)/
##### Docstring Checks #####
# - repo: https://github.com/akaihola/darglint2
diff --git a/README.md b/README.md
index 9fd45a7b722..357e62cc1e9 100644
--- a/README.md
+++ b/README.md
@@ -197,12 +197,12 @@ wandb login
### Visualize datasets
-Check out [example 1](https://github.com/huggingface/lerobot/blob/main/examples/1_load_lerobot_dataset.py) that illustrates how to use our dataset class which automatically downloads data from the Hugging Face hub.
+Check out [example 1](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) that illustrates how to use our dataset class which automatically downloads data from the Hugging Face hub.
You can also locally visualize episodes from a dataset on the hub by executing our script from the command line:
```bash
-python -m lerobot.scripts.visualize_dataset \
+lerobot-dataset-viz \
--repo-id lerobot/pusht \
--episode-index 0
```
@@ -210,7 +210,7 @@ python -m lerobot.scripts.visualize_dataset \
or from a dataset in a local folder with the `root` option and the `--local-files-only` (in the following case the dataset will be searched for in `./my_local_data_dir/lerobot/pusht`)
```bash
-python -m lerobot.scripts.visualize_dataset \
+lerobot-dataset-viz \
--repo-id lerobot/pusht \
--root ./my_local_data_dir \
--local-files-only 1 \
@@ -221,19 +221,19 @@ It will open `rerun.io` and display the camera streams, robot states and actions
https://github-production-user-asset-6210df.s3.amazonaws.com/4681518/328035972-fd46b787-b532-47e2-bb6f-fd536a55a7ed.mov?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240505%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240505T172924Z&X-Amz-Expires=300&X-Amz-Signature=d680b26c532eeaf80740f08af3320d22ad0b8a4e4da1bcc4f33142c15b509eda&X-Amz-SignedHeaders=host&actor_id=24889239&key_id=0&repo_id=748713144
-Our script can also visualize datasets stored on a distant server. See `python -m lerobot.scripts.visualize_dataset --help` for more instructions.
+Our script can also visualize datasets stored on a distant server. See `lerobot-dataset-viz --help` for more instructions.
### The `LeRobotDataset` format
A dataset in `LeRobotDataset` format is very simple to use. It can be loaded from a repository on the Hugging Face hub or a local folder simply with e.g. `dataset = LeRobotDataset("lerobot/aloha_static_coffee")` and can be indexed into like any Hugging Face and PyTorch dataset. For instance `dataset[0]` will retrieve a single temporal frame from the dataset containing observation(s) and an action as PyTorch tensors ready to be fed to a model.
-A specificity of `LeRobotDataset` is that, rather than retrieving a single frame by its index, we can retrieve several frames based on their temporal relationship with the indexed frame, by setting `delta_timestamps` to a list of relative times with respect to the indexed frame. For example, with `delta_timestamps = {"observation.image": [-1, -0.5, -0.2, 0]}` one can retrieve, for a given index, 4 frames: 3 "previous" frames 1 second, 0.5 seconds, and 0.2 seconds before the indexed frame, and the indexed frame itself (corresponding to the 0 entry). See example [1_load_lerobot_dataset.py](https://github.com/huggingface/lerobot/blob/main/examples/1_load_lerobot_dataset.py) for more details on `delta_timestamps`.
+A specificity of `LeRobotDataset` is that, rather than retrieving a single frame by its index, we can retrieve several frames based on their temporal relationship with the indexed frame, by setting `delta_timestamps` to a list of relative times with respect to the indexed frame. For example, with `delta_timestamps = {"observation.image": [-1, -0.5, -0.2, 0]}` one can retrieve, for a given index, 4 frames: 3 "previous" frames 1 second, 0.5 seconds, and 0.2 seconds before the indexed frame, and the indexed frame itself (corresponding to the 0 entry). See example [1_load_lerobot_dataset.py](https://github.com/huggingface/lerobot/blob/main/examples/dataset/load_lerobot_dataset.py) for more details on `delta_timestamps`.
Under the hood, the `LeRobotDataset` format makes use of several ways to serialize data which can be useful to understand if you plan to work more closely with this format. We tried to make a flexible yet simple dataset format that would cover most type of features and specificities present in reinforcement learning and robotics, in simulation and in real-world, with a focus on cameras and robot states but easily extended to other types of sensory inputs as long as they can be represented by a tensor.
Here are the important details and internal structure organization of a typical `LeRobotDataset` instantiated with `dataset = LeRobotDataset("lerobot/aloha_static_coffee")`. The exact features will change from dataset to dataset but not the main aspects:
-````
+```
dataset attributes:
├ hf_dataset: a Hugging Face dataset (backed by Arrow/parquet). Typical features example:
│ ├ observation.images.cam_high (VideoFrame):
@@ -269,7 +269,7 @@ dataset attributes:
├ root (Path): local directory where the dataset is stored
├ image_transforms (Callable): optional image transformations to apply to visual modalities
└ delta_timestamps (dict): optional delta timestamps for temporal queries
-decoding videos (e.g., 'pyav', 'torchcodec')
+```
A `LeRobotDataset` is serialised using several widespread file formats for each of its parts, namely:
@@ -279,42 +279,6 @@ A `LeRobotDataset` is serialised using several widespread file formats for each
Dataset can be uploaded/downloaded from the HuggingFace hub seamlessly. To work on a local dataset, you can specify its location with the `root` argument if it's not in the default `~/.cache/huggingface/lerobot` location.
-### Evaluate a pretrained policy
-
-Check out [example 2](https://github.com/huggingface/lerobot/blob/main/examples/2_evaluate_pretrained_policy.py) that illustrates how to download a pretrained policy from Hugging Face hub, and run an evaluation on its corresponding environment.
-
-We also provide a more capable script to parallelize the evaluation over multiple environments during the same rollout. Here is an example with a pretrained model hosted on [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht):
-
-```bash
-lerobot-eval \
- --policy.path=lerobot/diffusion_pusht \
- --env.type=pusht \
- --eval.batch_size=10 \
- --eval.n_episodes=10 \
- --policy.use_amp=false \
- --policy.device=cuda
-````
-
-Note: After training your own policy, you can re-evaluate the checkpoints with:
-
-```bash
-lerobot-eval --policy.path={OUTPUT_DIR}/checkpoints/last/pretrained_model
-```
-
-See `lerobot-eval --help` for more instructions.
-
-### Train your own policy
-
-Check out [example 3](https://github.com/huggingface/lerobot/blob/main/examples/3_train_policy.py) that illustrates how to train a model using our core library in python, and [example 4](https://github.com/huggingface/lerobot/blob/main/examples/4_train_policy_with_script.md) that shows how to use our training script from command line.
-
-To use wandb for logging training and evaluation curves, make sure you've run `wandb login` as a one-time setup step. Then, when running the training command above, enable WandB in the configuration by adding `--wandb.enable=true`.
-
-A link to the wandb logs for the run will also show up in yellow in your terminal. Here is an example of what they look like in your browser. Please also check [here](https://github.com/huggingface/lerobot/blob/main/examples/4_train_policy_with_script.md#typical-logs-and-metrics) for the explanation of some commonly used metrics in logs.
-
-\
-
-Note: For efficiency, during training every checkpoint is evaluated on a low number of episodes. You may use `--eval.n_episodes=500` to evaluate on more episodes than the default. Or, after training, you may want to re-evaluate your best checkpoints on more episodes or change the evaluation settings. See `lerobot-eval --help` for more instructions.
-
#### Reproduce state-of-the-art (SOTA)
We provide some pretrained policies on our [hub page](https://huggingface.co/lerobot) that can achieve state-of-the-art performances.
@@ -373,3 +337,7 @@ If you want, you can cite this work with:
## Star History
[](https://star-history.com/#huggingface/lerobot&Timeline)
+
+```
+
+```
diff --git a/src/lerobot/utils/benchmark.py b/benchmarks/video/benchmark.py
similarity index 100%
rename from src/lerobot/utils/benchmark.py
rename to benchmarks/video/benchmark.py
diff --git a/benchmarks/video/run_video_benchmark.py b/benchmarks/video/run_video_benchmark.py
index 5472551f579..9f34b227369 100644
--- a/benchmarks/video/run_video_benchmark.py
+++ b/benchmarks/video/run_video_benchmark.py
@@ -35,12 +35,13 @@
from skimage.metrics import mean_squared_error, peak_signal_noise_ratio, structural_similarity
from tqdm import tqdm
+from benchmarks.video.benchmark import TimeBenchmark
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.video_utils import (
decode_video_frames_torchvision,
encode_video_frames,
)
-from lerobot.utils.benchmark import TimeBenchmark
+from lerobot.utils.constants import OBS_IMAGE
BASE_ENCODING = OrderedDict(
[
@@ -117,7 +118,7 @@ def save_first_episode(imgs_dir: Path, dataset: LeRobotDataset) -> None:
hf_dataset = dataset.hf_dataset.with_format(None)
# We only save images from the first camera
- img_keys = [key for key in hf_dataset.features if key.startswith("observation.image")]
+ img_keys = [key for key in hf_dataset.features if key.startswith(OBS_IMAGE)]
imgs_dataset = hf_dataset.select_columns(img_keys[0])
for i, item in enumerate(
diff --git a/docker/Dockerfile.internal b/docker/Dockerfile.internal
index 8c77fe49731..2616cd06c01 100644
--- a/docker/Dockerfile.internal
+++ b/docker/Dockerfile.internal
@@ -39,6 +39,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common build-essential git curl \
libglib2.0-0 libgl1-mesa-glx libegl1-mesa ffmpeg \
libusb-1.0-0-dev speech-dispatcher libgeos-dev portaudio19-dev \
+ cmake pkg-config ninja-build \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
@@ -74,6 +75,14 @@ RUN uv venv --python python${PYTHON_VERSION}
# Install Python dependencies for caching
COPY --chown=user_lerobot:user_lerobot pyproject.toml README.md MANIFEST.in ./
COPY --chown=user_lerobot:user_lerobot src/ src/
+
+ARG UNBOUND_DEPS=false
+
+RUN if [ "$UNBOUND_DEPS" = "true" ]; then \
+ sed -i 's/,[[:space:]]*<[0-9\.]*//g' pyproject.toml; \
+ echo "Dependencies unbound:" && cat pyproject.toml; \
+ fi
+
RUN uv pip install --no-cache ".[all]"
# Copy the rest of the application source code
diff --git a/docker/Dockerfile.user b/docker/Dockerfile.user
index bcd067637bb..c1b28445351 100644
--- a/docker/Dockerfile.user
+++ b/docker/Dockerfile.user
@@ -31,6 +31,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential git curl libglib2.0-0 libegl1-mesa-dev ffmpeg \
libusb-1.0-0-dev speech-dispatcher libgeos-dev portaudio19-dev \
+ cmake pkg-config ninja-build \
&& curl -LsSf https://astral.sh/uv/install.sh | sh \
&& mv /root/.local/bin/uv /usr/local/bin/uv \
&& useradd --create-home --shell /bin/bash user_lerobot \
@@ -60,6 +61,14 @@ RUN uv venv
# Install Python dependencies for caching
COPY --chown=user_lerobot:user_lerobot pyproject.toml README.md MANIFEST.in ./
COPY --chown=user_lerobot:user_lerobot src/ src/
+
+ARG UNBOUND_DEPS=false
+
+RUN if [ "$UNBOUND_DEPS" = "true" ]; then \
+ sed -i 's/,[[:space:]]*<[0-9\.]*//g' pyproject.toml; \
+ echo "Dependencies unbound:" && cat pyproject.toml; \
+ fi
+
RUN uv pip install --no-cache ".[all]"
# Copy the rest of the application code
diff --git a/docs/source/_toctree.yml b/docs/source/_toctree.yml
index 5f5a509c7bf..3b6cccc9596 100644
--- a/docs/source/_toctree.yml
+++ b/docs/source/_toctree.yml
@@ -19,16 +19,36 @@
title: Train RL in Simulation
- local: async
title: Use Async Inference
+ title: "Tutorials"
+- sections:
+ - local: lerobot-dataset-v3
+ title: Using LeRobotDataset
- local: porting_datasets_v3
title: Porting Large Datasets
- title: "Tutorials"
+ title: "Datasets"
- sections:
+ - local: act
+ title: ACT
- local: smolvla
- title: Finetune SmolVLA
+ title: SmolVLA
+ - local: pi0
+ title: π₀ (Pi0)
+ - local: pi05
+ title: π₀.₅ (Pi05)
+ - local: libero
+ title: Using Libero
title: "Policies"
- sections:
- - local: hope_jr
- title: Hope Jr
+ - local: introduction_processors
+ title: Introduction to Robot Processors
+ - local: debug_processor_pipeline
+ title: Debug your processor pipeline
+ - local: implement_your_own_processor
+ title: Implement your own processor
+ - local: processors_robots_teleop
+ title: Processors for Robots and Teleoperators
+ title: "Robot Processors"
+- sections:
- local: so101
title: SO-101
- local: so100
@@ -37,9 +57,15 @@
title: Koch v1.1
- local: lekiwi
title: LeKiwi
+ - local: hope_jr
+ title: Hope Jr
- local: reachy2
title: Reachy 2
title: "Robots"
+- sections:
+ - local: phone_teleop
+ title: Phone
+ title: "Teleoperators"
- sections:
- local: notebooks
title: Notebooks
diff --git a/docs/source/act.mdx b/docs/source/act.mdx
new file mode 100644
index 00000000000..e3294ca6945
--- /dev/null
+++ b/docs/source/act.mdx
@@ -0,0 +1,92 @@
+# ACT (Action Chunking with Transformers)
+
+ACT is a **lightweight and efficient policy for imitation learning**, especially well-suited for fine-grained manipulation tasks. It's the **first model we recommend when you're starting out** with LeRobot due to its fast training time, low computational requirements, and strong performance.
+
+
@@ -216,11 +475,21 @@ The SO101 leader arm has reduced gears that allows it to move and track the foll
To setup the SO101 leader, you need to set the `control_mode` to `"leader"` and define the `teleop` section in the configuration file.
```json
+{
+ "env": {
"teleop": {
- "type": "so101_leader",
- "port": "/dev/tty.usbmodem585A0077921", # check your port number
- "use_degrees": true
+ "type": "so101_leader",
+ "port": "/dev/tty.usbmodem585A0077921",
+ "use_degrees": true
},
+ "processor": {
+ "control_mode": "leader",
+ "gripper": {
+ "use_gripper": true
+ }
+ }
+ }
+}
```
In order to annotate the success/failure of the episode, **you will need** to use a keyboard to press `s` for success, `esc` for failure.
@@ -246,12 +515,12 @@ During the online training, press `space` to take over the policy and `space` ag
Start the recording process, an example of the config file can be found [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/blob/main/env_config_so100.json):
```bash
-python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config_so100.json
+python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config_so100.json
```
During recording:
-1. The robot will reset to the initial position defined in the configuration file `fixed_reset_joint_positions`
+1. The robot will reset to the initial position defined in the configuration file `env.processor.reset.fixed_reset_joint_positions`
2. Complete the task successfully
3. The episode ends with a reward of 1 when you press the "success" button
4. If the time limit is reached, or the fail button is pressed, the episode ends with a reward of 0
@@ -277,7 +546,7 @@ Note: If you already know the crop parameters, you can skip this step and just s
Use the `crop_dataset_roi.py` script to interactively select regions of interest in your camera images:
```bash
-python -m lerobot.scripts.rl.crop_dataset_roi --repo-id username/pick_lift_cube
+python -m lerobot.rl.crop_dataset_roi --repo-id username/pick_lift_cube
```
1. For each camera view, the script will display the first frame
@@ -310,11 +579,19 @@ observation.images.front: [180, 250, 120, 150]
Add these crop parameters to your training configuration:
```json
-"crop_params_dict": {
- "observation.images.side": [180, 207, 180, 200],
- "observation.images.front": [180, 250, 120, 150]
-},
-"resize_size": [128, 128]
+{
+ "env": {
+ "processor": {
+ "image_preprocessing": {
+ "crop_params_dict": {
+ "observation.images.side": [180, 207, 180, 200],
+ "observation.images.front": [180, 250, 120, 150]
+ },
+ "resize_size": [128, 128]
+ }
+ }
+ }
+}
```
**Recommended image resolution**
@@ -338,31 +615,57 @@ Before training, you need to collect a dataset with labeled examples. The `recor
To collect a dataset, you need to modify some parameters in the environment configuration based on HILSerlRobotEnvConfig.
```bash
-python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/reward_classifier_train_config.json
+python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/reward_classifier_train_config.json
```
**Key Parameters for Data Collection**
-- **mode**: set it to `"record"` to collect a dataset
-- **repo_id**: `"hf_username/dataset_name"`, name of the dataset and repo on the hub
-- **num_episodes**: Number of episodes to record
-- **number_of_steps_after_success**: Number of additional frames to record after a success (reward=1) is detected
-- **fps**: Number of frames per second to record
-- **push_to_hub**: Whether to push the dataset to the hub
+- **mode**: set it to `"record"` to collect a dataset (at root level)
+- **dataset.repo_id**: `"hf_username/dataset_name"`, name of the dataset and repo on the hub
+- **dataset.num_episodes_to_record**: Number of episodes to record
+- **env.processor.reset.terminate_on_success**: Whether to automatically terminate episodes when success is detected (default: `true`)
+- **env.fps**: Number of frames per second to record
+- **dataset.push_to_hub**: Whether to push the dataset to the hub
+
+The `env.processor.reset.terminate_on_success` parameter allows you to control episode termination behavior. When set to `false`, episodes will continue even after success is detected, allowing you to collect more positive examples with the reward=1 label. This is crucial for training reward classifiers as it provides more success state examples in your dataset. When set to `true` (default), episodes terminate immediately upon success detection.
-The `number_of_steps_after_success` parameter is crucial as it allows you to collect more positive examples. When a success is detected, the system will continue recording for the specified number of steps while maintaining the reward=1 label. Otherwise, there won't be enough states in the dataset labeled to 1 to train a good classifier.
+**Important**: For reward classifier training, set `terminate_on_success: false` to collect sufficient positive examples. For regular HIL-SERL training, keep it as `true` to enable automatic episode termination when the task is completed successfully.
Example configuration section for data collection:
```json
{
+ "env": {
+ "type": "gym_manipulator",
+ "name": "real_robot",
+ "fps": 10,
+ "processor": {
+ "reset": {
+ "reset_time_s": 5.0,
+ "control_time_s": 20.0,
+ "terminate_on_success": false
+ },
+ "gripper": {
+ "use_gripper": true
+ }
+ },
+ "robot": {
+ // ... robot configuration ...
+ },
+ "teleop": {
+ // ... teleoperator configuration ...
+ }
+ },
+ "dataset": {
+ "repo_id": "hf_username/dataset_name",
+ "dataset_root": "data/your_dataset",
+ "task": "reward_classifier_task",
+ "num_episodes_to_record": 20,
+ "replay_episode": null,
+ "push_to_hub": true
+ },
"mode": "record",
- "repo_id": "hf_username/dataset_name",
- "dataset_root": "data/your_dataset",
- "num_episodes": 20,
- "push_to_hub": true,
- "fps": 10,
- "number_of_steps_after_success": 15
+ "device": "cpu"
}
```
@@ -421,9 +724,17 @@ To use your trained reward classifier, configure the `HILSerlRobotEnvConfig` to
```python
-env_config = HILSerlRobotEnvConfig(
- reward_classifier_pretrained_path="path_to_your_pretrained_trained_model",
- # Other environment parameters
+config = GymManipulatorConfig(
+ env=HILSerlRobotEnvConfig(
+ processor=HILSerlProcessorConfig(
+ reward_classifier=RewardClassifierConfig(
+ pretrained_path="path_to_your_pretrained_trained_model"
+ )
+ ),
+ # Other environment parameters
+ ),
+ dataset=DatasetConfig(...),
+ mode=None # For training
)
```
@@ -432,14 +743,25 @@ or set the argument in the json config file.
```json
{
- "reward_classifier_pretrained_path": "path_to_your_pretrained_model"
+ "env": {
+ "processor": {
+ "reward_classifier": {
+ "pretrained_path": "path_to_your_pretrained_model",
+ "success_threshold": 0.7,
+ "success_reward": 1.0
+ },
+ "reset": {
+ "terminate_on_success": true
+ }
+ }
+ }
}
```
Run `gym_manipulator.py` to test the model.
```bash
-python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config.json
+python -m lerobot.rl.gym_manipulator --config_path path/to/env_config.json
```
The reward classifier will automatically provide rewards based on the visual input from the robot's cameras.
@@ -447,12 +769,12 @@ The reward classifier will automatically provide rewards based on the visual inp
**Example Workflow for training the reward classifier**
1. **Create the configuration files**:
- Create the necessary json configuration files for the reward classifier and the environment. Check the examples [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/tree/main).
+ Create the necessary json configuration files for the reward classifier and the environment. Check the examples [here](https://huggingface.co/datasets/lerobot/config_examples/resolve/main/reward_classifier/config.json).
2. **Collect a dataset**:
```bash
- python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
+ python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
```
3. **Train the classifier**:
@@ -463,7 +785,7 @@ The reward classifier will automatically provide rewards based on the visual inp
4. **Test the classifier**:
```bash
- python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
+ python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
```
### Training with Actor-Learner
@@ -472,7 +794,7 @@ The LeRobot system uses a distributed actor-learner architecture for training. T
**Configuration Setup**
-Create a training configuration file (example available [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/blob/main/train_config_hilserl_so100.json)). The training config is based on the main `TrainRLServerPipelineConfig` class in `lerobot/configs/train.py`.
+Create a training configuration file (example available [here](https://huggingface.co/datasets/lerobot/config_examples/resolve/main/rl/train_config.json)). The training config is based on the main `TrainRLServerPipelineConfig` class in `lerobot/configs/train.py`.
1. Configure the policy settings (`type="sac"`, `device`, etc.)
2. Set `dataset` to your cropped dataset
@@ -485,7 +807,7 @@ Create a training configuration file (example available [here](https://huggingfa
First, start the learner server process:
```bash
-python -m lerobot.scripts.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
+python -m lerobot.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
```
The learner:
@@ -500,7 +822,7 @@ The learner:
In a separate terminal, start the actor process with the same configuration:
```bash
-python -m lerobot.scripts.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
+python -m lerobot.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
```
The actor:
diff --git a/docs/source/hilserl_sim.mdx b/docs/source/hilserl_sim.mdx
index c739be835c2..e2dddd9edb1 100644
--- a/docs/source/hilserl_sim.mdx
+++ b/docs/source/hilserl_sim.mdx
@@ -26,15 +26,18 @@ pip install -e ".[hilserl]"
## Configuration
-To use `gym_hil` with LeRobot, you need to create a configuration file. An example is provided [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/blob/main/gym_hil_env.json). Key configuration sections include:
+To use `gym_hil` with LeRobot, you need to create a configuration file. An example is provided [here](https://huggingface.co/datasets/lerobot/config_examples/resolve/main/rl/gym_hil/env_config.json). Key configuration sections include:
### Environment Type and Task
```json
{
- "type": "hil",
- "name": "franka_sim",
- "task": "PandaPickCubeGamepad-v0",
+ "env": {
+ "type": "gym_manipulator",
+ "name": "gym_hil",
+ "task": "PandaPickCubeGamepad-v0",
+ "fps": 10
+ },
"device": "cuda"
}
```
@@ -45,28 +48,40 @@ Available tasks:
- `PandaPickCubeGamepad-v0`: With gamepad control
- `PandaPickCubeKeyboard-v0`: With keyboard control
-### Gym Wrappers Configuration
+### Processor Configuration
```json
-"wrapper": {
- "gripper_penalty": -0.02,
- "control_time_s": 15.0,
- "use_gripper": true,
- "fixed_reset_joint_positions": [0.0, 0.195, 0.0, -2.43, 0.0, 2.62, 0.785],
- "end_effector_step_sizes": {
- "x": 0.025,
- "y": 0.025,
- "z": 0.025
- },
- "control_mode": "gamepad"
+{
+ "env": {
+ "processor": {
+ "control_mode": "gamepad",
+ "gripper": {
+ "use_gripper": true,
+ "gripper_penalty": -0.02
+ },
+ "reset": {
+ "control_time_s": 15.0,
+ "fixed_reset_joint_positions": [
+ 0.0, 0.195, 0.0, -2.43, 0.0, 2.62, 0.785
+ ]
+ },
+ "inverse_kinematics": {
+ "end_effector_step_sizes": {
+ "x": 0.025,
+ "y": 0.025,
+ "z": 0.025
+ }
+ }
}
+ }
+}
```
Important parameters:
-- `gripper_penalty`: Penalty for excessive gripper movement
-- `use_gripper`: Whether to enable gripper control
-- `end_effector_step_sizes`: Size of the steps in the x,y,z axes of the end-effector
+- `gripper.gripper_penalty`: Penalty for excessive gripper movement
+- `gripper.use_gripper`: Whether to enable gripper control
+- `inverse_kinematics.end_effector_step_sizes`: Size of the steps in the x,y,z axes of the end-effector
- `control_mode`: Set to `"gamepad"` to use a gamepad controller
## Running with HIL RL of LeRobot
@@ -75,39 +90,50 @@ Important parameters:
To run the environment, set mode to null:
-
-```python
-python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
+```bash
+python -m lerobot.rl.gym_manipulator --config_path path/to/gym_hil_env.json
```
-
### Recording a Dataset
To collect a dataset, set the mode to `record` whilst defining the repo_id and number of episodes to record:
-
-```python
-python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
+```json
+{
+ "env": {
+ "type": "gym_manipulator",
+ "name": "gym_hil",
+ "task": "PandaPickCubeGamepad-v0"
+ },
+ "dataset": {
+ "repo_id": "username/sim_dataset",
+ "root": null,
+ "task": "pick_cube",
+ "num_episodes_to_record": 10,
+ "replay_episode": null,
+ "push_to_hub": true
+ },
+ "mode": "record"
+}
+```
+
+```bash
+python -m lerobot.rl.gym_manipulator --config_path path/to/gym_hil_env.json
```
-
### Training a Policy
-To train a policy, checkout the configuration example available [here](https://huggingface.co/datasets/aractingi/lerobot-example-config-files/blob/main/train_gym_hil_env.json) and run the actor and learner servers:
+To train a policy, checkout the configuration example available [here](https://huggingface.co/datasets/lerobot/config_examples/resolve/main/rl/gym_hil/train_config.json) and run the actor and learner servers:
-
-```python
-python -m lerobot.scripts.rl.actor --config_path path/to/train_gym_hil_env.json
+```bash
+python -m lerobot.rl.actor --config_path path/to/train_gym_hil_env.json
```
-
In a different terminal, run the learner server:
-
-```python
-python -m lerobot.scripts.rl.learner --config_path path/to/train_gym_hil_env.json
+```bash
+python -m lerobot.rl.learner --config_path path/to/train_gym_hil_env.json
```
-
The simulation environment provides a safe and repeatable way to develop and test your Human-In-the-Loop reinforcement learning components before deploying to real robots.
diff --git a/docs/source/il_robots.mdx b/docs/source/il_robots.mdx
index 905046bef74..91df14028cd 100644
--- a/docs/source/il_robots.mdx
+++ b/docs/source/il_robots.mdx
@@ -200,7 +200,7 @@ from lerobot.teleoperators.so100_leader.config_so100_leader import SO100LeaderCo
from lerobot.teleoperators.so100_leader.so100_leader import SO100Leader
from lerobot.utils.control_utils import init_keyboard_listener
from lerobot.utils.utils import log_say
-from lerobot.utils.visualization_utils import _init_rerun
+from lerobot.utils.visualization_utils import init_rerun
from lerobot.record import record_loop
NUM_EPISODES = 5
@@ -237,7 +237,7 @@ dataset = LeRobotDataset.create(
# Initialize the keyboard listener and rerun visualization
_, events = init_keyboard_listener()
-_init_rerun(session_name="recording")
+init_rerun(session_name="recording")
# Connect the robot and teleoperator
robot.connect()
@@ -517,13 +517,16 @@ from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerCon
from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.utils.control_utils import init_keyboard_listener
from lerobot.utils.utils import log_say
-from lerobot.utils.visualization_utils import _init_rerun
+from lerobot.utils.visualization_utils import init_rerun
from lerobot.record import record_loop
+from lerobot.policies.factory import make_processor
NUM_EPISODES = 5
FPS = 30
EPISODE_TIME_SEC = 60
TASK_DESCRIPTION = "My task description"
+HF_MODEL_ID = "
+
+
+
+### Step 1: Choose the platform
+
+Modify the examples to use `PhoneOS.IOS` or `PhoneOS.ANDROID` in `PhoneConfig`. The API is identical across platforms, only the input source differs. All examples are under `examples/` and have `phone_so100_*.py` variants.
+
+Teleoperation example:
+
+```36:43:examples/phone_so100_teleop.py
+from lerobot.teleoperators.phone.config_phone import PhoneConfig, PhoneOS
+
+teleop_config = PhoneConfig(phone_os=PhoneOS.IOS) # or PhoneOS.ANDROID
+teleop_device = Phone(teleop_config)
+```
+
+### Step 2: Connect and calibrate
+
+When `Phone(teleop_config)` is created and `connect()` is called, calibration is prompted automatically. Hold the phone in the orientation described above, then:
+
+- iOS: press and hold `B1` to capture the reference pose.
+- Android: press `Move` button on the WebXR page to capture the reference pose.
+
+Why calibrate? We capture the current pose so subsequent poses are expressed in a robot aligned frame. When you again press the button to enable control, the position is recaptured to avoid drift when your phone is repositioned while it was disabled.
+
+### Step 3: Run an example
+
+Run on of the examples scripts to teleoperate, record a dataset, replay a dataset or evaluate a policy.
+
+All scripts assume you configured your robot (e.g., SO-100 follower) and set the correct serial port.
+
+Additionally you need to **copy the urdf of the robot to the examples folder**. For the examples in this tutorial (Using SO100/SO101) it is highly recommended to use the urdf in the [SO-ARM100 repo](https://github.com/TheRobotStudio/SO-ARM100/blob/main/Simulation/SO101/so101_new_calib.urdf)
+
+- Run this example to teleoperate:
+
+ ```bash
+ python examples/phone_to_so100/teleoperate.py
+ ```
+
+After running the example:
+
+- Android: after starting the script, open the printed local URL on your phone, tap Start, then press and hold Move.
+- iOS: open HEBI Mobile I/O first; B1 enables motion. A3 controls the gripper.
+
+Additionally you can customize mapping or safety limits by editing the processor steps shown in the examples. You can also remap inputs (e.g., use a different analog input) or adapt the pipeline to other robots (e.g., LeKiwi) by modifying the input and kinematics steps. More about this in the [Processors for Robots and Teleoperators](./processors_robots_teleop) guide.
+
+- Run this example to record a dataset, which saves absolute end effector observations and actions:
+
+ ```bash
+ python examples/phone_to_so100/record.py
+ ```
+
+- Run this example to replay recorded episodes:
+
+ ```bash
+ python examples/phone_to_so100/replay.py
+ ```
+
+- Run this example to evaluate a pretrained policy:
+
+ ```bash
+ python examples/phone_to_so100/evaluate.py
+ ```
+
+### Important pipeline steps and options
+
+- Kinematics are used in multiple steps. We use [Placo](https://github.com/Rhoban/placo) which is a wrapper around Pinocchio for handling our kinematics. We construct the kinematics object by passing the robot's URDF and target frame. We set `target_frame_name` to the gripper frame.
+
+ ```examples/phone_to_so100/teleoperate.py
+ kinematics_solver = RobotKinematics(
+ urdf_path="./SO101/so101_new_calib.urdf",
+ target_frame_name="gripper_frame_link",
+ joint_names=list(robot.bus.motors.keys()),
+ )
+
+ ```
+
+- The `MapPhoneActionToRobotAction` step converts the calibrated phone pose and inputs into target deltas and gripper commands, below is shown what the step outputs.
+
+ ```src/lerobot/teleoperators/phone/phone_processor.py
+ action["enabled"] = enabled
+ action["target_x"] = -pos[1] if enabled else 0.0
+ action["target_y"] = pos[0] if enabled else 0.0
+ action["target_z"] = pos[2] if enabled else 0.0
+ action["target_wx"] = rotvec[1] if enabled else 0.0
+ action["target_wy"] = rotvec[0] if enabled else 0.0
+ action["target_wz"] = -rotvec[2] if enabled else 0.0
+ action["gripper_vel"] = gripper_vel # Still send gripper action when disabled
+ ```
+
+- The `EEReferenceAndDelta` step converts target deltas to an absolute desired EE pose, storing a reference on enable, the `end_effector_step_sizes` are the step sizes for the EE pose and can be modified to change the motion speed.
+
+ ```examples/phone_to_so100/teleoperate.py
+ EEReferenceAndDelta(
+ kinematics=kinematics_solver,
+ end_effector_step_sizes={"x": 0.5, "y": 0.5, "z": 0.5},
+ motor_names=list(robot.bus.motors.keys()),
+ use_latched_reference=True,
+ ),
+ ```
+
+- The `EEBoundsAndSafety` step clamps EE motion to a workspace and checks for large ee step jumps to ensure safety. The `end_effector_bounds` are the bounds for the EE pose and can be modified to change the workspace. The `max_ee_step_m` are the step limits for the EE pose and can be modified to change the safety limits.
+
+ ```examples/phone_to_so100/teleoperate.py
+ EEBoundsAndSafety(
+ end_effector_bounds={"min": [-1.0, -1.0, -1.0], "max": [1.0, 1.0, 1.0]},
+ max_ee_step_m=0.10,
+ )
+ ```
+
+- The `GripperVelocityToJoint` step turns a velocity‑like gripper input into absolute gripper position using the current measured state. The `speed_factor` is the factor by which the velocity is multiplied.
+
+ ```examples/phone_to_so100/teleoperate.py
+ GripperVelocityToJoint(speed_factor=20.0)
+ ```
+
+#### Different IK initial guesses
+
+We use different IK initial guesses in the kinematic steps. As initial guess either the current measured joints or the previous IK solution is used.
+
+- Closed loop (used in record/eval): sets `initial_guess_current_joints=True` so IK starts from the measured joints each frame.
+
+ ```examples/phone_to_so100/record.py
+ InverseKinematicsEEToJoints(
+ kinematics=kinematics_solver,
+ motor_names=list(robot.bus.motors.keys()),
+ initial_guess_current_joints=True, # closed loop
+ )
+ ```
+
+- Open loop (used in replay): sets `initial_guess_current_joints=False` so IK continues from the previous IK solution rather than the measured state. This preserves action stability when we replay without feedback.
+
+ ```examples/phone_to_so100/replay.py
+ InverseKinematicsEEToJoints(
+ kinematics=kinematics_solver,
+ motor_names=list(robot.bus.motors.keys()),
+ initial_guess_current_joints=False, # open loop
+ )
+ ```
+
+### Pipeline steps explained
+
+- MapPhoneActionToRobotAction: converts calibrated phone pose and inputs into target deltas and a gripper command. Motion is gated by an enable signal (B1 on iOS, Move on Android).
+- EEReferenceAndDelta: latches a reference EE pose on enable and combines it with target deltas to produce an absolute desired EE pose each frame. When disabled, it keeps sending the last commanded pose.
+- EEBoundsAndSafety: clamps the EE pose to a workspace and rate‑limits jumps for safety. Also declares `action.ee.*` features.
+- InverseKinematicsEEToJoints: turns an EE pose into joint positions with IK. `initial_guess_current_joints=True` is recommended for closed‑loop control; set `False` for open‑loop replay for stability.
+- GripperVelocityToJoint: integrates a velocity‑like gripper input into an absolute gripper position using the current measured state.
+- ForwardKinematicsJointsToEE: computes `observation.state.ee.*` from observed joints for logging and training on EE state.
+
+### Troubleshooting
+
+- iOS not discovered: ensure HEBI Mobile I/O is open and your laptop/phone are on the same network.
+- Android URL not reachable: check local you used `https` instead of `http`, use the exact IP printed by the script and allow your browser to enter and ignore the certificate issue.
+- Motion feels inverted: adjust the sign flips in `MapPhoneActionToRobotAction` or swap axes to match your setup.
diff --git a/docs/source/pi0.mdx b/docs/source/pi0.mdx
new file mode 100644
index 00000000000..d36fe0ce4c5
--- /dev/null
+++ b/docs/source/pi0.mdx
@@ -0,0 +1,79 @@
+# π₀ (Pi0)
+
+π₀ is a **Vision-Language-Action model for general robot control**, from Physical Intelligence. The LeRobot implementation is adapted from their open source [OpenPI](https://github.com/Physical-Intelligence/openpi) repository.
+
+## Model Overview
+
+π₀ represents a breakthrough in robotics as the first general-purpose robot foundation model developed by [Physical Intelligence](https://www.physicalintelligence.company/blog/pi0). Unlike traditional robot programs that are narrow specialists programmed for repetitive motions, π₀ is designed to be a generalist policy that can understand visual inputs, interpret natural language instructions, and control a variety of different robots across diverse tasks.
+
+### The Vision for Physical Intelligence
+
+As described by Physical Intelligence, while AI has achieved remarkable success in digital domains, from chess-playing to drug discovery, human intelligence still dramatically outpaces AI in the physical world. To paraphrase Moravec's paradox, winning a game of chess represents an "easy" problem for AI, but folding a shirt or cleaning up a table requires solving some of the most difficult engineering problems ever conceived. π₀ represents a first step toward developing artificial physical intelligence that enables users to simply ask robots to perform any task they want, just like they can with large language models.
+
+### Architecture and Approach
+
+π₀ combines several key innovations:
+
+- **Flow Matching**: Uses a novel method to augment pre-trained VLMs with continuous action outputs via flow matching (a variant of diffusion models)
+- **Cross-Embodiment Training**: Trained on data from 8 distinct robot platforms including UR5e, Bimanual UR5e, Franka, Bimanual Trossen, Bimanual ARX, Mobile Trossen, and Mobile Fibocom
+- **Internet-Scale Pre-training**: Inherits semantic knowledge from a pre-trained 3B parameter Vision-Language Model
+- **High-Frequency Control**: Outputs motor commands at up to 50 Hz for real-time dexterous manipulation
+
+## Installation Requirements
+
+1. Install LeRobot by following our [Installation Guide](./installation).
+2. Install Pi0 dependencies by running:
+
+ ```bash
+ pip install -e ".[pi]"
+ ```
+
+## Training Data and Capabilities
+
+π₀ is trained on the largest robot interaction dataset to date, combining three key data sources:
+
+1. **Internet-Scale Pre-training**: Vision-language data from the web for semantic understanding
+2. **Open X-Embodiment Dataset**: Open-source robot manipulation datasets
+3. **Physical Intelligence Dataset**: Large and diverse dataset of dexterous tasks across 8 distinct robots
+
+## Usage
+
+To use π₀ in LeRobot, specify the policy type as:
+
+```python
+policy.type=pi0
+```
+
+## Training
+
+For training π₀, you can use the standard LeRobot training script with the appropriate configuration:
+
+```bash
+python src/lerobot/scripts/lerobot_train.py \
+ --dataset.repo_id=your_dataset \
+ --policy.type=pi0 \
+ --output_dir=./outputs/pi0_training \
+ --job_name=pi0_training \
+ --policy.pretrained_path=lerobot/pi0_base \
+ --policy.repo_id=your_repo_id \
+ --policy.compile_model=true \
+ --policy.gradient_checkpointing=true \
+ --policy.dtype=bfloat16 \
+ --steps=3000 \
+ --policy.device=cuda \
+ --batch_size=32
+```
+
+### Key Training Parameters
+
+- **`--policy.compile_model=true`**: Enables model compilation for faster training
+- **`--policy.gradient_checkpointing=true`**: Reduces memory usage significantly during training
+- **`--policy.dtype=bfloat16`**: Use mixed precision training for efficiency
+- **`--batch_size=32`**: Batch size for training, adapt this based on your GPU memory
+- **`--policy.pretrained_path=lerobot/pi0_base`**: The base π₀ model you want to finetune, options are:
+ - [lerobot/pi0_base](https://huggingface.co/lerobot/pi0_base)
+ - [lerobot/pi0_libero](https://huggingface.co/lerobot/pi0_libero) (specifically trained on the Libero dataset)
+
+## License
+
+This model follows the **Apache 2.0 License**, consistent with the original [OpenPI repository](https://github.com/Physical-Intelligence/openpi).
diff --git a/docs/source/pi05.mdx b/docs/source/pi05.mdx
new file mode 100644
index 00000000000..b6267fc5e30
--- /dev/null
+++ b/docs/source/pi05.mdx
@@ -0,0 +1,107 @@
+# π₀.₅ (Pi05) Policy
+
+π₀.₅ is a **Vision-Language-Action model with open-world generalization**, from Physical Intelligence. The LeRobot implementation is adapted from their open source [OpenPI](https://github.com/Physical-Intelligence/openpi) repository.
+
+## Model Overview
+
+π₀.₅ represents a significant evolution from π₀, developed by [Physical Intelligence](https://www.physicalintelligence.company/blog/pi05) to address a big challenge in robotics: **open-world generalization**. While robots can perform impressive tasks in controlled environments, π₀.₅ is designed to generalize to entirely new environments and situations that were never seen during training.
+
+### The Generalization Challenge
+
+As Physical Intelligence explains, the fundamental challenge isn't performing tasks of agility or dexterity, but generalization, the ability to correctly perform tasks in new settings with new objects. Consider a robot cleaning different homes: each home has different objects in different places. Generalization must occur at multiple levels:
+
+- **Physical Level**: Understanding how to pick up a spoon (by the handle) or plate (by the edge), even with unseen objects in cluttered environments
+- **Semantic Level**: Understanding task semantics, where to put clothes and shoes (laundry hamper, not on the bed), and what tools are appropriate for cleaning spills
+- **Environmental Level**: Adapting to "messy" real-world environments like homes, grocery stores, offices, and hospitals
+
+### Co-Training on Heterogeneous Data
+
+The breakthrough innovation in π₀.₅ is **co-training on heterogeneous data sources**. The model learns from:
+
+1. **Multimodal Web Data**: Image captioning, visual question answering, object detection
+2. **Verbal Instructions**: Humans coaching robots through complex tasks step-by-step
+3. **Subtask Commands**: High-level semantic behavior labels (e.g., "pick up the pillow" for an unmade bed)
+4. **Cross-Embodiment Robot Data**: Data from various robot platforms with different capabilities
+5. **Multi-Environment Data**: Static robots deployed across many different homes
+6. **Mobile Manipulation Data**: ~400 hours of mobile robot demonstrations
+
+This diverse training mixture creates a "curriculum" that enables generalization across physical, visual, and semantic levels simultaneously.
+
+## Installation Requirements
+
+1. Install LeRobot by following our [Installation Guide](./installation).
+2. Install Pi0.5 dependencies by running:
+
+ ```bash
+ pip install -e ".[pi]"
+ ```
+
+## Usage
+
+To use π₀.₅ in your LeRobot configuration, specify the policy type as:
+
+```python
+policy.type=pi05
+```
+
+## Training
+
+### Training Command Example
+
+Here's a complete training command for finetuning the base π₀.₅ model on your own dataset:
+
+```bash
+python src/lerobot/scripts/lerobot_train.py\
+ --dataset.repo_id=your_dataset \
+ --policy.type=pi05 \
+ --output_dir=./outputs/pi05_training \
+ --job_name=pi05_training \
+ --policy.repo_id=your_repo_id \
+ --policy.pretrained_path=lerobot/pi05_base \
+ --policy.compile_model=true \
+ --policy.gradient_checkpointing=true \
+ --wandb.enable=true \
+ --policy.dtype=bfloat16 \
+ --steps=3000 \
+ --policy.device=cuda \
+ --batch_size=32
+```
+
+### Key Training Parameters
+
+- **`--policy.compile_model=true`**: Enables model compilation for faster training
+- **`--policy.gradient_checkpointing=true`**: Reduces memory usage significantly during training
+- **`--policy.dtype=bfloat16`**: Use mixed precision training for efficiency
+- **`--batch_size=32`**: Batch size for training, adapt this based on your GPU memory
+- **`--policy.pretrained_path=lerobot/pi05_base`**: The base π₀.₅ model you want to finetune, options are:
+ - [lerobot/pi05_base](https://huggingface.co/lerobot/pi05_base)
+ - [lerobot/pi05_libero](https://huggingface.co/lerobot/pi05_libero) (specifically trained on the Libero dataset)
+
+If your dataset is not converted with `quantiles`, you can convert it with the following command:
+
+```bash
+python src/lerobot/datasets/v30/augment_dataset_quantile_stats.py \
+ --repo-id=your_dataset \
+```
+
+Or train pi05 with this normalization mapping: `--policy.normalization_mapping='{"ACTION": "MEAN_STD", "STATE": "MEAN_STD", "VISUAL": "IDENTITY"}'`
+
+## Performance Results
+
+### Libero Benchmark Results
+
+π₀.₅ has demonstrated strong performance on the Libero benchmark suite. To compare and test its LeRobot implementation, we finetuned the libero base model for an additional 6k steps on the Libero dataset and compared the results to the OpenPI reference results.
+
+| Benchmark | LeRobot Implementation | OpenPI Reference |
+| ------------------ | ---------------------- | ---------------- |
+| **Libero Spatial** | 97.0% | 98.8% |
+| **Libero Object** | 99.0% | 98.2% |
+| **Libero Goal** | 98.0% | 98.0% |
+| **Libero 10** | 96.0% | 92.4% |
+| **Average** | 97.5% | 96.85% |
+
+These results demonstrate π₀.₅'s strong generalization capabilities across diverse robotic manipulation tasks. To reproduce these results, you can follow the instructions in the [Libero](https://huggingface.co/docs/lerobot/libero) section.
+
+## License
+
+This model follows the **Apache 2.0 License**, consistent with the original [OpenPI repository](https://github.com/Physical-Intelligence/openpi).
diff --git a/docs/source/processors_robots_teleop.mdx b/docs/source/processors_robots_teleop.mdx
new file mode 100644
index 00000000000..3d8dcb409f1
--- /dev/null
+++ b/docs/source/processors_robots_teleop.mdx
@@ -0,0 +1,151 @@
+# Processors for Robots and Teleoperators
+
+This guide shows how to build and modify processing pipelines that connect teleoperators (e.g., phone) to robots and datasets. Pipelines standardize conversions between different action/observation spaces so you can swap teleops and robots without rewriting glue code.
+
+We use the Phone to SO‑100 follower examples for concreteness, but the same patterns apply to other robots.
+
+**What you'll learn**
+
+- Absolute vs. relative EE control: What each means, trade‑offs, and how to choose for your task.
+- Three-pipeline pattern: How to map teleop actions → dataset actions → robot commands, and robot observations → dataset observations.
+- Adapters (`to_transition` / `to_output`): How these convert raw dicts to `EnvTransition` and back to reduce boilerplate.
+- Dataset feature contracts: How steps declare features via `transform_features(...)`, and how to aggregate/merge them for recording.
+- Choosing a representation: When to store joints, absolute EE poses, or relative EE deltas—and how that affects training.
+- Pipeline customization guidance: How to swap robots/URDFs safely and tune bounds, step sizes, and options like IK initialization.
+
+### Absolute vs relative EE control
+
+The examples in this guide use absolute end effector (EE) poses because they are easy to reason about. In practice, relative EE deltas or joint position are often preferred as learning features.
+
+With processors, you choose the learning features you want to use for your policy. This could be joints positions/velocities, absolute EE, or relative EE positions. You can also choose to store other features, such as joint torques, motor currents, etc.
+
+## Three pipelines
+
+We often compose three pipelines. Depending on your setup, some can be empty if action and observation spaces already match.
+Each of these pipelines handle different conversions between different action and observation spaces. Below is a quick explanation of each pipeline.
+
+1. Pipeline 1: Teleop action space → dataset action space (phone pose → EE targets)
+2. Pipeline 2: Dataset action space → robot command space (EE targets → joints)
+3. Pipeline 3: Robot observation space → dataset observation space (joints → EE pose)
+
+Below is an example of the three pipelines that we use in the phone to SO-100 follower examples:
+
+```69:90:examples/phone_so100_record.py
+phone_to_robot_ee_pose_processor = RobotProcessorPipeline[RobotAction, RobotAction]( # teleop -> dataset action
+ steps=[
+ MapPhoneActionToRobotAction(platform=teleop_config.phone_os),
+ EEReferenceAndDelta(
+ kinematics=kinematics_solver, end_effector_step_sizes={"x": 0.5, "y": 0.5, "z": 0.5}, motor_names=list(robot.bus.motors.keys()),
+ ),
+ EEBoundsAndSafety(
+ end_effector_bounds={"min": [-1.0, -1.0, -1.0], "max": [1.0, 1.0, 1.0]}, max_ee_step_m=0.20,
+ ),
+ GripperVelocityToJoint(),
+ ],
+ to_transition=robot_action_to_transition,
+ to_output=transition_to_robot_action,
+)
+
+robot_ee_to_joints_processor = RobotProcessorPipeline[RobotAction, RobotAction]( # dataset action -> robot
+ steps=[
+ InverseKinematicsEEToJoints(
+ kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys()), initial_guess_current_joints=True,
+ ),
+ ],
+ to_transition=robot_action_to_transition,
+ to_output=transition_to_robot_action,
+)
+
+robot_joints_to_ee_pose = RobotProcessorPipeline[RobotObservation, RobotObservation]( # robot obs -> dataset obs
+ steps=[
+ ForwardKinematicsJointsToEE(kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys()))
+ ],
+ to_transition=observation_to_transition,
+ to_output=transition_to_observation,
+)
+```
+
+## Why to_transition / to_output
+
+To convert from robot/teleoperator to pipeline and back, we use the `to_transition` and `to_output` pipeline adapters.
+They standardize conversions to reduce boilerplate code, and form the bridge between the robot and teleoperators raw dictionaries and the pipeline’s `EnvTransition` format.
+In the phone to SO-100 follower examples we use the following adapters:
+
+- `robot_action_to_transition`: transforms the teleop action dict to a pipeline transition.
+- `transition_to_robot_action`: transforms the pipeline transition to a robot action dict.
+- `observation_to_transition`: transforms the robot observation dict to a pipeline transition.
+- `transition_to_observation`: transforms the pipeline transition to a observation dict.
+
+Checkout [src/lerobot/processor/converters.py](https://github.com/huggingface/lerobot/blob/main/src/lerobot/processor/converters.py) for more details.
+
+## Dataset feature contracts
+
+Dataset features are determined by the keys saved in the dataset. Each step can declare what features it modifies in a contract called `transform_features(...)`. Once you build a processor, the processor can then aggregate all of these features with `aggregate_pipeline_dataset_features()` and merge multiple feature dicts with `combine_feature_dicts(...)`.
+
+Below is and example of how we declare features with the `transform_features` method in the phone to SO-100 follower examples:
+
+```src/lerobot/robots/so100_follower/robot_kinematic_processor.py
+ def transform_features(
+ self, features: dict[PipelineFeatureType, dict[str, PolicyFeature]]
+ ) -> dict[PipelineFeatureType, dict[str, PolicyFeature]]:
+ # We only use the ee pose in the dataset, so we don't need the joint positions
+ for n in self.motor_names:
+ features[PipelineFeatureType.ACTION].pop(f"{n}.pos", None)
+ # We specify the dataset features of this step that we want to be stored in the dataset
+ for k in ["x", "y", "z", "wx", "wy", "wz", "gripper_pos"]:
+ features[PipelineFeatureType.ACTION][f"ee.{k}"] = PolicyFeature(
+ type=FeatureType.STATE, shape=(1,)
+ )
+ return features
+```
+
+Here we declare what PolicyFeatures we modify in this step, so we know what features we can expect when we run the processor. These features can then be aggregated and used to create the dataset features.
+
+Below is an example of how we aggregate and merge features in the phone to SO-100 record example:
+
+```121:145:examples/phone_so100_record.py
+features=combine_feature_dicts(
+ # Run the feature contract of the pipelines
+ # This tells you how the features would look like after the pipeline steps
+ aggregate_pipeline_dataset_features(
+ pipeline=phone_to_robot_ee_pose_processor,
+ initial_features=create_initial_features(action=phone.action_features), # <- Action features we can expect, these come from our teleop device (phone) and action processor
+ use_videos=True,
+ ),
+ aggregate_pipeline_dataset_features(
+ pipeline=robot_joints_to_ee_pose,
+ initial_features=create_initial_features(observation=robot.observation_features), # <- Observation features we can expect, these come from our robot and observation processor
+ use_videos=True,
+ patterns=["observation.state.ee"], # <- Here you could optionally filter the features we want to store in the dataset, with a specific pattern
+
+ ),
+ ),
+```
+
+How it works:
+
+- `aggregate_pipeline_dataset_features(...)`: applies `transform_features` across the pipeline and filters by patterns (images included when `use_videos=True`, and state features included when `patterns` is specified).
+- `combine_feature_dicts(...)`: combine multiple feature dicts.
+- Recording with `record_loop(...)` uses `build_dataset_frame(...)` to build frames consistent with `dataset.features` before we call `add_frame(...)` to add the frame to the dataset.
+
+## Guidance when customizing robot pipelines
+
+You can store any of the following features as your action/observation space:
+
+- Joint positions
+- Absolute EE poses
+- Relative EE deltas
+- Other features: joint velocity, torques, etc.
+
+Pick what you want to use for your policy action and observation space and configure/modify the pipelines and steps accordingly.
+
+### Different robots
+
+- You can easily reuse pipelines, for example to use another robot with phone teleop, modify the examples and swap the robot `RobotKinematics` (URDF) and `motor_names` to use your own robot with Phone teleop. Additionally you should ensure `target_frame_name` points to your gripper/wrist.
+
+### Safety first
+
+- When changing pipelines, start with tight bounds, implement safety steps when working with real robots.
+- Its advised to start with simulation first and then move to real robots.
+
+Thats it! We hope this guide helps you get started with customizing your robot pipelines, If you run into any issues at any point, jump into our [Discord community](https://discord.com/invite/s3KuuzsPFb) for support.
diff --git a/docs/source/smolvla.mdx b/docs/source/smolvla.mdx
index 89c475a90cb..a56298b5ecb 100644
--- a/docs/source/smolvla.mdx
+++ b/docs/source/smolvla.mdx
@@ -1,4 +1,4 @@
-# Finetune SmolVLA
+# SmolVLA
SmolVLA is Hugging Face’s lightweight foundation model for robotics. Designed for easy fine-tuning on LeRobot datasets, it helps accelerate your development!
@@ -29,7 +29,7 @@ SmolVLA is Hugging Face’s lightweight foundation model for robotics. Designed
## Collect a dataset
SmolVLA is a base model, so fine-tuning on your own data is required for optimal performance in your setup.
-We recommend recording ~50 episodes of your task as a starting point. Follow our guide to get started: [Recording a Dataset](https://huggingface.co/docs/lerobot/getting_started_real_world_robot#record-a-dataset)
+We recommend recording ~50 episodes of your task as a starting point. Follow our guide to get started: [Recording a Dataset](./il_robots)