forked from tangger/lerobot
Update pre-commit-config.yaml + pyproject.toml + ceil rerun & transformer dependencies version (#1520)
* chore: update .gitignore * chore: update pre-commit * chore(deps): update pyproject * fix(ci): multiple fixes * chore: pre-commit apply * chore: address review comments * Update pyproject.toml Co-authored-by: Ben Zhang <5977478+ben-z@users.noreply.github.com> Signed-off-by: Steven Palma <imstevenpmwork@ieee.org> * chore(deps): add todo --------- Signed-off-by: Steven Palma <imstevenpmwork@ieee.org> Co-authored-by: Ben Zhang <5977478+ben-z@users.noreply.github.com>
This commit is contained in:
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,33 +1,40 @@
|
|||||||
## What this does
|
## What this does
|
||||||
|
|
||||||
Explain what this PR does. Feel free to tag your PR with the appropriate label(s).
|
Explain what this PR does. Feel free to tag your PR with the appropriate label(s).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Title | Label |
|
| Title | Label |
|
||||||
|----------------------|-----------------|
|
|----------------------|-----------------|
|
||||||
| Fixes #[issue] | (🐛 Bug) |
|
| Fixes #[issue] | (🐛 Bug) |
|
||||||
| Adds new dataset | (🗃️ Dataset) |
|
| Adds new dataset | (🗃️ Dataset) |
|
||||||
| Optimizes something | (⚡️ Performance) |
|
| Optimizes something | (⚡️ Performance) |
|
||||||
|
|
||||||
## How it was tested
|
## How it was tested
|
||||||
|
|
||||||
Explain/show how you tested your changes.
|
Explain/show how you tested your changes.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- Added `test_something` in `tests/test_stuff.py`.
|
- Added `test_something` in `tests/test_stuff.py`.
|
||||||
- Added `new_feature` and checked that training converges with policy X on dataset/environment Y.
|
- Added `new_feature` and checked that training converges with policy X on dataset/environment Y.
|
||||||
- Optimized `some_function`, it now runs X times faster than previously.
|
- Optimized `some_function`, it now runs X times faster than previously.
|
||||||
|
|
||||||
## How to checkout & try? (for the reviewer)
|
## How to checkout & try? (for the reviewer)
|
||||||
|
|
||||||
Provide a simple way for the reviewer to try out your changes.
|
Provide a simple way for the reviewer to try out your changes.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest -sx tests/test_stuff.py::test_something
|
pytest -sx tests/test_stuff.py::test_something
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train --some.option=true
|
python -m lerobot.scripts.train --some.option=true
|
||||||
```
|
```
|
||||||
|
|
||||||
## SECTION TO REMOVE BEFORE SUBMITTING YOUR PR
|
## SECTION TO REMOVE BEFORE SUBMITTING YOUR PR
|
||||||
|
|
||||||
**Note**: Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
|
**Note**: Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
|
||||||
members/contributors who may be interested in your PR. Try to avoid tagging more than 3 people.
|
members/contributors who may be interested in your PR. Try to avoid tagging more than 3 people.
|
||||||
|
|
||||||
|
|||||||
282
.gitignore
vendored
282
.gitignore
vendored
@@ -12,164 +12,164 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Dev scripts
|
### Environments & Dependencies ###
|
||||||
.dev
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
logs
|
|
||||||
tmp
|
|
||||||
wandb
|
|
||||||
|
|
||||||
# Data
|
|
||||||
data
|
|
||||||
outputs
|
|
||||||
|
|
||||||
# Apple
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# VS Code
|
|
||||||
.vscode
|
|
||||||
.devcontainer
|
|
||||||
|
|
||||||
# HPC
|
|
||||||
nautilus/*.yaml
|
|
||||||
*.key
|
|
||||||
|
|
||||||
# Slurm
|
|
||||||
sbatch*.sh
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
pip-wheel-metadata/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# uv/poetry lock files
|
|
||||||
poetry.lock
|
|
||||||
uv.lock
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
!tests/artifacts
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
|
|
||||||
# Ignore .cache
|
|
||||||
.cache/*
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
.python-version
|
||||||
|
__pypackages__/
|
||||||
|
node_modules/
|
||||||
|
|
||||||
# Spyder project settings
|
# Lock files
|
||||||
|
poetry.lock
|
||||||
|
uv.lock
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
### Build & Distribution ###
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
sdist/
|
||||||
|
wheels/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
parts/
|
||||||
|
var/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
develop-eggs/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
### Compiled & Cached Files ###
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
*.sage.py
|
||||||
|
.cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.pyre/
|
||||||
|
.pytype/
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
### Testing & Coverage ###
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.pytest_cache/
|
||||||
|
.hypothesis/
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
!tests/artifacts
|
||||||
|
|
||||||
|
### Logs & Temporary Files ###
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
|
*.log
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
### IDE & Editor Config ###
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
.devcontainer/
|
||||||
|
|
||||||
|
# JetBrains / PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Spyder
|
||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|
||||||
# Rope project settings
|
# Rope
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
# mkdocs documentation
|
# Vim
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Other
|
||||||
|
*~
|
||||||
|
|
||||||
|
### OS Specific ###
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
### Framework & Tool Specific ###
|
||||||
|
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Django
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Jupyter
|
||||||
|
.ipynb_checkpoints/
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# Sphinx
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# MkDocs
|
||||||
/site
|
/site
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
### HPC & Slurm ###
|
||||||
.pyre/
|
nautilus/*.yaml
|
||||||
|
*.key
|
||||||
|
sbatch*.sh
|
||||||
|
|
||||||
# pytype static type analyzer
|
### Miscellaneous ###
|
||||||
.pytype/
|
# W&B
|
||||||
|
wandb/
|
||||||
|
|
||||||
# Cython debug symbols
|
# Dev scripts
|
||||||
cython_debug/
|
.dev/
|
||||||
|
|
||||||
|
# Data folders
|
||||||
|
data/
|
||||||
|
outputs/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Dev folders
|
||||||
|
.cache/*
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
exclude: "tests/artifacts/.*\\.safetensors$"
|
|
||||||
default_language_version:
|
default_language_version:
|
||||||
python: python3.10
|
python: python3.10
|
||||||
|
|
||||||
|
exclude: "tests/artifacts/.*\\.safetensors$"
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
##### Meta #####
|
##### Meta #####
|
||||||
- repo: meta
|
- repo: meta
|
||||||
@@ -22,12 +24,12 @@ repos:
|
|||||||
- id: check-useless-excludes
|
- id: check-useless-excludes
|
||||||
- id: check-hooks-apply
|
- id: check-hooks-apply
|
||||||
|
|
||||||
|
##### General Code Quality & Formatting #####
|
||||||
##### Style / Misc. #####
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
args: ['--maxkb=1024']
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
@@ -36,7 +38,14 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
|
||||||
- repo: https://github.com/adhtruong/mirrors-typos
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.11.13
|
||||||
|
hooks:
|
||||||
|
- id: ruff-format
|
||||||
|
- id: ruff
|
||||||
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
||||||
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.34.0
|
rev: v1.34.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
@@ -46,14 +55,16 @@ repos:
|
|||||||
rev: v3.20.0
|
rev: v3.20.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
|
args: [--py310-plus]
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
##### Markdown Quality #####
|
||||||
rev: v0.12.3
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v4.0.0-alpha.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: prettier
|
||||||
args: [--fix]
|
name: Format Markdown with Prettier
|
||||||
- id: ruff-format
|
types_or: [markdown, mdx]
|
||||||
|
args: [--prose-wrap=preserve]
|
||||||
|
|
||||||
##### Security #####
|
##### Security #####
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
@@ -72,3 +83,25 @@ repos:
|
|||||||
- id: bandit
|
- id: bandit
|
||||||
args: ["-c", "pyproject.toml"]
|
args: ["-c", "pyproject.toml"]
|
||||||
additional_dependencies: ["bandit[toml]"]
|
additional_dependencies: ["bandit[toml]"]
|
||||||
|
|
||||||
|
# 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]
|
||||||
|
|
||||||
|
##### Docstring Checks #####
|
||||||
|
# - repo: https://github.com/akaihola/darglint2
|
||||||
|
# rev: v1.8.2
|
||||||
|
# hooks:
|
||||||
|
# - id: darglint2
|
||||||
|
# args: ["--docstring-style", "google", "-v", "2"]
|
||||||
|
# exclude: ^tests/.*$
|
||||||
|
|
||||||
|
# - repo: https://github.com/econchick/interrogate
|
||||||
|
# rev: 1.7.0
|
||||||
|
# hooks:
|
||||||
|
# - id: interrogate
|
||||||
|
# args: ["-vv", "--config=pyproject.toml"]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Contributor Covenant Code of Conduct
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
@@ -18,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community include:
|
community include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
- Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
- Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
- Focusing on what is best not just for us as individuals, but for the overall
|
||||||
community
|
community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
any kind
|
any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email address,
|
- Publishing others' private information, such as a physical or email address,
|
||||||
without their explicit permission
|
without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ Whichever way you choose to contribute, please be mindful to respect our
|
|||||||
## You can contribute in so many ways!
|
## You can contribute in so many ways!
|
||||||
|
|
||||||
Some of the ways you can contribute to 🤗 LeRobot:
|
Some of the ways you can contribute to 🤗 LeRobot:
|
||||||
* Fixing outstanding issues with the existing code.
|
|
||||||
* Implementing new models, datasets or simulation environments.
|
- Fixing outstanding issues with the existing code.
|
||||||
* Contributing to the examples or to the documentation.
|
- Implementing new models, datasets or simulation environments.
|
||||||
* Submitting issues related to bugs or desired new features.
|
- Contributing to the examples or to the documentation.
|
||||||
|
- Submitting issues related to bugs or desired new features.
|
||||||
|
|
||||||
Following the guides below, feel free to open issues and PRs and to coordinate your efforts with the community on our [Discord Channel](https://discord.gg/VjFz58wn3R). For specific inquiries, reach out to [Remi Cadene](mailto:remi.cadene@huggingface.co).
|
Following the guides below, feel free to open issues and PRs and to coordinate your efforts with the community on our [Discord Channel](https://discord.gg/VjFz58wn3R). For specific inquiries, reach out to [Remi Cadene](mailto:remi.cadene@huggingface.co).
|
||||||
|
|
||||||
@@ -40,24 +41,26 @@ already reported** (use the search bar on Github under Issues).
|
|||||||
|
|
||||||
Did not find it? :( So we can act quickly on it, please follow these steps:
|
Did not find it? :( So we can act quickly on it, please follow these steps:
|
||||||
|
|
||||||
* Include your **OS type and version**, the versions of **Python** and **PyTorch**.
|
- Include your **OS type and version**, the versions of **Python** and **PyTorch**.
|
||||||
* A short, self-contained, code snippet that allows us to reproduce the bug in
|
- A short, self-contained, code snippet that allows us to reproduce the bug in
|
||||||
less than 30s.
|
less than 30s.
|
||||||
* The full traceback if an exception is raised.
|
- The full traceback if an exception is raised.
|
||||||
* Attach any other additional information, like screenshots, you think may help.
|
- Attach any other additional information, like screenshots, you think may help.
|
||||||
|
|
||||||
### Do you want a new feature?
|
### Do you want a new feature?
|
||||||
|
|
||||||
A good feature request addresses the following points:
|
A good feature request addresses the following points:
|
||||||
|
|
||||||
1. Motivation first:
|
1. Motivation first:
|
||||||
* Is it related to a problem/frustration with the library? If so, please explain
|
|
||||||
|
- Is it related to a problem/frustration with the library? If so, please explain
|
||||||
why. Providing a code snippet that demonstrates the problem is best.
|
why. Providing a code snippet that demonstrates the problem is best.
|
||||||
* Is it related to something you would need for a project? We'd love to hear
|
- Is it related to something you would need for a project? We'd love to hear
|
||||||
about it!
|
about it!
|
||||||
* Is it something you worked on and think could benefit the community?
|
- Is it something you worked on and think could benefit the community?
|
||||||
Awesome! Tell us what problem it solved for you.
|
Awesome! Tell us what problem it solved for you.
|
||||||
2. Write a *paragraph* describing the feature.
|
|
||||||
|
2. Write a _paragraph_ describing the feature.
|
||||||
3. Provide a **code snippet** that demonstrates its future use.
|
3. Provide a **code snippet** that demonstrates its future use.
|
||||||
4. In case this is related to a paper, please attach a link.
|
4. In case this is related to a paper, please attach a link.
|
||||||
5. Attach any additional information (drawings, screenshots, etc.) you think may help.
|
5. Attach any additional information (drawings, screenshots, etc.) you think may help.
|
||||||
@@ -74,12 +77,15 @@ environments ([aloha](https://github.com/huggingface/gym-aloha),
|
|||||||
and follow the same api design.
|
and follow the same api design.
|
||||||
|
|
||||||
When implementing a new dataset loadable with LeRobotDataset follow these steps:
|
When implementing a new dataset loadable with LeRobotDataset follow these steps:
|
||||||
|
|
||||||
- Update `available_datasets_per_env` in `lerobot/__init__.py`
|
- Update `available_datasets_per_env` in `lerobot/__init__.py`
|
||||||
|
|
||||||
When implementing a new environment (e.g. `gym_aloha`), follow these steps:
|
When implementing a new environment (e.g. `gym_aloha`), follow these steps:
|
||||||
|
|
||||||
- Update `available_tasks_per_env` and `available_datasets_per_env` in `lerobot/__init__.py`
|
- Update `available_tasks_per_env` and `available_datasets_per_env` in `lerobot/__init__.py`
|
||||||
|
|
||||||
When implementing a new policy class (e.g. `DiffusionPolicy`) follow these steps:
|
When implementing a new policy class (e.g. `DiffusionPolicy`) follow these steps:
|
||||||
|
|
||||||
- Update `available_policies` and `available_policies_per_env`, in `lerobot/__init__.py`
|
- Update `available_policies` and `available_policies_per_env`, in `lerobot/__init__.py`
|
||||||
- Set the required `name` class attribute.
|
- Set the required `name` class attribute.
|
||||||
- Update variables in `tests/test_available.py` by importing your new Policy class
|
- Update variables in `tests/test_available.py` by importing your new Policy class
|
||||||
@@ -133,11 +139,13 @@ Follow these steps to start contributing:
|
|||||||
Follow the instructions to [install poetry](https://python-poetry.org/docs/#installation) (use a version >=2.1.0) or to [install uv](https://docs.astral.sh/uv/getting-started/installation/#installation-methods) if you don't have one of them already.
|
Follow the instructions to [install poetry](https://python-poetry.org/docs/#installation) (use a version >=2.1.0) or to [install uv](https://docs.astral.sh/uv/getting-started/installation/#installation-methods) if you don't have one of them already.
|
||||||
|
|
||||||
Set up a development environment with conda or miniconda:
|
Set up a development environment with conda or miniconda:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda create -y -n lerobot-dev python=3.10 && conda activate lerobot-dev
|
conda create -y -n lerobot-dev python=3.10 && conda activate lerobot-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using `uv`, it can manage python versions so you can instead do:
|
If you're using `uv`, it can manage python versions so you can instead do:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv --python 3.10 && source .venv/bin/activate
|
uv venv --python 3.10 && source .venv/bin/activate
|
||||||
```
|
```
|
||||||
@@ -145,11 +153,13 @@ Follow these steps to start contributing:
|
|||||||
To develop on 🤗 LeRobot, you will at least need to install the `dev` and `test` extras dependencies along with the core library:
|
To develop on 🤗 LeRobot, you will at least need to install the `dev` and `test` extras dependencies along with the core library:
|
||||||
|
|
||||||
using `poetry`
|
using `poetry`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry sync --extras "dev test"
|
poetry sync --extras "dev test"
|
||||||
```
|
```
|
||||||
|
|
||||||
using `uv`
|
using `uv`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync --extra dev --extra test
|
uv sync --extra dev --extra test
|
||||||
```
|
```
|
||||||
@@ -157,43 +167,48 @@ Follow these steps to start contributing:
|
|||||||
You can also install the project with all its dependencies (including environments):
|
You can also install the project with all its dependencies (including environments):
|
||||||
|
|
||||||
using `poetry`
|
using `poetry`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry sync --all-extras
|
poetry sync --all-extras
|
||||||
```
|
```
|
||||||
|
|
||||||
using `uv`
|
using `uv`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync --all-extras
|
uv sync --all-extras
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** If you don't install simulation environments with `--all-extras`, the tests that require them will be skipped when running the pytest suite locally. However, they *will* be tested in the CI. In general, we advise you to install everything and test locally before pushing.
|
> **Note:** If you don't install simulation environments with `--all-extras`, the tests that require them will be skipped when running the pytest suite locally. However, they _will_ be tested in the CI. In general, we advise you to install everything and test locally before pushing.
|
||||||
|
|
||||||
Whichever command you chose to install the project (e.g. `poetry sync --all-extras`), you should run it again when pulling code with an updated version of `pyproject.toml` and `poetry.lock` in order to synchronize your virtual environment with the new dependencies.
|
Whichever command you chose to install the project (e.g. `poetry sync --all-extras`), you should run it again when pulling code with an updated version of `pyproject.toml` and `poetry.lock` in order to synchronize your virtual environment with the new dependencies.
|
||||||
|
|
||||||
The equivalent of `pip install some-package`, would just be:
|
The equivalent of `pip install some-package`, would just be:
|
||||||
|
|
||||||
using `poetry`
|
using `poetry`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry add some-package
|
poetry add some-package
|
||||||
```
|
```
|
||||||
|
|
||||||
using `uv`
|
using `uv`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv add some-package
|
uv add some-package
|
||||||
```
|
```
|
||||||
|
|
||||||
When making changes to the poetry sections of the `pyproject.toml`, you should run the following command to lock dependencies.
|
When making changes to the poetry sections of the `pyproject.toml`, you should run the following command to lock dependencies.
|
||||||
using `poetry`
|
using `poetry`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry lock
|
poetry lock
|
||||||
```
|
```
|
||||||
|
|
||||||
using `uv`
|
using `uv`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv lock
|
uv lock
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
5. Develop the features on your branch.
|
5. Develop the features on your branch.
|
||||||
|
|
||||||
As you work on the features, you should make sure that the test suite
|
As you work on the features, you should make sure that the test suite
|
||||||
@@ -211,11 +226,13 @@ Follow these steps to start contributing:
|
|||||||
automatically as Git commit hooks.
|
automatically as Git commit hooks.
|
||||||
|
|
||||||
Install `pre-commit` hooks:
|
Install `pre-commit` hooks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
You can run these hooks whenever you need on staged files with:
|
You can run these hooks whenever you need on staged files with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pre-commit
|
pre-commit
|
||||||
```
|
```
|
||||||
@@ -229,6 +246,7 @@ Follow these steps to start contributing:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note, if you already committed some changes that have a wrong formatting, you can use:
|
Note, if you already committed some changes that have a wrong formatting, you can use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
```
|
```
|
||||||
@@ -249,16 +267,15 @@ Follow these steps to start contributing:
|
|||||||
git push -u origin a-descriptive-name-for-my-changes
|
git push -u origin a-descriptive-name-for-my-changes
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Once you are satisfied (**and the checklist below is happy too**), go to the
|
7. Once you are satisfied (**and the checklist below is happy too**), go to the
|
||||||
webpage of your fork on GitHub. Click on 'Pull request' to send your changes
|
webpage of your fork on GitHub. Click on 'Pull request' to send your changes
|
||||||
to the project maintainers for review.
|
to the project maintainers for review.
|
||||||
|
|
||||||
7. It's ok if maintainers ask you for changes. It happens to core contributors
|
8. It's ok if maintainers ask you for changes. It happens to core contributors
|
||||||
too! So everyone can see the changes in the Pull request, work in your local
|
too! So everyone can see the changes in the Pull request, work in your local
|
||||||
branch and push the changes to your fork. They will automatically appear in
|
branch and push the changes to your fork. They will automatically appear in
|
||||||
the pull request.
|
the pull request.
|
||||||
|
|
||||||
|
|
||||||
### Checklist
|
### Checklist
|
||||||
|
|
||||||
1. The title of your pull request should be a summary of its contribution;
|
1. The title of your pull request should be a summary of its contribution;
|
||||||
@@ -277,18 +294,21 @@ An extensive test suite is included to test the library behavior and several exa
|
|||||||
Install [git lfs](https://git-lfs.com/) to retrieve test artifacts (if you don't have it already).
|
Install [git lfs](https://git-lfs.com/) to retrieve test artifacts (if you don't have it already).
|
||||||
|
|
||||||
On Mac:
|
On Mac:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install git-lfs
|
brew install git-lfs
|
||||||
git lfs install
|
git lfs install
|
||||||
```
|
```
|
||||||
|
|
||||||
On Ubuntu:
|
On Ubuntu:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install git-lfs
|
sudo apt-get install git-lfs
|
||||||
git lfs install
|
git lfs install
|
||||||
```
|
```
|
||||||
|
|
||||||
Pull artifacts if they're not in [tests/artifacts](tests/artifacts)
|
Pull artifacts if they're not in [tests/artifacts](tests/artifacts)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git lfs pull
|
git lfs pull
|
||||||
```
|
```
|
||||||
@@ -300,6 +320,5 @@ repository, here's how to run tests with `pytest` for the library:
|
|||||||
python -m pytest -sv ./tests
|
python -m pytest -sv ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
You can specify a smaller set of tests in order to test only the feature
|
You can specify a smaller set of tests in order to test only the feature
|
||||||
you're working on.
|
you're working on.
|
||||||
|
|||||||
51
README.md
51
README.md
@@ -66,7 +66,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<p><strong>Meet the updated SO100, the SO-101 – Just €114 per arm!</strong></p>
|
<p><strong>Meet the updated SO100, the SO-101 – Just €114 per arm!</strong></p>
|
||||||
<p>Train it in minutes with a few simple moves on your laptop.</p>
|
<p>Train it in minutes with a few simple moves on your laptop.</p>
|
||||||
<p>Then sit back and watch your creation act autonomously! 🤯</p>
|
<p>Then sit back and watch your creation act autonomously! 🤯</p>
|
||||||
@@ -120,52 +119,61 @@
|
|||||||
- Thanks to Antonio Loquercio and Ashish Kumar for their early support.
|
- Thanks to Antonio Loquercio and Ashish Kumar for their early support.
|
||||||
- Thanks to [Seungjae (Jay) Lee](https://sjlee.cc/), [Mahi Shafiullah](https://mahis.life/) and colleagues for open sourcing [VQ-BeT](https://sjlee.cc/vq-bet/) policy and helping us adapt the codebase to our repository. The policy is adapted from [VQ-BeT repo](https://github.com/jayLEE0301/vq_bet_official).
|
- Thanks to [Seungjae (Jay) Lee](https://sjlee.cc/), [Mahi Shafiullah](https://mahis.life/) and colleagues for open sourcing [VQ-BeT](https://sjlee.cc/vq-bet/) policy and helping us adapt the codebase to our repository. The policy is adapted from [VQ-BeT repo](https://github.com/jayLEE0301/vq_bet_official).
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Download our source code:
|
Download our source code:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/huggingface/lerobot.git
|
git clone https://github.com/huggingface/lerobot.git
|
||||||
cd lerobot
|
cd lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a virtual environment with Python 3.10 and activate it, e.g. with [`miniconda`](https://docs.anaconda.com/free/miniconda/index.html):
|
Create a virtual environment with Python 3.10 and activate it, e.g. with [`miniconda`](https://docs.anaconda.com/free/miniconda/index.html):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda create -y -n lerobot python=3.10
|
conda create -y -n lerobot python=3.10
|
||||||
conda activate lerobot
|
conda activate lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
When using `miniconda`, install `ffmpeg` in your environment:
|
When using `miniconda`, install `ffmpeg` in your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda install ffmpeg -c conda-forge
|
conda install ffmpeg -c conda-forge
|
||||||
```
|
```
|
||||||
|
|
||||||
> **NOTE:** This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can:
|
> **NOTE:** This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can:
|
||||||
> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using:
|
>
|
||||||
> ```bash
|
> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using:
|
||||||
> conda install ffmpeg=7.1.1 -c conda-forge
|
>
|
||||||
> ```
|
> ```bash
|
||||||
> - _[On Linux only]_ Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`.
|
> conda install ffmpeg=7.1.1 -c conda-forge
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> - _[On Linux only]_ Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`.
|
||||||
|
|
||||||
Install 🤗 LeRobot:
|
Install 🤗 LeRobot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
> **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run:
|
> **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run:
|
||||||
`sudo apt-get install cmake build-essential python3-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev`. For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg)
|
> `sudo apt-get install cmake build-essential python3-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev`. For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg)
|
||||||
|
|
||||||
For simulations, 🤗 LeRobot comes with gymnasium environments that can be installed as extras:
|
For simulations, 🤗 LeRobot comes with gymnasium environments that can be installed as extras:
|
||||||
|
|
||||||
- [aloha](https://github.com/huggingface/gym-aloha)
|
- [aloha](https://github.com/huggingface/gym-aloha)
|
||||||
- [xarm](https://github.com/huggingface/gym-xarm)
|
- [xarm](https://github.com/huggingface/gym-xarm)
|
||||||
- [pusht](https://github.com/huggingface/gym-pusht)
|
- [pusht](https://github.com/huggingface/gym-pusht)
|
||||||
|
|
||||||
For instance, to install 🤗 LeRobot with aloha and pusht, use:
|
For instance, to install 🤗 LeRobot with aloha and pusht, use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[aloha, pusht]"
|
pip install -e ".[aloha, pusht]"
|
||||||
```
|
```
|
||||||
|
|
||||||
To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with
|
To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wandb login
|
wandb login
|
||||||
```
|
```
|
||||||
@@ -177,6 +185,7 @@ wandb login
|
|||||||
Check out [example 1](./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](./examples/1_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:
|
You can also locally visualize episodes from a dataset on the hub by executing our script from the command line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.visualize_dataset \
|
python -m lerobot.scripts.visualize_dataset \
|
||||||
--repo-id lerobot/pusht \
|
--repo-id lerobot/pusht \
|
||||||
@@ -184,6 +193,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`)
|
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
|
```bash
|
||||||
python -m lerobot.scripts.visualize_dataset \
|
python -m lerobot.scripts.visualize_dataset \
|
||||||
--repo-id lerobot/pusht \
|
--repo-id lerobot/pusht \
|
||||||
@@ -192,19 +202,17 @@ python -m lerobot.scripts.visualize_dataset \
|
|||||||
--episode-index 0
|
--episode-index 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
It will open `rerun.io` and display the camera streams, robot states and actions, like this:
|
It will open `rerun.io` and display the camera streams, robot states and actions, like this:
|
||||||
|
|
||||||
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
|
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 `python -m lerobot.scripts.visualize_dataset --help` for more instructions.
|
||||||
|
|
||||||
### The `LeRobotDataset` format
|
### 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 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](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](examples/1_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.
|
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.
|
||||||
|
|
||||||
@@ -239,6 +247,7 @@ dataset attributes:
|
|||||||
```
|
```
|
||||||
|
|
||||||
A `LeRobotDataset` is serialised using several widespread file formats for each of its parts, namely:
|
A `LeRobotDataset` is serialised using several widespread file formats for each of its parts, namely:
|
||||||
|
|
||||||
- hf_dataset stored using Hugging Face datasets library serialization to parquet
|
- hf_dataset stored using Hugging Face datasets library serialization to parquet
|
||||||
- videos are stored in mp4 format to save space
|
- videos are stored in mp4 format to save space
|
||||||
- metadata are stored in plain json/jsonl files
|
- metadata are stored in plain json/jsonl files
|
||||||
@@ -250,6 +259,7 @@ Dataset can be uploaded/downloaded from the HuggingFace hub seamlessly. To work
|
|||||||
Check out [example 2](./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.
|
Check out [example 2](./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):
|
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
|
```bash
|
||||||
python -m lerobot.scripts.eval \
|
python -m lerobot.scripts.eval \
|
||||||
--policy.path=lerobot/diffusion_pusht \
|
--policy.path=lerobot/diffusion_pusht \
|
||||||
@@ -284,9 +294,11 @@ Note: For efficiency, during training every checkpoint is evaluated on a low num
|
|||||||
|
|
||||||
We provide some pretrained policies on our [hub page](https://huggingface.co/lerobot) that can achieve state-of-the-art performances.
|
We provide some pretrained policies on our [hub page](https://huggingface.co/lerobot) that can achieve state-of-the-art performances.
|
||||||
You can reproduce their training by loading the config from their run. Simply running:
|
You can reproduce their training by loading the config from their run. Simply running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train --config_path=lerobot/diffusion_pusht
|
python -m lerobot.scripts.train --config_path=lerobot/diffusion_pusht
|
||||||
```
|
```
|
||||||
|
|
||||||
reproduces SOTA results for Diffusion Policy on the PushT task.
|
reproduces SOTA results for Diffusion Policy on the PushT task.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
@@ -313,27 +325,29 @@ See `python lerobot/scripts/push_dataset_to_hub.py --help` for more instructions
|
|||||||
|
|
||||||
If your dataset format is not supported, implement your own in `lerobot/datasets/push_dataset_to_hub/${raw_format}_format.py` by copying examples like [pusht_zarr](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/pusht_zarr_format.py), [umi_zarr](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/umi_zarr_format.py), [aloha_hdf5](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/aloha_hdf5_format.py), or [xarm_pkl](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/xarm_pkl_format.py). -->
|
If your dataset format is not supported, implement your own in `lerobot/datasets/push_dataset_to_hub/${raw_format}_format.py` by copying examples like [pusht_zarr](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/pusht_zarr_format.py), [umi_zarr](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/umi_zarr_format.py), [aloha_hdf5](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/aloha_hdf5_format.py), or [xarm_pkl](https://github.com/huggingface/lerobot/blob/main/lerobot/datasets/push_dataset_to_hub/xarm_pkl_format.py). -->
|
||||||
|
|
||||||
|
|
||||||
### Add a pretrained policy
|
### Add a pretrained policy
|
||||||
|
|
||||||
Once you have trained a policy you may upload it to the Hugging Face hub using a hub id that looks like `${hf_user}/${repo_name}` (e.g. [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)).
|
Once you have trained a policy you may upload it to the Hugging Face hub using a hub id that looks like `${hf_user}/${repo_name}` (e.g. [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)).
|
||||||
|
|
||||||
You first need to find the checkpoint folder located inside your experiment directory (e.g. `outputs/train/2024-05-05/20-21-12_aloha_act_default/checkpoints/002500`). Within that there is a `pretrained_model` directory which should contain:
|
You first need to find the checkpoint folder located inside your experiment directory (e.g. `outputs/train/2024-05-05/20-21-12_aloha_act_default/checkpoints/002500`). Within that there is a `pretrained_model` directory which should contain:
|
||||||
|
|
||||||
- `config.json`: A serialized version of the policy configuration (following the policy's dataclass config).
|
- `config.json`: A serialized version of the policy configuration (following the policy's dataclass config).
|
||||||
- `model.safetensors`: A set of `torch.nn.Module` parameters, saved in [Hugging Face Safetensors](https://huggingface.co/docs/safetensors/index) format.
|
- `model.safetensors`: A set of `torch.nn.Module` parameters, saved in [Hugging Face Safetensors](https://huggingface.co/docs/safetensors/index) format.
|
||||||
- `train_config.json`: A consolidated configuration containing all parameters used for training. The policy configuration should match `config.json` exactly. This is useful for anyone who wants to evaluate your policy or for reproducibility.
|
- `train_config.json`: A consolidated configuration containing all parameters used for training. The policy configuration should match `config.json` exactly. This is useful for anyone who wants to evaluate your policy or for reproducibility.
|
||||||
|
|
||||||
To upload these to the hub, run the following:
|
To upload these to the hub, run the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli upload ${hf_user}/${repo_name} path/to/pretrained_model
|
huggingface-cli upload ${hf_user}/${repo_name} path/to/pretrained_model
|
||||||
```
|
```
|
||||||
|
|
||||||
See [eval.py](https://github.com/huggingface/lerobot/blob/main/lerobot/scripts/eval.py) for an example of how other people may use your policy.
|
See [eval.py](https://github.com/huggingface/lerobot/blob/main/lerobot/scripts/eval.py) for an example of how other people may use your policy.
|
||||||
|
|
||||||
|
|
||||||
### Improve your code with profiling
|
### Improve your code with profiling
|
||||||
|
|
||||||
An example of a code snippet to profile the evaluation of a policy:
|
An example of a code snippet to profile the evaluation of a policy:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from torch.profiler import profile, record_function, ProfilerActivity
|
from torch.profiler import profile, record_function, ProfilerActivity
|
||||||
|
|
||||||
@@ -354,10 +368,12 @@ with profile(
|
|||||||
prof.step()
|
prof.step()
|
||||||
# insert code to profile, potentially whole body of eval_policy function
|
# insert code to profile, potentially whole body of eval_policy function
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Citation
|
## Citation
|
||||||
|
|
||||||
If you want, you can cite this work with:
|
If you want, you can cite this work with:
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@misc{cadene2024lerobot,
|
@misc{cadene2024lerobot,
|
||||||
author = {Cadene, Remi and Alibert, Simon and Soare, Alexander and Gallouedec, Quentin and Zouitine, Adil and Palma, Steven and Kooijmans, Pepijn and Aractingi, Michel and Shukor, Mustafa and Aubakirova, Dana and Russi, Martino and Capuano, Francesco and Pascale, Caroline and Choghari, Jade and Moss, Jess and Wolf, Thomas},
|
author = {Cadene, Remi and Alibert, Simon and Soare, Alexander and Gallouedec, Quentin and Zouitine, Adil and Palma, Steven and Kooijmans, Pepijn and Aractingi, Michel and Shukor, Mustafa and Aubakirova, Dana and Russi, Martino and Capuano, Francesco and Pascale, Caroline and Choghari, Jade and Moss, Jess and Wolf, Thomas},
|
||||||
@@ -368,7 +384,9 @@ If you want, you can cite this work with:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Additionally, if you are using any of the particular policy architecture, pretrained models, or datasets, it is recommended to cite the original authors of the work as they appear below:
|
Additionally, if you are using any of the particular policy architecture, pretrained models, or datasets, it is recommended to cite the original authors of the work as they appear below:
|
||||||
|
|
||||||
- [SmolVLA](https://arxiv.org/abs/2506.01844)
|
- [SmolVLA](https://arxiv.org/abs/2506.01844)
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@article{shukor2025smolvla,
|
@article{shukor2025smolvla,
|
||||||
title={SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics},
|
title={SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics},
|
||||||
@@ -379,6 +397,7 @@ Additionally, if you are using any of the particular policy architecture, pretra
|
|||||||
```
|
```
|
||||||
|
|
||||||
- [Diffusion Policy](https://diffusion-policy.cs.columbia.edu)
|
- [Diffusion Policy](https://diffusion-policy.cs.columbia.edu)
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@article{chi2024diffusionpolicy,
|
@article{chi2024diffusionpolicy,
|
||||||
author = {Cheng Chi and Zhenjia Xu and Siyuan Feng and Eric Cousineau and Yilun Du and Benjamin Burchfiel and Russ Tedrake and Shuran Song},
|
author = {Cheng Chi and Zhenjia Xu and Siyuan Feng and Eric Cousineau and Yilun Du and Benjamin Burchfiel and Russ Tedrake and Shuran Song},
|
||||||
@@ -387,7 +406,9 @@ Additionally, if you are using any of the particular policy architecture, pretra
|
|||||||
year = {2024},
|
year = {2024},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ACT or ALOHA](https://tonyzhaozh.github.io/aloha)
|
- [ACT or ALOHA](https://tonyzhaozh.github.io/aloha)
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@article{zhao2023learning,
|
@article{zhao2023learning,
|
||||||
title={Learning fine-grained bimanual manipulation with low-cost hardware},
|
title={Learning fine-grained bimanual manipulation with low-cost hardware},
|
||||||
@@ -409,6 +430,7 @@ Additionally, if you are using any of the particular policy architecture, pretra
|
|||||||
```
|
```
|
||||||
|
|
||||||
- [VQ-BeT](https://sjlee.cc/vq-bet/)
|
- [VQ-BeT](https://sjlee.cc/vq-bet/)
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@article{lee2024behavior,
|
@article{lee2024behavior,
|
||||||
title={Behavior generation with latent actions},
|
title={Behavior generation with latent actions},
|
||||||
@@ -418,8 +440,8 @@ Additionally, if you are using any of the particular policy architecture, pretra
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
- [HIL-SERL](https://hil-serl.github.io/)
|
- [HIL-SERL](https://hil-serl.github.io/)
|
||||||
|
|
||||||
```bibtex
|
```bibtex
|
||||||
@Article{luo2024hilserl,
|
@Article{luo2024hilserl,
|
||||||
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
||||||
@@ -430,6 +452,7 @@ archivePrefix={arXiv},
|
|||||||
primaryClass={cs.RO}
|
primaryClass={cs.RO}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#huggingface/lerobot&Timeline)
|
[](https://star-history.com/#huggingface/lerobot&Timeline)
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
# Video benchmark
|
# Video benchmark
|
||||||
|
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
What is the optimal trade-off between:
|
What is the optimal trade-off between:
|
||||||
|
|
||||||
- maximizing loading time with random access,
|
- maximizing loading time with random access,
|
||||||
- minimizing memory space on disk,
|
- minimizing memory space on disk,
|
||||||
- maximizing success rate of policies,
|
- maximizing success rate of policies,
|
||||||
- compatibility across devices/platforms for decoding videos (e.g. video players, web browsers).
|
- compatibility across devices/platforms for decoding videos (e.g. video players, web browsers).
|
||||||
|
|
||||||
How to encode videos?
|
How to encode videos?
|
||||||
|
|
||||||
- Which video codec (`-vcodec`) to use? h264, h265, AV1?
|
- Which video codec (`-vcodec`) to use? h264, h265, AV1?
|
||||||
- What pixel format to use (`-pix_fmt`)? `yuv444p` or `yuv420p`?
|
- What pixel format to use (`-pix_fmt`)? `yuv444p` or `yuv420p`?
|
||||||
- How much compression (`-crf`)? No compression with `0`, intermediate compression with `25` or extreme with `50+`?
|
- How much compression (`-crf`)? No compression with `0`, intermediate compression with `25` or extreme with `50+`?
|
||||||
- Which frequency to chose for key frames (`-g`)? A key frame every `10` frames?
|
- Which frequency to chose for key frames (`-g`)? A key frame every `10` frames?
|
||||||
|
|
||||||
How to decode videos?
|
How to decode videos?
|
||||||
|
|
||||||
- Which `decoder`? `torchvision`, `torchaudio`, `ffmpegio`, `decord`, or `nvc`?
|
- Which `decoder`? `torchvision`, `torchaudio`, `ffmpegio`, `decord`, or `nvc`?
|
||||||
- What scenarios to use for the requesting timestamps during benchmark? (`timestamps_mode`)
|
- What scenarios to use for the requesting timestamps during benchmark? (`timestamps_mode`)
|
||||||
|
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
**Image content & size**
|
**Image content & size**
|
||||||
We don't expect the same optimal settings for a dataset of images from a simulation, or from real-world in an apartment, or in a factory, or outdoor, or with lots of moving objects in the scene, etc. Similarly, loading times might not vary linearly with the image size (resolution).
|
We don't expect the same optimal settings for a dataset of images from a simulation, or from real-world in an apartment, or in a factory, or outdoor, or with lots of moving objects in the scene, etc. Similarly, loading times might not vary linearly with the image size (resolution).
|
||||||
For these reasons, we run this benchmark on four representative datasets:
|
For these reasons, we run this benchmark on four representative datasets:
|
||||||
|
|
||||||
- `lerobot/pusht_image`: (96 x 96 pixels) simulation with simple geometric shapes, fixed camera.
|
- `lerobot/pusht_image`: (96 x 96 pixels) simulation with simple geometric shapes, fixed camera.
|
||||||
- `aliberts/aloha_mobile_shrimp_image`: (480 x 640 pixels) real-world indoor, moving camera.
|
- `aliberts/aloha_mobile_shrimp_image`: (480 x 640 pixels) real-world indoor, moving camera.
|
||||||
- `aliberts/paris_street`: (720 x 1280 pixels) real-world outdoor, moving camera.
|
- `aliberts/paris_street`: (720 x 1280 pixels) real-world outdoor, moving camera.
|
||||||
@@ -34,8 +38,9 @@ Note: The datasets used for this benchmark need to be image datasets, not video
|
|||||||
We might revisit this benchmark and find better settings if we train our policies with various data augmentations to make them more robust (e.g. robust to color changes, compression, etc.).
|
We might revisit this benchmark and find better settings if we train our policies with various data augmentations to make them more robust (e.g. robust to color changes, compression, etc.).
|
||||||
|
|
||||||
### Encoding parameters
|
### Encoding parameters
|
||||||
|
|
||||||
| parameter | values |
|
| parameter | values |
|
||||||
|-------------|--------------------------------------------------------------|
|
| ----------- | ------------------------------------------------------------ |
|
||||||
| **vcodec** | `libx264`, `libx265`, `libsvtav1` |
|
| **vcodec** | `libx264`, `libx265`, `libsvtav1` |
|
||||||
| **pix_fmt** | `yuv444p`, `yuv420p` |
|
| **pix_fmt** | `yuv444p`, `yuv420p` |
|
||||||
| **g** | `1`, `2`, `3`, `4`, `5`, `6`, `10`, `15`, `20`, `40`, `None` |
|
| **g** | `1`, `2`, `3`, `4`, `5`, `6`, `10`, `15`, `20`, `40`, `None` |
|
||||||
@@ -44,19 +49,23 @@ We might revisit this benchmark and find better settings if we train our policie
|
|||||||
Note that `crf` value might be interpreted differently by various video codecs. In other words, the same value used with one codec doesn't necessarily translate into the same compression level with another codec. In fact, the default value (`None`) isn't the same amongst the different video codecs. Importantly, it is also the case for many other ffmpeg arguments like `g` which specifies the frequency of the key frames.
|
Note that `crf` value might be interpreted differently by various video codecs. In other words, the same value used with one codec doesn't necessarily translate into the same compression level with another codec. In fact, the default value (`None`) isn't the same amongst the different video codecs. Importantly, it is also the case for many other ffmpeg arguments like `g` which specifies the frequency of the key frames.
|
||||||
|
|
||||||
For a comprehensive list and documentation of these parameters, see the ffmpeg documentation depending on the video codec used:
|
For a comprehensive list and documentation of these parameters, see the ffmpeg documentation depending on the video codec used:
|
||||||
|
|
||||||
- h264: https://trac.ffmpeg.org/wiki/Encode/H.264
|
- h264: https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||||
- h265: https://trac.ffmpeg.org/wiki/Encode/H.265
|
- h265: https://trac.ffmpeg.org/wiki/Encode/H.265
|
||||||
- AV1: https://trac.ffmpeg.org/wiki/Encode/AV1
|
- AV1: https://trac.ffmpeg.org/wiki/Encode/AV1
|
||||||
|
|
||||||
### Decoding parameters
|
### Decoding parameters
|
||||||
|
|
||||||
**Decoder**
|
**Decoder**
|
||||||
We tested two video decoding backends from torchvision:
|
We tested two video decoding backends from torchvision:
|
||||||
|
|
||||||
- `pyav`
|
- `pyav`
|
||||||
- `video_reader` (requires to build torchvision from source)
|
- `video_reader` (requires to build torchvision from source)
|
||||||
|
|
||||||
**Requested timestamps**
|
**Requested timestamps**
|
||||||
Given the way video decoding works, once a keyframe has been loaded, the decoding of subsequent frames is fast.
|
Given the way video decoding works, once a keyframe has been loaded, the decoding of subsequent frames is fast.
|
||||||
This of course is affected by the `-g` parameter during encoding, which specifies the frequency of the keyframes. Given our typical use cases in robotics policies which might request a few timestamps in different random places, we want to replicate these use cases with the following scenarios:
|
This of course is affected by the `-g` parameter during encoding, which specifies the frequency of the keyframes. Given our typical use cases in robotics policies which might request a few timestamps in different random places, we want to replicate these use cases with the following scenarios:
|
||||||
|
|
||||||
- `1_frame`: 1 frame,
|
- `1_frame`: 1 frame,
|
||||||
- `2_frames`: 2 consecutive frames (e.g. `[t, t + 1 / fps]`),
|
- `2_frames`: 2 consecutive frames (e.g. `[t, t + 1 / fps]`),
|
||||||
- `6_frames`: 6 consecutive frames (e.g. `[t + i / fps for i in range(6)]`)
|
- `6_frames`: 6 consecutive frames (e.g. `[t + i / fps for i in range(6)]`)
|
||||||
@@ -64,12 +73,13 @@ This of course is affected by the `-g` parameter during encoding, which specifie
|
|||||||
Note that this differs significantly from a typical use case like watching a movie, in which every frame is loaded sequentially from the beginning to the end and it's acceptable to have big values for `-g`.
|
Note that this differs significantly from a typical use case like watching a movie, in which every frame is loaded sequentially from the beginning to the end and it's acceptable to have big values for `-g`.
|
||||||
|
|
||||||
Additionally, because some policies might request single timestamps that are a few frames apart, we also have the following scenario:
|
Additionally, because some policies might request single timestamps that are a few frames apart, we also have the following scenario:
|
||||||
|
|
||||||
- `2_frames_4_space`: 2 frames with 4 consecutive frames of spacing in between (e.g `[t, t + 5 / fps]`),
|
- `2_frames_4_space`: 2 frames with 4 consecutive frames of spacing in between (e.g `[t, t + 5 / fps]`),
|
||||||
|
|
||||||
However, due to how video decoding is implemented with `pyav`, we don't have access to an accurate seek so in practice this scenario is essentially the same as `6_frames` since all 6 frames between `t` and `t + 5 / fps` will be decoded.
|
However, due to how video decoding is implemented with `pyav`, we don't have access to an accurate seek so in practice this scenario is essentially the same as `6_frames` since all 6 frames between `t` and `t + 5 / fps` will be decoded.
|
||||||
|
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
**Data compression ratio (lower is better)**
|
**Data compression ratio (lower is better)**
|
||||||
`video_images_size_ratio` is the ratio of the memory space on disk taken by the encoded video over the memory space taken by the original images. For instance, `video_images_size_ratio=25%` means that the video takes 4 times less memory space on disk compared to the original images.
|
`video_images_size_ratio` is the ratio of the memory space on disk taken by the encoded video over the memory space taken by the original images. For instance, `video_images_size_ratio=25%` means that the video takes 4 times less memory space on disk compared to the original images.
|
||||||
|
|
||||||
@@ -87,18 +97,18 @@ However, due to how video decoding is implemented with `pyav`, we don't have acc
|
|||||||
|
|
||||||
One aspect that can't be measured here with those metrics is the compatibility of the encoding across platforms, in particular on web browser, for visualization purposes.
|
One aspect that can't be measured here with those metrics is the compatibility of the encoding across platforms, in particular on web browser, for visualization purposes.
|
||||||
h264, h265 and AV1 are all commonly used codecs and should not pose an issue. However, the chroma subsampling (`pix_fmt`) format might affect compatibility:
|
h264, h265 and AV1 are all commonly used codecs and should not pose an issue. However, the chroma subsampling (`pix_fmt`) format might affect compatibility:
|
||||||
|
|
||||||
- `yuv420p` is more widely supported across various platforms, including web browsers.
|
- `yuv420p` is more widely supported across various platforms, including web browsers.
|
||||||
- `yuv444p` offers higher color fidelity but might not be supported as broadly.
|
- `yuv444p` offers higher color fidelity but might not be supported as broadly.
|
||||||
|
|
||||||
|
|
||||||
<!-- **Loss of a pretrained policy (higher is better)** (not available)
|
<!-- **Loss of a pretrained policy (higher is better)** (not available)
|
||||||
`loss_pretrained` is the result of evaluating with the selected encoding/decoding settings a policy pretrained on original images. It is easier to understand than `avg_l2_error`.
|
`loss_pretrained` is the result of evaluating with the selected encoding/decoding settings a policy pretrained on original images. It is easier to understand than `avg_l2_error`.
|
||||||
|
|
||||||
**Success rate after retraining (higher is better)** (not available)
|
**Success rate after retraining (higher is better)** (not available)
|
||||||
`success_rate` is the result of training and evaluating a policy with the selected encoding/decoding settings. It is the most difficult metric to get but also the very best. -->
|
`success_rate` is the result of training and evaluating a policy with the selected encoding/decoding settings. It is the most difficult metric to get but also the very best. -->
|
||||||
|
|
||||||
|
|
||||||
## How the benchmark works
|
## How the benchmark works
|
||||||
|
|
||||||
The benchmark evaluates both encoding and decoding of video frames on the first episode of each dataset.
|
The benchmark evaluates both encoding and decoding of video frames on the first episode of each dataset.
|
||||||
|
|
||||||
**Encoding:** for each `vcodec` and `pix_fmt` pair, we use a default value for `g` and `crf` upon which we change a single value (either `g` or `crf`) to one of the specified values (we don't test every combination of those as this would be computationally too heavy).
|
**Encoding:** for each `vcodec` and `pix_fmt` pair, we use a default value for `g` and `crf` upon which we change a single value (either `g` or `crf`) to one of the specified values (we don't test every combination of those as this would be computationally too heavy).
|
||||||
@@ -110,15 +120,18 @@ Intermediate results saved for each `vcodec` and `pix_fmt` combination in csv ta
|
|||||||
These are then all concatenated to a single table ready for analysis.
|
These are then all concatenated to a single table ready for analysis.
|
||||||
|
|
||||||
## Caveats
|
## Caveats
|
||||||
|
|
||||||
We tried to measure the most impactful parameters for both encoding and decoding. However, for computational reasons we can't test out every combination.
|
We tried to measure the most impactful parameters for both encoding and decoding. However, for computational reasons we can't test out every combination.
|
||||||
|
|
||||||
Additional encoding parameters exist that are not included in this benchmark. In particular:
|
Additional encoding parameters exist that are not included in this benchmark. In particular:
|
||||||
|
|
||||||
- `-preset` which allows for selecting encoding presets. This represents a collection of options that will provide a certain encoding speed to compression ratio. By leaving this parameter unspecified, it is considered to be `medium` for libx264 and libx265 and `8` for libsvtav1.
|
- `-preset` which allows for selecting encoding presets. This represents a collection of options that will provide a certain encoding speed to compression ratio. By leaving this parameter unspecified, it is considered to be `medium` for libx264 and libx265 and `8` for libsvtav1.
|
||||||
- `-tune` which allows to optimize the encoding for certain aspects (e.g. film quality, fast decoding, etc.).
|
- `-tune` which allows to optimize the encoding for certain aspects (e.g. film quality, fast decoding, etc.).
|
||||||
|
|
||||||
See the documentation mentioned above for more detailed info on these settings and for a more comprehensive list of other parameters.
|
See the documentation mentioned above for more detailed info on these settings and for a more comprehensive list of other parameters.
|
||||||
|
|
||||||
Similarly on the decoding side, other decoders exist but are not implemented in our current benchmark. To name a few:
|
Similarly on the decoding side, other decoders exist but are not implemented in our current benchmark. To name a few:
|
||||||
|
|
||||||
- `torchaudio`
|
- `torchaudio`
|
||||||
- `ffmpegio`
|
- `ffmpegio`
|
||||||
- `decord`
|
- `decord`
|
||||||
@@ -127,16 +140,17 @@ Similarly on the decoding side, other decoders exist but are not implemented in
|
|||||||
Note as well that since we are mostly interested in the performance at decoding time (also because encoding is done only once before uploading a dataset), we did not measure encoding times nor have any metrics regarding encoding.
|
Note as well that since we are mostly interested in the performance at decoding time (also because encoding is done only once before uploading a dataset), we did not measure encoding times nor have any metrics regarding encoding.
|
||||||
However, besides the necessity to build ffmpeg from source, encoding did not pose any issue and it didn't take a significant amount of time during this benchmark.
|
However, besides the necessity to build ffmpeg from source, encoding did not pose any issue and it didn't take a significant amount of time during this benchmark.
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Building ffmpeg from source is required to include libx265 and libaom/libsvtav1 (av1) video codecs ([compilation guide](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu)).
|
Building ffmpeg from source is required to include libx265 and libaom/libsvtav1 (av1) video codecs ([compilation guide](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu)).
|
||||||
|
|
||||||
**Note:** While you still need to build torchvision with a conda-installed `ffmpeg<4.3` to use the `video_reader` decoder (as described in [#220](https://github.com/huggingface/lerobot/pull/220)), you also need another version which is custom-built with all the video codecs for encoding. For the script to then use that version, you can prepend the command above with `PATH="$HOME/bin:$PATH"`, which is where ffmpeg should be built.
|
**Note:** While you still need to build torchvision with a conda-installed `ffmpeg<4.3` to use the `video_reader` decoder (as described in [#220](https://github.com/huggingface/lerobot/pull/220)), you also need another version which is custom-built with all the video codecs for encoding. For the script to then use that version, you can prepend the command above with `PATH="$HOME/bin:$PATH"`, which is where ffmpeg should be built.
|
||||||
|
|
||||||
|
|
||||||
## Adding a video decoder
|
## Adding a video decoder
|
||||||
|
|
||||||
Right now, we're only benchmarking the two video decoder available with torchvision: `pyav` and `video_reader`.
|
Right now, we're only benchmarking the two video decoder available with torchvision: `pyav` and `video_reader`.
|
||||||
You can easily add a new decoder to benchmark by adding it to this function in the script:
|
You can easily add a new decoder to benchmark by adding it to this function in the script:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
def decode_video_frames(
|
def decode_video_frames(
|
||||||
video_path: str,
|
video_path: str,
|
||||||
@@ -156,9 +170,10 @@ def decode_video_frames(
|
|||||||
raise NotImplementedError(backend)
|
raise NotImplementedError(backend)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
For a quick run, you can try these parameters:
|
For a quick run, you can try these parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python benchmark/video/run_video_benchmark.py \
|
python benchmark/video/run_video_benchmark.py \
|
||||||
--output-dir outputs/video_benchmark \
|
--output-dir outputs/video_benchmark \
|
||||||
@@ -176,11 +191,12 @@ python benchmark/video/run_video_benchmark.py \
|
|||||||
--save-frames 0
|
--save-frames 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Results
|
## Results
|
||||||
|
|
||||||
### Reproduce
|
### Reproduce
|
||||||
|
|
||||||
We ran the benchmark with the following parameters:
|
We ran the benchmark with the following parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# h264 and h265 encodings
|
# h264 and h265 encodings
|
||||||
python benchmark/video/run_video_benchmark.py \
|
python benchmark/video/run_video_benchmark.py \
|
||||||
@@ -221,9 +237,10 @@ python benchmark/video/run_video_benchmark.py \
|
|||||||
|
|
||||||
The full results are available [here](https://docs.google.com/spreadsheets/d/1OYJB43Qu8fC26k_OyoMFgGBBKfQRCi4BIuYitQnq3sw/edit?usp=sharing)
|
The full results are available [here](https://docs.google.com/spreadsheets/d/1OYJB43Qu8fC26k_OyoMFgGBBKfQRCi4BIuYitQnq3sw/edit?usp=sharing)
|
||||||
|
|
||||||
|
|
||||||
### Parameters selected for LeRobotDataset
|
### Parameters selected for LeRobotDataset
|
||||||
|
|
||||||
Considering these results, we chose what we think is the best set of encoding parameter:
|
Considering these results, we chose what we think is the best set of encoding parameter:
|
||||||
|
|
||||||
- vcodec: `libsvtav1`
|
- vcodec: `libsvtav1`
|
||||||
- pix-fmt: `yuv420p`
|
- pix-fmt: `yuv420p`
|
||||||
- g: `2`
|
- g: `2`
|
||||||
@@ -236,7 +253,7 @@ Since we're using av1 encoding, we're choosing the `pyav` decoder as `video_read
|
|||||||
These tables show the results for `g=2` and `crf=30`, using `timestamps-modes=6_frames` and `backend=pyav`
|
These tables show the results for `g=2` and `crf=30`, using `timestamps-modes=6_frames` and `backend=pyav`
|
||||||
|
|
||||||
| video_images_size_ratio | vcodec | pix_fmt | | | |
|
| video_images_size_ratio | vcodec | pix_fmt | | | |
|
||||||
|------------------------------------|------------|---------|-----------|-----------|-----------|
|
| ---------------------------------- | ---------- | ------- | --------- | --------- | --------- |
|
||||||
| | libx264 | | libx265 | | libsvtav1 |
|
| | libx264 | | libx265 | | libsvtav1 |
|
||||||
| repo_id | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
| repo_id | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
||||||
| lerobot/pusht_image | **16.97%** | 17.58% | 18.57% | 18.86% | 22.06% |
|
| lerobot/pusht_image | **16.97%** | 17.58% | 18.57% | 18.86% | 22.06% |
|
||||||
@@ -245,7 +262,7 @@ These tables show the results for `g=2` and `crf=30`, using `timestamps-modes=6_
|
|||||||
| aliberts/kitchen | 1.40% | 1.39% | **1.00%** | **1.00%** | 2.52% |
|
| aliberts/kitchen | 1.40% | 1.39% | **1.00%** | **1.00%** | 2.52% |
|
||||||
|
|
||||||
| video_images_load_time_ratio | vcodec | pix_fmt | | | |
|
| video_images_load_time_ratio | vcodec | pix_fmt | | | |
|
||||||
|------------------------------------|---------|---------|----------|---------|-----------|
|
| ---------------------------------- | ------- | ------- | -------- | ------- | --------- |
|
||||||
| | libx264 | | libx265 | | libsvtav1 |
|
| | libx264 | | libx265 | | libsvtav1 |
|
||||||
| repo_id | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
| repo_id | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
||||||
| lerobot/pusht_image | 6.45 | 5.19 | **1.90** | 2.12 | 2.47 |
|
| lerobot/pusht_image | 6.45 | 5.19 | **1.90** | 2.12 | 2.47 |
|
||||||
@@ -254,7 +271,7 @@ These tables show the results for `g=2` and `crf=30`, using `timestamps-modes=6_
|
|||||||
| aliberts/kitchen | 1.46 | 1.46 | 0.28 | 0.51 | **0.26** |
|
| aliberts/kitchen | 1.46 | 1.46 | 0.28 | 0.51 | **0.26** |
|
||||||
|
|
||||||
| | | vcodec | pix_fmt | | | |
|
| | | vcodec | pix_fmt | | | |
|
||||||
|------------------------------------|----------|----------|--------------|----------|-----------|--------------|
|
| ---------------------------------- | -------- | -------- | ------------ | -------- | --------- | ------------ |
|
||||||
| | | libx264 | | libx265 | | libsvtav1 |
|
| | | libx264 | | libx265 | | libsvtav1 |
|
||||||
| repo_id | metric | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
| repo_id | metric | yuv420p | yuv444p | yuv420p | yuv444p | yuv420p |
|
||||||
| lerobot/pusht_image | avg_mse | 2.90E-04 | **2.03E-04** | 3.13E-04 | 2.29E-04 | 2.19E-04 |
|
| lerobot/pusht_image | avg_mse | 2.90E-04 | **2.03E-04** | 3.13E-04 | 2.29E-04 | 2.19E-04 |
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pip install -e ".[docs]"
|
|||||||
You will also need `nodejs`. Please refer to their [installation page](https://nodejs.org/en/download)
|
You will also need `nodejs`. Please refer to their [installation page](https://nodejs.org/en/download)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**NOTE**
|
**NOTE**
|
||||||
|
|
||||||
You only need to generate the documentation to inspect it locally (if you're planning changes and want to
|
You only need to generate the documentation to inspect it locally (if you're planning changes and want to
|
||||||
@@ -63,6 +64,7 @@ doc-builder preview lerobot docs/source/
|
|||||||
The docs will be viewable at [http://localhost:3000](http://localhost:3000). You can also preview the docs once you have opened a PR. You will see a bot add a comment to a link where the documentation with your changes lives.
|
The docs will be viewable at [http://localhost:3000](http://localhost:3000). You can also preview the docs once you have opened a PR. You will see a bot add a comment to a link where the documentation with your changes lives.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**NOTE**
|
**NOTE**
|
||||||
|
|
||||||
The `preview` command only works with existing doc files. When you add a completely new file, you need to update `_toctree.yml` & restart `preview` command (`ctrl-c` to stop it & call `doc-builder preview ...` again).
|
The `preview` command only works with existing doc files. When you add a completely new file, you need to update `_toctree.yml` & restart `preview` command (`ctrl-c` to stop it & call `doc-builder preview ...` again).
|
||||||
@@ -89,6 +91,7 @@ Sections that were moved:
|
|||||||
|
|
||||||
[ <a href="#section-b">Section A</a><a id="section-a"></a> ]
|
[ <a href="#section-b">Section A</a><a id="section-a"></a> ]
|
||||||
```
|
```
|
||||||
|
|
||||||
and of course, if you moved it to another file, then:
|
and of course, if you moved it to another file, then:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -119,7 +122,6 @@ and objects like True, None or any strings should usually be put in `code`.
|
|||||||
|
|
||||||
Multi-line code blocks can be useful for displaying examples. They are done between two lines of three backticks as usual in Markdown:
|
Multi-line code blocks can be useful for displaying examples. They are done between two lines of three backticks as usual in Markdown:
|
||||||
|
|
||||||
|
|
||||||
````
|
````
|
||||||
```
|
```
|
||||||
# first line of code
|
# first line of code
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ In this tutorial, we'll show how to use asynchronous inference (_async inference
|
|||||||
**Try async inference with all the policies** supported by LeRobot!
|
**Try async inference with all the policies** supported by LeRobot!
|
||||||
|
|
||||||
**What you'll learn:**
|
**What you'll learn:**
|
||||||
|
|
||||||
1. Why asynchronous inference matters and how it compares to, more traditional, sequential inference.
|
1. Why asynchronous inference matters and how it compares to, more traditional, sequential inference.
|
||||||
2. How to spin-up a `PolicyServer` and connect a `RobotClient` from the same machine, and even over the network.
|
2. How to spin-up a `PolicyServer` and connect a `RobotClient` from the same machine, and even over the network.
|
||||||
3. How to tune key parameters (`actions_per_chunk`, `chunk_size_threshold`) for your robot and policy.
|
3. How to tune key parameters (`actions_per_chunk`, `chunk_size_threshold`) for your robot and policy.
|
||||||
|
|
||||||
If you get stuck, hop into our [Discord community](https://discord.gg/s3KuuzsPFb)!
|
If you get stuck, hop into our [Discord community](https://discord.gg/s3KuuzsPFb)!
|
||||||
|
|
||||||
|
In a nutshell: with _async inference_, your robot keeps acting while the policy server is already busy computing the next chunk of actions---eliminating "wait-for-inference" lags and unlocking smoother, more reactive behaviours.
|
||||||
In a nutshell: with *async inference*, your robot keeps acting while the policy server is already busy computing the next chunk of actions---eliminating "wait-for-inference" lags and unlocking smoother, more reactive behaviours.
|
|
||||||
This is fundamentally different from synchronous inference (sync), where the robot stays idle while the policy computes the next chunk of actions.
|
This is fundamentally different from synchronous inference (sync), where the robot stays idle while the policy computes the next chunk of actions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Getting started with async inference
|
## Getting started with async inference
|
||||||
|
|
||||||
You can read more information on asynchronous inference in our [blogpost](https://huggingface.co/blog/async-robot-inference). This guide is designed to help you quickly set up and run asynchronous inference in your environment.
|
You can read more information on asynchronous inference in our [blogpost](https://huggingface.co/blog/async-robot-inference). This guide is designed to help you quickly set up and run asynchronous inference in your environment.
|
||||||
@@ -53,40 +54,53 @@ python src/lerobot/scripts/server/robot_client.py \
|
|||||||
--aggregate_fn_name=weighted_average \ # CLIENT: the function to aggregate actions on overlapping portions
|
--aggregate_fn_name=weighted_average \ # CLIENT: the function to aggregate actions on overlapping portions
|
||||||
--debug_visualize_queue_size=True # CLIENT: whether to visualize the queue size at runtime
|
--debug_visualize_queue_size=True # CLIENT: whether to visualize the queue size at runtime
|
||||||
```
|
```
|
||||||
|
|
||||||
In summary, you need to specify instructions for:
|
In summary, you need to specify instructions for:
|
||||||
|
|
||||||
- `SERVER`: the address and port of the policy server
|
- `SERVER`: the address and port of the policy server
|
||||||
- `ROBOT`: the type of robot to connect to, the port to connect to, and the local `id` of the robot
|
- `ROBOT`: the type of robot to connect to, the port to connect to, and the local `id` of the robot
|
||||||
- `POLICY`: the type of policy to run, and the model name/path on server to the checkpoint to run. You also need to specify which device should the sever be using, and how many actions to output at once (capped at the policy max actions value).
|
- `POLICY`: the type of policy to run, and the model name/path on server to the checkpoint to run. You also need to specify which device should the sever be using, and how many actions to output at once (capped at the policy max actions value).
|
||||||
- `CLIENT`: the threshold for the chunk size before sending a new observation to the server, and the function to aggregate actions on overlapping portions. Optionally, you can also visualize the queue size at runtime, to help you tune the `CLIENT` parameters.
|
- `CLIENT`: the threshold for the chunk size before sending a new observation to the server, and the function to aggregate actions on overlapping portions. Optionally, you can also visualize the queue size at runtime, to help you tune the `CLIENT` parameters.
|
||||||
|
|
||||||
Importantly,
|
Importantly,
|
||||||
|
|
||||||
- `actions_per_chunk` and `chunk_size_threshold` are key parameters to tune for your setup.
|
- `actions_per_chunk` and `chunk_size_threshold` are key parameters to tune for your setup.
|
||||||
- `aggregate_fn_name` is the function to aggregate actions on overlapping portions. You can either add a new one to a registry of functions, or add your own in `robot_client.py` (see [here](NOTE:addlinktoLOC))
|
- `aggregate_fn_name` is the function to aggregate actions on overlapping portions. You can either add a new one to a registry of functions, or add your own in `robot_client.py` (see [here](NOTE:addlinktoLOC))
|
||||||
- `debug_visualize_queue_size` is a useful tool to tune the `CLIENT` parameters.
|
- `debug_visualize_queue_size` is a useful tool to tune the `CLIENT` parameters.
|
||||||
|
|
||||||
Done! You should see your robot moving around by now 😉
|
## Done! You should see your robot moving around by now 😉
|
||||||
---
|
|
||||||
|
|
||||||
## Async vs. synchronous inference
|
## Async vs. synchronous inference
|
||||||
|
|
||||||
Synchronous inference relies on interleaving action chunk prediction and action execution. This inherently results in *idle frames*, frames where the robot awaits idle the policy's output: a new action chunk.
|
Synchronous inference relies on interleaving action chunk prediction and action execution. This inherently results in _idle frames_, frames where the robot awaits idle the policy's output: a new action chunk.
|
||||||
In turn, inference is plagued by evident real-time lags, where the robot simply stops acting due to the lack of available actions.
|
In turn, inference is plagued by evident real-time lags, where the robot simply stops acting due to the lack of available actions.
|
||||||
With robotics models increasing in size, this problem risks becoming only more severe.
|
With robotics models increasing in size, this problem risks becoming only more severe.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/sync.png" width="80%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/sync.png"
|
||||||
|
width="80%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>Synchronous inference</i> makes the robot idle while the policy is
|
||||||
|
computing the next chunk of actions.
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>Synchronous inference</i> makes the robot idle while the policy is computing the next chunk of actions.</p>
|
|
||||||
|
|
||||||
To overcome this, we design async inference, a paradigm where action planning and execution are decoupled, resulting in (1) higher adaptability and, most importantly, (2) no idle frames.
|
To overcome this, we design async inference, a paradigm where action planning and execution are decoupled, resulting in (1) higher adaptability and, most importantly, (2) no idle frames.
|
||||||
Crucially, with async inference, the next action chunk is computed *before* the current one is exhausted, resulting in no idleness.
|
Crucially, with async inference, the next action chunk is computed _before_ the current one is exhausted, resulting in no idleness.
|
||||||
Higher adaptability is ensured by aggregating the different action chunks on overlapping portions, obtaining an up-to-date plan and a tighter control loop.
|
Higher adaptability is ensured by aggregating the different action chunks on overlapping portions, obtaining an up-to-date plan and a tighter control loop.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/async.png" width="80%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/async.png"
|
||||||
|
width="80%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>Asynchronous inference</i> results in no idleness because the next chunk is
|
||||||
|
computed before the current chunk is exhausted.
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>Asynchronous inference</i> results in no idleness because the next chunk is computed before the current chunk is exhausted.</p>
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -105,6 +119,8 @@ python -m lerobot.scripts.server.policy_server \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.scripts.server.configs import PolicyServerConfig
|
from lerobot.scripts.server.configs import PolicyServerConfig
|
||||||
from lerobot.scripts.server.policy_server import serve
|
from lerobot.scripts.server.policy_server import serve
|
||||||
@@ -115,6 +131,8 @@ config = PolicyServerConfig(
|
|||||||
)
|
)
|
||||||
serve(config)
|
serve(config)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -147,6 +165,8 @@ python src/lerobot/scripts/server/robot_client.py \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
import threading
|
import threading
|
||||||
from lerobot.robots.so100_follower import SO100FollowerConfig
|
from lerobot.robots.so100_follower import SO100FollowerConfig
|
||||||
@@ -201,6 +221,8 @@ if client.start():
|
|||||||
# (Optionally) plot the action queue size
|
# (Optionally) plot the action queue size
|
||||||
visualize_action_queue_size(client.action_queue_size)
|
visualize_action_queue_size(client.action_queue_size)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -216,20 +238,30 @@ The following two parameters are key in every setup:
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>actions_per_chunk</code></td>
|
<td>
|
||||||
|
<code>actions_per_chunk</code>
|
||||||
|
</td>
|
||||||
<td>50</td>
|
<td>50</td>
|
||||||
<td>How many actions the policy outputs at once. Typical values: 10-50.</td>
|
<td>
|
||||||
|
How many actions the policy outputs at once. Typical values: 10-50.
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>chunk_size_threshold</code></td>
|
<td>
|
||||||
|
<code>chunk_size_threshold</code>
|
||||||
|
</td>
|
||||||
<td>0.7</td>
|
<td>0.7</td>
|
||||||
<td>When the queue is ≤ 50% full, the client sends a fresh observation. Value in [0, 1].</td>
|
<td>
|
||||||
|
When the queue is ≤ 50% full, the client sends a fresh observation.
|
||||||
|
Value in [0, 1].
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Different values of `actions_per_chunk` and `chunk_size_threshold` do result in different behaviours.
|
Different values of `actions_per_chunk` and `chunk_size_threshold` do result
|
||||||
|
in different behaviours.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
On the one hand, increasing the value of `actions_per_chunk` will result in reducing the likelihood of ending up with no actions to execute, as more actions will be available when the new chunk is computed.
|
On the one hand, increasing the value of `actions_per_chunk` will result in reducing the likelihood of ending up with no actions to execute, as more actions will be available when the new chunk is computed.
|
||||||
@@ -249,10 +281,18 @@ We found the default values of `actions_per_chunk` and `chunk_size_threshold` to
|
|||||||
- We found values around 0.5-0.6 to work well. If you want to tweak this, spin up a `RobotClient` setting the `--debug-visualize-queue-size` to `True`. This will plot the action queue size evolution at runtime, and you can use it to find the value of `chunk_size_threshold` that works best for your setup.
|
- We found values around 0.5-0.6 to work well. If you want to tweak this, spin up a `RobotClient` setting the `--debug-visualize-queue-size` to `True`. This will plot the action queue size evolution at runtime, and you can use it to find the value of `chunk_size_threshold` that works best for your setup.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/queues.png" width="80%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/async-inference/queues.png"
|
||||||
|
width="80%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>
|
||||||
|
The action queue size is plotted at runtime when the
|
||||||
|
`--debug-visualize-queue-size` flag is passed, for various levels of
|
||||||
|
`chunk_size_threshold` (`g` in the SmolVLA paper).
|
||||||
|
</i>
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>The action queue size is plotted at runtime when the `--debug-visualize-queue-size` flag is passed, for various levels of `chunk_size_threshold` (`g` in the SmolVLA paper).</i></p>
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,22 @@ PR [#777](https://github.com/huggingface/lerobot/pull/777) improves the LeRobot
|
|||||||
|
|
||||||
### What changed?
|
### What changed?
|
||||||
|
|
||||||
| | Before PR #777 | After PR #777 |
|
| | Before PR #777 | After PR #777 |
|
||||||
| --------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------- |
|
| --------------------------------- | ------------------------------------------------- | ------------------------------------------------------------ |
|
||||||
| **Joint range** | Degrees `-180...180°` | **Normalised range** Joints: `–100...100` Gripper: `0...100` |
|
| **Joint range** | Degrees `-180...180°` | **Normalised range** Joints: `–100...100` Gripper: `0...100` |
|
||||||
| **Zero position (SO100 / SO101)** | Arm fully extended horizontally | **In middle of the range for each joint** |
|
| **Zero position (SO100 / SO101)** | Arm fully extended horizontally | **In middle of the range for each joint** |
|
||||||
| **Boundary handling** | Software safeguards to detect ±180 ° wrap-arounds | No wrap-around logic needed due to mid-range zero |
|
| **Boundary handling** | Software safeguards to detect ±180 ° wrap-arounds | No wrap-around logic needed due to mid-range zero |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Impact on existing datasets
|
### Impact on existing datasets
|
||||||
|
|
||||||
* Recorded trajectories created **before** PR #777 will replay incorrectly if loaded directly:
|
- Recorded trajectories created **before** PR #777 will replay incorrectly if loaded directly:
|
||||||
* Joint angles are offset and incorrectly normalized.
|
- Joint angles are offset and incorrectly normalized.
|
||||||
* Any models directly finetuned or trained on the old data will need their inputs and outputs converted.
|
- Any models directly finetuned or trained on the old data will need their inputs and outputs converted.
|
||||||
|
|
||||||
### Using datasets made with the previous calibration system
|
### Using datasets made with the previous calibration system
|
||||||
|
|
||||||
We provide a migration example script for replaying an episode recorded with the previous calibration here: `examples/backward_compatibility/replay.py`.
|
We provide a migration example script for replaying an episode recorded with the previous calibration here: `examples/backward_compatibility/replay.py`.
|
||||||
Below we take you through the modifications that are done in the example script to make the previous calibration datasets work.
|
Below we take you through the modifications that are done in the example script to make the previous calibration datasets work.
|
||||||
|
|
||||||
@@ -33,20 +34,31 @@ Below we take you through the modifications that are done in the example script
|
|||||||
|
|
||||||
Let's break this down.
|
Let's break this down.
|
||||||
New codebase uses `.pos` suffix for the position observations and we have removed `main_` prefix:
|
New codebase uses `.pos` suffix for the position observations and we have removed `main_` prefix:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
key = f"{name.removeprefix('main_')}.pos"
|
key = f"{name.removeprefix('main_')}.pos"
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
For `"shoulder_lift"` (id = 2), the 0 position is changed by -90 degrees and the direction is reversed compared to old calibration/code.
|
For `"shoulder_lift"` (id = 2), the 0 position is changed by -90 degrees and the direction is reversed compared to old calibration/code.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
action["shoulder_lift.pos"] = -(action["shoulder_lift.pos"] - 90)
|
action["shoulder_lift.pos"] = -(action["shoulder_lift.pos"] - 90)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
For `"elbow_flex"` (id = 3), the 0 position is changed by -90 degrees compared to old calibration/code.
|
For `"elbow_flex"` (id = 3), the 0 position is changed by -90 degrees compared to old calibration/code.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
action["elbow_flex.pos"] -= 90
|
action["elbow_flex.pos"] -= 90
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
To use degrees normalization we then set the `--robot.use_degrees` option to `true`.
|
To use degrees normalization we then set the `--robot.use_degrees` option to `true`.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
python examples/backward_compatibility/replay.py \
|
python examples/backward_compatibility/replay.py \
|
||||||
--robot.type=so101_follower \
|
--robot.type=so101_follower \
|
||||||
@@ -63,6 +75,7 @@ Policies output actions in the same format as the datasets (`torch.Tensors`). Th
|
|||||||
|
|
||||||
To find these transformations, we recommend to first try and and replay an episode of the dataset your policy was trained on using the section above.
|
To find these transformations, we recommend to first try and and replay an episode of the dataset your policy was trained on using the section above.
|
||||||
Then, add these same transformations on your inference script (shown here in the `record.py` script):
|
Then, add these same transformations on your inference script (shown here in the `record.py` script):
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
action_values = predict_action(
|
action_values = predict_action(
|
||||||
observation_frame,
|
observation_frame,
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ LeRobot offers multiple options for video capture, including phone cameras, buil
|
|||||||
To instantiate a camera, you need a camera identifier. This identifier might change if you reboot your computer or re-plug your camera, a behavior mostly dependant on your operating system.
|
To instantiate a camera, you need a camera identifier. This identifier might change if you reboot your computer or re-plug your camera, a behavior mostly dependant on your operating system.
|
||||||
|
|
||||||
To find the camera indices of the cameras plugged into your system, run the following script:
|
To find the camera indices of the cameras plugged into your system, run the following script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_cameras opencv # or realsense for Intel Realsense cameras
|
python -m lerobot.find_cameras opencv # or realsense for Intel Realsense cameras
|
||||||
```
|
```
|
||||||
|
|
||||||
The output will look something like this if you have two cameras connected:
|
The output will look something like this if you have two cameras connected:
|
||||||
|
|
||||||
```
|
```
|
||||||
--- Detected Cameras ---
|
--- Detected Cameras ---
|
||||||
Camera #0:
|
Camera #0:
|
||||||
@@ -31,7 +33,6 @@ Camera #0:
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> When using Intel RealSense cameras in `macOS`, you could get this [error](https://github.com/IntelRealSense/librealsense/issues/12307): `Error finding RealSense cameras: failed to set power state`, this can be solved by running the same command with `sudo` permissions. Note that using RealSense cameras in `macOS` is unstable.
|
> When using Intel RealSense cameras in `macOS`, you could get this [error](https://github.com/IntelRealSense/librealsense/issues/12307): `Error finding RealSense cameras: failed to set power state`, this can be solved by running the same command with `sudo` permissions. Note that using RealSense cameras in `macOS` is unstable.
|
||||||
|
|
||||||
|
|
||||||
## Use Cameras
|
## Use Cameras
|
||||||
|
|
||||||
Below are two examples, demonstrating how to work with the API.
|
Below are two examples, demonstrating how to work with the API.
|
||||||
@@ -39,10 +40,10 @@ Below are two examples, demonstrating how to work with the API.
|
|||||||
- **Asynchronous frame capture** using an OpenCV-based camera
|
- **Asynchronous frame capture** using an OpenCV-based camera
|
||||||
- **Color and depth capture** using an Intel RealSense camera
|
- **Color and depth capture** using an Intel RealSense camera
|
||||||
|
|
||||||
|
|
||||||
<hfoptions id="shell_restart">
|
<hfoptions id="shell_restart">
|
||||||
<hfoption id="Open CV Camera">
|
<hfoption id="Open CV Camera">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
||||||
from lerobot.cameras.opencv.camera_opencv import OpenCVCamera
|
from lerobot.cameras.opencv.camera_opencv import OpenCVCamera
|
||||||
@@ -70,10 +71,12 @@ try:
|
|||||||
finally:
|
finally:
|
||||||
camera.disconnect()
|
camera.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="Intel Realsense Camera">
|
<hfoption id="Intel Realsense Camera">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraConfig
|
from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraConfig
|
||||||
from lerobot.cameras.realsense.camera_realsense import RealSenseCamera
|
from lerobot.cameras.realsense.camera_realsense import RealSenseCamera
|
||||||
@@ -103,15 +106,18 @@ try:
|
|||||||
finally:
|
finally:
|
||||||
camera.disconnect()
|
camera.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
|
|
||||||
## Use your phone
|
## Use your phone
|
||||||
|
|
||||||
<hfoptions id="use phone">
|
<hfoptions id="use phone">
|
||||||
<hfoption id="Mac">
|
<hfoption id="Mac">
|
||||||
|
|
||||||
To use your iPhone as a camera on macOS, enable the Continuity Camera feature:
|
To use your iPhone as a camera on macOS, enable the Continuity Camera feature:
|
||||||
|
|
||||||
- Ensure your Mac is running macOS 13 or later, and your iPhone is on iOS 16 or later.
|
- Ensure your Mac is running macOS 13 or later, and your iPhone is on iOS 16 or later.
|
||||||
- Sign in both devices with the same Apple ID.
|
- Sign in both devices with the same Apple ID.
|
||||||
- Connect your devices with a USB cable or turn on Wi-Fi and Bluetooth for a wireless connection.
|
- Connect your devices with a USB cable or turn on Wi-Fi and Bluetooth for a wireless connection.
|
||||||
@@ -125,40 +131,67 @@ Your iPhone should be detected automatically when running the camera setup scrip
|
|||||||
|
|
||||||
If you want to use your phone as a camera on Linux, follow these steps to set up a virtual camera
|
If you want to use your phone as a camera on Linux, follow these steps to set up a virtual camera
|
||||||
|
|
||||||
1. *Install `v4l2loopback-dkms` and `v4l-utils`*. Those packages are required to create virtual camera devices (`v4l2loopback`) and verify their settings with the `v4l2-ctl` utility from `v4l-utils`. Install them using:
|
1. _Install `v4l2loopback-dkms` and `v4l-utils`_. Those packages are required to create virtual camera devices (`v4l2loopback`) and verify their settings with the `v4l2-ctl` utility from `v4l-utils`. Install them using:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
sudo apt install v4l2loopback-dkms v4l-utils
|
sudo apt install v4l2loopback-dkms v4l-utils
|
||||||
```
|
```
|
||||||
2. *Install [DroidCam](https://droidcam.app) on your phone*. This app is available for both iOS and Android.
|
<!-- prettier-ignore-end -->
|
||||||
3. *Install [OBS Studio](https://obsproject.com)*. This software will help you manage the camera feed. Install it using [Flatpak](https://flatpak.org):
|
|
||||||
|
2. _Install [DroidCam](https://droidcam.app) on your phone_. This app is available for both iOS and Android.
|
||||||
|
3. _Install [OBS Studio](https://obsproject.com)_. This software will help you manage the camera feed. Install it using [Flatpak](https://flatpak.org):
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
flatpak install flathub com.obsproject.Studio
|
flatpak install flathub com.obsproject.Studio
|
||||||
```
|
```
|
||||||
4. *Install the DroidCam OBS plugin*. This plugin integrates DroidCam with OBS Studio. Install it with:
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
4. _Install the DroidCam OBS plugin_. This plugin integrates DroidCam with OBS Studio. Install it with:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
flatpak install flathub com.obsproject.Studio.Plugin.DroidCam
|
flatpak install flathub com.obsproject.Studio.Plugin.DroidCam
|
||||||
```
|
```
|
||||||
5. *Start OBS Studio*. Launch with:
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
5. _Start OBS Studio_. Launch with:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
flatpak run com.obsproject.Studio
|
flatpak run com.obsproject.Studio
|
||||||
```
|
```
|
||||||
6. *Add your phone as a source*. Follow the instructions [here](https://droidcam.app/obs/usage). Be sure to set the resolution to `640x480`.
|
<!-- prettier-ignore-end -->
|
||||||
7. *Adjust resolution settings*. In OBS Studio, go to `File > Settings > Video`. Change the `Base(Canvas) Resolution` and the `Output(Scaled) Resolution` to `640x480` by manually typing it in.
|
|
||||||
8. *Start virtual camera*. In OBS Studio, follow the instructions [here](https://obsproject.com/kb/virtual-camera-guide).
|
6. _Add your phone as a source_. Follow the instructions [here](https://droidcam.app/obs/usage). Be sure to set the resolution to `640x480`.
|
||||||
9. *Verify the virtual camera setup*. Use `v4l2-ctl` to list the devices:
|
7. _Adjust resolution settings_. In OBS Studio, go to `File > Settings > Video`. Change the `Base(Canvas) Resolution` and the `Output(Scaled) Resolution` to `640x480` by manually typing it in.
|
||||||
|
8. _Start virtual camera_. In OBS Studio, follow the instructions [here](https://obsproject.com/kb/virtual-camera-guide).
|
||||||
|
9. _Verify the virtual camera setup_. Use `v4l2-ctl` to list the devices:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
v4l2-ctl --list-devices
|
v4l2-ctl --list-devices
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
You should see an entry like:
|
You should see an entry like:
|
||||||
|
|
||||||
```
|
```
|
||||||
VirtualCam (platform:v4l2loopback-000):
|
VirtualCam (platform:v4l2loopback-000):
|
||||||
/dev/video1
|
/dev/video1
|
||||||
```
|
```
|
||||||
10. *Check the camera resolution*. Use `v4l2-ctl` to ensure that the virtual camera output resolution is `640x480`. Change `/dev/video1` to the port of your virtual camera from the output of `v4l2-ctl --list-devices`.
|
|
||||||
|
10. _Check the camera resolution_. Use `v4l2-ctl` to ensure that the virtual camera output resolution is `640x480`. Change `/dev/video1` to the port of your virtual camera from the output of `v4l2-ctl --list-devices`.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
v4l2-ctl -d /dev/video1 --get-fmt-video
|
v4l2-ctl -d /dev/video1 --get-fmt-video
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
You should see an entry like:
|
You should see an entry like:
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> Format Video Capture:
|
>>> Format Video Capture:
|
||||||
>>> Width/Height : 640/480
|
>>> Width/Height : 640/480
|
||||||
|
|||||||
@@ -4,18 +4,22 @@ In this tutorial you will go through the full Human-in-the-Loop Sample-Efficient
|
|||||||
|
|
||||||
HIL-SERL is a sample-efficient reinforcement learning algorithm that combines human demonstrations with online learning and human interventions. The approach starts from a small set of human demonstrations, uses them to train a reward classifier, and then employs an actor-learner architecture where humans can intervene during policy execution to guide exploration and correct unsafe behaviors. In this tutorial, you'll use a gamepad to provide interventions and control the robot during the learning process.
|
HIL-SERL is a sample-efficient reinforcement learning algorithm that combines human demonstrations with online learning and human interventions. The approach starts from a small set of human demonstrations, uses them to train a reward classifier, and then employs an actor-learner architecture where humans can intervene during policy execution to guide exploration and correct unsafe behaviors. In this tutorial, you'll use a gamepad to provide interventions and control the robot during the learning process.
|
||||||
|
|
||||||
It combines three key ingredients:
|
It combines three key ingredients: 1. **Offline demonstrations & reward classifier:** a handful of human-teleop episodes plus a vision-based success detector give the policy a shaped starting point. 2. **On-robot actor / learner loop with human interventions:** a distributed Soft Actor Critic (SAC) learner updates the policy while an actor explores on the physical robot; the human can jump in at any time to correct dangerous or unproductive behaviour. 3. **Safety & efficiency tools:** joint/end-effector (EE) bounds, crop region of interest (ROI) preprocessing and WandB monitoring keep the data useful and the hardware safe.
|
||||||
1. **Offline demonstrations & reward classifier:** a handful of human-teleop episodes plus a vision-based success detector give the policy a shaped starting point.
|
|
||||||
2. **On-robot actor / learner loop with human interventions:** a distributed Soft Actor Critic (SAC) learner updates the policy while an actor explores on the physical robot; the human can jump in at any time to correct dangerous or unproductive behaviour.
|
|
||||||
3. **Safety & efficiency tools:** joint/end-effector (EE) bounds, crop region of interest (ROI) preprocessing and WandB monitoring keep the data useful and the hardware safe.
|
|
||||||
|
|
||||||
Together these elements let HIL-SERL reach near-perfect task success and faster cycle times than imitation-only baselines.
|
Together these elements let HIL-SERL reach near-perfect task success and faster cycle times than imitation-only baselines.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/hilserl-main-figure.png" alt="HIL-SERL workflow" title="HIL-SERL workflow" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/hilserl-main-figure.png"
|
||||||
|
alt="HIL-SERL workflow"
|
||||||
|
title="HIL-SERL workflow"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><i>HIL-SERL workflow, Luo et al. 2024</i></p>
|
<p align="center">
|
||||||
|
<i>HIL-SERL workflow, Luo et al. 2024</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
This guide provides step-by-step instructions for training a robot policy using LeRobot's HilSerl implementation to train on a real robot.
|
This guide provides step-by-step instructions for training a robot policy using LeRobot's HilSerl implementation to train on a real robot.
|
||||||
|
|
||||||
@@ -29,6 +33,7 @@ This guide provides step-by-step instructions for training a robot policy using
|
|||||||
## What kind of tasks can I train?
|
## What kind of tasks can I train?
|
||||||
|
|
||||||
One can use HIL-SERL to train on a variety of manipulation tasks. Some recommendations:
|
One can use HIL-SERL to train on a variety of manipulation tasks. Some recommendations:
|
||||||
|
|
||||||
- Start with a simple task to understand how the system works.
|
- Start with a simple task to understand how the system works.
|
||||||
- Push cube to a goal region
|
- Push cube to a goal region
|
||||||
- Pick and lift cube with the gripper
|
- Pick and lift cube with the gripper
|
||||||
@@ -53,6 +58,7 @@ pip install -e ".[hilserl]"
|
|||||||
|
|
||||||
The training process begins with proper configuration for the HILSerl environment. The configuration class of interest is `HILSerlRobotEnvConfig` in `lerobot/envs/configs.py`. Which is defined as:
|
The training process begins with proper configuration for the HILSerl environment. The configuration class of interest is `HILSerlRobotEnvConfig` in `lerobot/envs/configs.py`. Which is defined as:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
class HILSerlRobotEnvConfig(EnvConfig):
|
class HILSerlRobotEnvConfig(EnvConfig):
|
||||||
robot: RobotConfig | None = None # Main robot agent (defined in `lerobot/robots`)
|
robot: RobotConfig | None = None # Main robot agent (defined in `lerobot/robots`)
|
||||||
@@ -72,7 +78,7 @@ class HILSerlRobotEnvConfig(EnvConfig):
|
|||||||
reward_classifier_pretrained_path: str | None = None # For reward model
|
reward_classifier_pretrained_path: str | None = None # For reward model
|
||||||
number_of_steps_after_success: int = 0 # For reward classifier, collect more positive examples after a success to train a classifier
|
number_of_steps_after_success: int = 0 # For reward classifier, collect more positive examples after a success to train a classifier
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### Finding Robot Workspace Bounds
|
### Finding Robot Workspace Bounds
|
||||||
|
|
||||||
@@ -131,6 +137,7 @@ Create a configuration file for recording demonstrations (or edit an existing on
|
|||||||
5. Configure `robot`, `cameras`, and other hardware settings
|
5. Configure `robot`, `cameras`, and other hardware settings
|
||||||
|
|
||||||
Example configuration section:
|
Example configuration section:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"mode": "record",
|
"mode": "record",
|
||||||
"repo_id": "username/pick_lift_cube",
|
"repo_id": "username/pick_lift_cube",
|
||||||
@@ -150,6 +157,7 @@ HIL-Serl learns actions in the end-effector space of the robot. Therefore, the t
|
|||||||
|
|
||||||
For that we need to define a version of the robot that takes actions in the end-effector space. Check the robot class `SO100FollowerEndEffector` and its configuration `SO100FollowerEndEffectorConfig` for the default parameters related to the end-effector space.
|
For that we need to define a version of the robot that takes actions in the end-effector space. Check the robot class `SO100FollowerEndEffector` and its configuration `SO100FollowerEndEffectorConfig` for the default parameters related to the end-effector space.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
class SO100FollowerEndEffectorConfig(SO100FollowerConfig):
|
class SO100FollowerEndEffectorConfig(SO100FollowerConfig):
|
||||||
"""Configuration for the SO100FollowerEndEffector robot."""
|
"""Configuration for the SO100FollowerEndEffector robot."""
|
||||||
@@ -172,6 +180,7 @@ class SO100FollowerEndEffectorConfig(SO100FollowerConfig):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
The `Teleoperator` defines the teleoperation device. You can check the list of available teleoperators in `lerobot/teleoperators`.
|
The `Teleoperator` defines the teleoperation device. You can check the list of available teleoperators in `lerobot/teleoperators`.
|
||||||
|
|
||||||
@@ -189,9 +198,16 @@ To setup the gamepad, you need to set the `control_mode` to `"gamepad"` and defi
|
|||||||
```
|
```
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/gamepad_guide.jpg?raw=true" alt="Figure shows the control mappings on a Logitech gamepad." title="Gamepad Control Mapping" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/gamepad_guide.jpg?raw=true"
|
||||||
|
alt="Figure shows the control mappings on a Logitech gamepad."
|
||||||
|
title="Gamepad Control Mapping"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>Gamepad button mapping for robot control and episode management</i>
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>Gamepad button mapping for robot control and episode management</i></p>
|
|
||||||
|
|
||||||
**Setting up the SO101 leader**
|
**Setting up the SO101 leader**
|
||||||
|
|
||||||
@@ -215,7 +231,10 @@ During the online training, press `space` to take over the policy and `space` ag
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so101_leader_tutorial.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so101_leader_tutorial.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -231,6 +250,7 @@ python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/e
|
|||||||
```
|
```
|
||||||
|
|
||||||
During recording:
|
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 `fixed_reset_joint_positions`
|
||||||
2. Complete the task successfully
|
2. Complete the task successfully
|
||||||
3. The episode ends with a reward of 1 when you press the "success" button
|
3. The episode ends with a reward of 1 when you press the "success" button
|
||||||
@@ -239,13 +259,13 @@ During recording:
|
|||||||
6. The process automatically continues to the next episode
|
6. The process automatically continues to the next episode
|
||||||
7. After recording all episodes, the dataset is pushed to the Hugging Face Hub (optional) and saved locally
|
7. After recording all episodes, the dataset is pushed to the Hugging Face Hub (optional) and saved locally
|
||||||
|
|
||||||
|
|
||||||
### Processing the Dataset
|
### Processing the Dataset
|
||||||
|
|
||||||
After collecting demonstrations, process them to determine optimal camera crops.
|
After collecting demonstrations, process them to determine optimal camera crops.
|
||||||
Reinforcement learning is sensitive to background distractions, so it is important to crop the images to the relevant workspace area.
|
Reinforcement learning is sensitive to background distractions, so it is important to crop the images to the relevant workspace area.
|
||||||
|
|
||||||
Visual RL algorithms learn directly from pixel inputs, making them vulnerable to irrelevant visual information. Background elements like changing lighting, shadows, people moving, or objects outside the workspace can confuse the learning process. Good ROI selection should:
|
Visual RL algorithms learn directly from pixel inputs, making them vulnerable to irrelevant visual information. Background elements like changing lighting, shadows, people moving, or objects outside the workspace can confuse the learning process. Good ROI selection should:
|
||||||
|
|
||||||
- Include only the essential workspace where the task happens
|
- Include only the essential workspace where the task happens
|
||||||
- Capture the robot's end-effector and all objects involved in the task
|
- Capture the robot's end-effector and all objects involved in the task
|
||||||
- Exclude unnecessary background elements and distractions
|
- Exclude unnecessary background elements and distractions
|
||||||
@@ -267,6 +287,7 @@ python -m lerobot.scripts.rl.crop_dataset_roi --repo-id username/pick_lift_cube
|
|||||||
5. The script outputs cropping parameters and creates a new cropped dataset
|
5. The script outputs cropping parameters and creates a new cropped dataset
|
||||||
|
|
||||||
Example output:
|
Example output:
|
||||||
|
|
||||||
```
|
```
|
||||||
Selected Rectangular Regions of Interest (top, left, height, width):
|
Selected Rectangular Regions of Interest (top, left, height, width):
|
||||||
observation.images.side: [180, 207, 180, 200]
|
observation.images.side: [180, 207, 180, 200]
|
||||||
@@ -274,11 +295,15 @@ observation.images.front: [180, 250, 120, 150]
|
|||||||
```
|
```
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/crop_dataset.gif" width="600"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/crop_dataset.gif"
|
||||||
|
width="600"
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><i>Interactive cropping tool for selecting regions of interest</i></p>
|
<p align="center">
|
||||||
|
<i>Interactive cropping tool for selecting regions of interest</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
**Updating Configuration**
|
**Updating Configuration**
|
||||||
|
|
||||||
@@ -294,8 +319,7 @@ Add these crop parameters to your training configuration:
|
|||||||
|
|
||||||
**Recommended image resolution**
|
**Recommended image resolution**
|
||||||
|
|
||||||
Most vision-based policies have been validated on square inputs of either **128×128** (default) or **64×64** pixels. We therefore advise setting the resize_size parameter to [128, 128] – or [64, 64] if you need to save GPU memory and bandwidth. Other resolutions are possible but have not been extensively tested.
|
Most vision-based policies have been validated on square inputs of either **128×128** (default) or **64×64** pixels. We therefore advise setting the resize_size parameter to [128, 128] – or [64, 64] if you need to save GPU memory and bandwidth. Other resolutions are possible but have not been extensively tested.
|
||||||
|
|
||||||
|
|
||||||
### Training a Reward Classifier
|
### Training a Reward Classifier
|
||||||
|
|
||||||
@@ -332,13 +356,13 @@ Example configuration section for data collection:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mode": "record",
|
"mode": "record",
|
||||||
"repo_id": "hf_username/dataset_name",
|
"repo_id": "hf_username/dataset_name",
|
||||||
"dataset_root": "data/your_dataset",
|
"dataset_root": "data/your_dataset",
|
||||||
"num_episodes": 20,
|
"num_episodes": 20,
|
||||||
"push_to_hub": true,
|
"push_to_hub": true,
|
||||||
"fps": 10,
|
"fps": 10,
|
||||||
"number_of_steps_after_success": 15
|
"number_of_steps_after_success": 15
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -395,21 +419,25 @@ python -m lerobot.scripts.train --config_path path/to/reward_classifier_train_co
|
|||||||
|
|
||||||
To use your trained reward classifier, configure the `HILSerlRobotEnvConfig` to use your model:
|
To use your trained reward classifier, configure the `HILSerlRobotEnvConfig` to use your model:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
env_config = HILSerlRobotEnvConfig(
|
env_config = HILSerlRobotEnvConfig(
|
||||||
reward_classifier_pretrained_path="path_to_your_pretrained_trained_model",
|
reward_classifier_pretrained_path="path_to_your_pretrained_trained_model",
|
||||||
# Other environment parameters
|
# Other environment parameters
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
or set the argument in the json config file.
|
or set the argument in the json config file.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"reward_classifier_pretrained_path": "path_to_your_pretrained_model"
|
"reward_classifier_pretrained_path": "path_to_your_pretrained_model"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `gym_manipulator.py` to test the model.
|
Run `gym_manipulator.py` to test the model.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config.json
|
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/env_config.json
|
||||||
```
|
```
|
||||||
@@ -422,11 +450,13 @@ The reward classifier will automatically provide rewards based on the visual inp
|
|||||||
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/aractingi/lerobot-example-config-files/tree/main).
|
||||||
|
|
||||||
2. **Collect a dataset**:
|
2. **Collect a dataset**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
python -m lerobot.scripts.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Train the classifier**:
|
3. **Train the classifier**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train --config_path src/lerobot/configs/reward_classifier_train_config.json
|
python -m lerobot.scripts.train --config_path src/lerobot/configs/reward_classifier_train_config.json
|
||||||
```
|
```
|
||||||
@@ -459,6 +489,7 @@ python -m lerobot.scripts.rl.learner --config_path src/lerobot/configs/train_con
|
|||||||
```
|
```
|
||||||
|
|
||||||
The learner:
|
The learner:
|
||||||
|
|
||||||
- Initializes the policy network
|
- Initializes the policy network
|
||||||
- Prepares replay buffers
|
- Prepares replay buffers
|
||||||
- Opens a `gRPC` server to communicate with actors
|
- Opens a `gRPC` server to communicate with actors
|
||||||
@@ -473,6 +504,7 @@ python -m lerobot.scripts.rl.actor --config_path src/lerobot/configs/train_confi
|
|||||||
```
|
```
|
||||||
|
|
||||||
The actor:
|
The actor:
|
||||||
|
|
||||||
- Connects to the learner via `gRPC`
|
- Connects to the learner via `gRPC`
|
||||||
- Initializes the environment
|
- Initializes the environment
|
||||||
- Execute rollouts of the policy to collect experience
|
- Execute rollouts of the policy to collect experience
|
||||||
@@ -496,10 +528,19 @@ The training proceeds automatically:
|
|||||||
- A successful experiment is one where the human has to intervene at the start but then reduces the amount of interventions as the policy improves. You can monitor the intervention rate in the `wandb` dashboard.
|
- A successful experiment is one where the human has to intervene at the start but then reduces the amount of interventions as the policy improves. You can monitor the intervention rate in the `wandb` dashboard.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/hil_effect.png?raw=true" alt="Figure shows the control mappings on a Logitech gamepad." title="Gamepad Control Mapping" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/hil_effect.png?raw=true"
|
||||||
|
alt="Figure shows the control mappings on a Logitech gamepad."
|
||||||
|
title="Gamepad Control Mapping"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><i>Example showing how human interventions help guide policy learning over time</i></p>
|
<p align="center">
|
||||||
|
<i>
|
||||||
|
Example showing how human interventions help guide policy learning over time
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
- The figure shows the plot of the episodic reward over interaction step. The figure shows the effect of human interventions on the policy learning.
|
- The figure shows the plot of the episodic reward over interaction step. The figure shows the effect of human interventions on the policy learning.
|
||||||
- The orange curve is an experiment without any human interventions. While the pink and blue curves are experiments with human interventions.
|
- The orange curve is an experiment without any human interventions. While the pink and blue curves are experiments with human interventions.
|
||||||
@@ -510,7 +551,9 @@ The training proceeds automatically:
|
|||||||
If you have `wandb.enable` set to `true` in your configuration, you can monitor training progress in real-time through the [Weights & Biases](https://wandb.ai/site/) dashboard.
|
If you have `wandb.enable` set to `true` in your configuration, you can monitor training progress in real-time through the [Weights & Biases](https://wandb.ai/site/) dashboard.
|
||||||
|
|
||||||
### Guide to Human Interventions
|
### Guide to Human Interventions
|
||||||
|
|
||||||
The learning process is very sensitive to the intervention strategy. It will takes a few runs to understand how to intervene effectively. Some tips and hints:
|
The learning process is very sensitive to the intervention strategy. It will takes a few runs to understand how to intervene effectively. Some tips and hints:
|
||||||
|
|
||||||
- Allow the policy to explore for a few episodes at the start of training.
|
- Allow the policy to explore for a few episodes at the start of training.
|
||||||
- Avoid intervening for long periods of time. Try to intervene in situation to correct the robot's behaviour when it goes off track.
|
- Avoid intervening for long periods of time. Try to intervene in situation to correct the robot's behaviour when it goes off track.
|
||||||
- Once the policy starts achieving the task, even if its not perfect, you can limit your interventions to simple quick actions like a simple grasping commands.
|
- Once the policy starts achieving the task, even if its not perfect, you can limit your interventions to simple quick actions like a simple grasping commands.
|
||||||
@@ -518,26 +561,36 @@ The learning process is very sensitive to the intervention strategy. It will tak
|
|||||||
The ideal behaviour is that your intervention rate should drop gradually during training as shown in the figure below.
|
The ideal behaviour is that your intervention rate should drop gradually during training as shown in the figure below.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/intervention_rate_tutorial_rl.png?raw=true" alt="Intervention rate" title="Intervention rate during training" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/intervention_rate_tutorial_rl.png?raw=true"
|
||||||
|
alt="Intervention rate"
|
||||||
|
title="Intervention rate during training"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><i>Plot of the intervention rate during a training run on a pick and lift cube task</i></p>
|
<p align="center">
|
||||||
|
<i>
|
||||||
|
Plot of the intervention rate during a training run on a pick and lift cube
|
||||||
|
task
|
||||||
|
</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
### Key hyperparameters to tune
|
### Key hyperparameters to tune
|
||||||
|
|
||||||
Some configuration values have a disproportionate impact on training stability and speed:
|
Some configuration values have a disproportionate impact on training stability and speed:
|
||||||
|
|
||||||
- **`temperature_init`** (`policy.temperature_init`) – initial entropy temperature in SAC. Higher values encourage more exploration; lower values make the policy more deterministic early on. A good starting point is `1e-2`. We observed that setting it too high can make human interventions ineffective and slow down learning.
|
- **`temperature_init`** (`policy.temperature_init`) – initial entropy temperature in SAC. Higher values encourage more exploration; lower values make the policy more deterministic early on. A good starting point is `1e-2`. We observed that setting it too high can make human interventions ineffective and slow down learning.
|
||||||
- **`policy_parameters_push_frequency`** (`policy.actor_learner_config.policy_parameters_push_frequency`) – interval in *seconds* between two weight pushes from the learner to the actor. The default is `4 s`. Decrease to **1-2 s** to provide fresher weights (at the cost of more network traffic); increase only if your connection is slow, as this will reduce sample efficiency.
|
- **`policy_parameters_push_frequency`** (`policy.actor_learner_config.policy_parameters_push_frequency`) – interval in _seconds_ between two weight pushes from the learner to the actor. The default is `4 s`. Decrease to **1-2 s** to provide fresher weights (at the cost of more network traffic); increase only if your connection is slow, as this will reduce sample efficiency.
|
||||||
- **`storage_device`** (`policy.storage_device`) – device on which the learner keeps the policy parameters. If you have spare GPU memory, set this to `"cuda"` (instead of the default `"cpu"`). Keeping the weights on-GPU removes CPU→GPU transfer overhead and can significantly increase the number of learner updates per second.
|
- **`storage_device`** (`policy.storage_device`) – device on which the learner keeps the policy parameters. If you have spare GPU memory, set this to `"cuda"` (instead of the default `"cpu"`). Keeping the weights on-GPU removes CPU→GPU transfer overhead and can significantly increase the number of learner updates per second.
|
||||||
|
|
||||||
|
|
||||||
Congrats 🎉, you have finished this tutorial!
|
Congrats 🎉, you have finished this tutorial!
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|
||||||
Paper citation:
|
Paper citation:
|
||||||
|
|
||||||
```
|
```
|
||||||
@article{luo2024precise,
|
@article{luo2024precise,
|
||||||
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ This guide explains how to use the `gym_hil` simulation environments as an alter
|
|||||||
|
|
||||||
Currently, the main environment is a Franka Panda robot simulation based on MuJoCo, with tasks like picking up a cube.
|
Currently, the main environment is a Franka Panda robot simulation based on MuJoCo, with tasks like picking up a cube.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
First, install the `gym_hil` package within the LeRobot environment:
|
First, install the `gym_hil` package within the LeRobot environment:
|
||||||
@@ -25,8 +24,6 @@ pip install -e ".[hilserl]"
|
|||||||
- A gamepad or keyboard to control the robot
|
- A gamepad or keyboard to control the robot
|
||||||
- A Nvidia GPU
|
- A Nvidia GPU
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## 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/aractingi/lerobot-example-config-files/blob/main/gym_hil_env.json). Key configuration sections include:
|
||||||
@@ -35,14 +32,15 @@ To use `gym_hil` with LeRobot, you need to create a configuration file. An examp
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "hil",
|
"type": "hil",
|
||||||
"name": "franka_sim",
|
"name": "franka_sim",
|
||||||
"task": "PandaPickCubeGamepad-v0",
|
"task": "PandaPickCubeGamepad-v0",
|
||||||
"device": "cuda"
|
"device": "cuda"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Available tasks:
|
Available tasks:
|
||||||
|
|
||||||
- `PandaPickCubeBase-v0`: Basic environment
|
- `PandaPickCubeBase-v0`: Basic environment
|
||||||
- `PandaPickCubeGamepad-v0`: With gamepad control
|
- `PandaPickCubeGamepad-v0`: With gamepad control
|
||||||
- `PandaPickCubeKeyboard-v0`: With keyboard control
|
- `PandaPickCubeKeyboard-v0`: With keyboard control
|
||||||
@@ -65,6 +63,7 @@ Available tasks:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Important parameters:
|
Important parameters:
|
||||||
|
|
||||||
- `gripper_penalty`: Penalty for excessive gripper movement
|
- `gripper_penalty`: Penalty for excessive gripper movement
|
||||||
- `use_gripper`: Whether to enable gripper control
|
- `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
|
- `end_effector_step_sizes`: Size of the steps in the x,y,z axes of the end-effector
|
||||||
@@ -76,40 +75,49 @@ Important parameters:
|
|||||||
|
|
||||||
To run the environment, set mode to null:
|
To run the environment, set mode to null:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### Recording a Dataset
|
### Recording a Dataset
|
||||||
|
|
||||||
To collect a dataset, set the mode to `record` whilst defining the repo_id and number of episodes to record:
|
To collect a dataset, set the mode to `record` whilst defining the repo_id and number of episodes to record:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
python -m lerobot.scripts.rl.gym_manipulator --config_path path/to/gym_hil_env.json
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### Training a Policy
|
### 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/aractingi/lerobot-example-config-files/blob/main/train_gym_hil_env.json) and run the actor and learner servers:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
python -m lerobot.scripts.rl.actor --config_path path/to/train_gym_hil_env.json
|
python -m lerobot.scripts.rl.actor --config_path path/to/train_gym_hil_env.json
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
In a different terminal, run the learner server:
|
In a different terminal, run the learner server:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
python -m lerobot.scripts.rl.learner --config_path path/to/train_gym_hil_env.json
|
python -m lerobot.scripts.rl.learner --config_path path/to/train_gym_hil_env.json
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Congrats 🎉, you have finished this tutorial!
|
Congrats 🎉, you have finished this tutorial!
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|
||||||
Paper citation:
|
Paper citation:
|
||||||
|
|
||||||
```
|
```
|
||||||
@article{luo2024precise,
|
@article{luo2024precise,
|
||||||
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
This tutorial will explain how to train a neural network to control a real robot autonomously.
|
This tutorial will explain how to train a neural network to control a real robot autonomously.
|
||||||
|
|
||||||
**You'll learn:**
|
**You'll learn:**
|
||||||
|
|
||||||
1. How to record and visualize your dataset.
|
1. How to record and visualize your dataset.
|
||||||
2. How to train a policy using your data and prepare it for evaluation.
|
2. How to train a policy using your data and prepare it for evaluation.
|
||||||
3. How to evaluate your policy and visualize the results.
|
3. How to evaluate your policy and visualize the results.
|
||||||
@@ -14,7 +15,10 @@ By following these steps, you'll be able to replicate tasks, such as picking up
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/lerobot_task.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/lerobot_task.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,6 +55,8 @@ python -m lerobot.teleoperate \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so101_leader import SO101LeaderConfig, SO101Leader
|
from lerobot.teleoperators.so101_leader import SO101LeaderConfig, SO101Leader
|
||||||
from lerobot.robots.so101_follower import SO101FollowerConfig, SO101Follower
|
from lerobot.robots.so101_follower import SO101FollowerConfig, SO101Follower
|
||||||
@@ -74,10 +80,13 @@ while True:
|
|||||||
action = teleop_device.get_action()
|
action = teleop_device.get_action()
|
||||||
robot.send_action(action)
|
robot.send_action(action)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
The teleoperate command will automatically:
|
The teleoperate command will automatically:
|
||||||
|
|
||||||
1. Identify any missing calibrations and initiate the calibration procedure.
|
1. Identify any missing calibrations and initiate the calibration procedure.
|
||||||
2. Connect the robot and teleop device and start teleoperation.
|
2. Connect the robot and teleop device and start teleoperation.
|
||||||
|
|
||||||
@@ -104,6 +113,8 @@ python -m lerobot.teleoperate \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
||||||
from lerobot.teleoperators.koch_leader import KochLeaderConfig, KochLeader
|
from lerobot.teleoperators.koch_leader import KochLeaderConfig, KochLeader
|
||||||
@@ -134,6 +145,8 @@ while True:
|
|||||||
action = teleop_device.get_action()
|
action = teleop_device.get_action()
|
||||||
robot.send_action(action)
|
robot.send_action(action)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -144,11 +157,13 @@ Once you're familiar with teleoperation, you can record your first dataset.
|
|||||||
We use the Hugging Face hub features for uploading your dataset. If you haven't previously used the Hub, make sure you can login via the cli using a write-access token, this token can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens).
|
We use the Hugging Face hub features for uploading your dataset. If you haven't previously used the Hub, make sure you can login via the cli using a write-access token, this token can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens).
|
||||||
|
|
||||||
Add your token to the CLI by running this command:
|
Add your token to the CLI by running this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
||||||
```
|
```
|
||||||
|
|
||||||
Then store your Hugging Face repository name in a variable:
|
Then store your Hugging Face repository name in a variable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HF_USER=$(huggingface-cli whoami | head -n 1)
|
HF_USER=$(huggingface-cli whoami | head -n 1)
|
||||||
echo $HF_USER
|
echo $HF_USER
|
||||||
@@ -174,6 +189,8 @@ python -m lerobot.record \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
||||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||||
@@ -270,40 +287,49 @@ robot.disconnect()
|
|||||||
teleop.disconnect()
|
teleop.disconnect()
|
||||||
dataset.push_to_hub()
|
dataset.push_to_hub()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
#### Dataset upload
|
#### Dataset upload
|
||||||
|
|
||||||
Locally, your dataset is stored in this folder: `~/.cache/huggingface/lerobot/{repo-id}`. At the end of data recording, your dataset will be uploaded on your Hugging Face page (e.g. https://huggingface.co/datasets/cadene/so101_test) that you can obtain by running:
|
Locally, your dataset is stored in this folder: `~/.cache/huggingface/lerobot/{repo-id}`. At the end of data recording, your dataset will be uploaded on your Hugging Face page (e.g. https://huggingface.co/datasets/cadene/so101_test) that you can obtain by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo https://huggingface.co/datasets/${HF_USER}/so101_test
|
echo https://huggingface.co/datasets/${HF_USER}/so101_test
|
||||||
```
|
```
|
||||||
|
|
||||||
Your dataset will be automatically tagged with `LeRobot` for the community to find it easily, and you can also add custom tags (in this case `tutorial` for example).
|
Your dataset will be automatically tagged with `LeRobot` for the community to find it easily, and you can also add custom tags (in this case `tutorial` for example).
|
||||||
|
|
||||||
You can look for other LeRobot datasets on the hub by searching for `LeRobot` [tags](https://huggingface.co/datasets?other=LeRobot).
|
You can look for other LeRobot datasets on the hub by searching for `LeRobot` [tags](https://huggingface.co/datasets?other=LeRobot).
|
||||||
|
|
||||||
You can also push your local dataset to the Hub manually, running:
|
You can also push your local dataset to the Hub manually, running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli upload ${HF_USER}/record-test ~/.cache/huggingface/lerobot/{repo-id} --repo-type dataset
|
huggingface-cli upload ${HF_USER}/record-test ~/.cache/huggingface/lerobot/{repo-id} --repo-type dataset
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Record function
|
#### Record function
|
||||||
|
|
||||||
The `record` function provides a suite of tools for capturing and managing data during robot operation:
|
The `record` function provides a suite of tools for capturing and managing data during robot operation:
|
||||||
|
|
||||||
##### 1. Data Storage
|
##### 1. Data Storage
|
||||||
|
|
||||||
- Data is stored using the `LeRobotDataset` format and is stored on disk during recording.
|
- Data is stored using the `LeRobotDataset` format and is stored on disk during recording.
|
||||||
- By default, the dataset is pushed to your Hugging Face page after recording.
|
- By default, the dataset is pushed to your Hugging Face page after recording.
|
||||||
- To disable uploading, use `--dataset.push_to_hub=False`.
|
- To disable uploading, use `--dataset.push_to_hub=False`.
|
||||||
|
|
||||||
##### 2. Checkpointing and Resuming
|
##### 2. Checkpointing and Resuming
|
||||||
|
|
||||||
- Checkpoints are automatically created during recording.
|
- Checkpoints are automatically created during recording.
|
||||||
- If an issue occurs, you can resume by re-running the same command with `--resume=true`.
|
- If an issue occurs, you can resume by re-running the same command with `--resume=true`.
|
||||||
- To start recording from scratch, **manually delete** the dataset directory.
|
- To start recording from scratch, **manually delete** the dataset directory.
|
||||||
|
|
||||||
##### 3. Recording Parameters
|
##### 3. Recording Parameters
|
||||||
|
|
||||||
Set the flow of data recording using command-line arguments:
|
Set the flow of data recording using command-line arguments:
|
||||||
|
|
||||||
- `--dataset.episode_time_s=60`
|
- `--dataset.episode_time_s=60`
|
||||||
Duration of each data recording episode (default: **60 seconds**).
|
Duration of each data recording episode (default: **60 seconds**).
|
||||||
- `--dataset.reset_time_s=60`
|
- `--dataset.reset_time_s=60`
|
||||||
@@ -312,7 +338,9 @@ Set the flow of data recording using command-line arguments:
|
|||||||
Total number of episodes to record (default: **50**).
|
Total number of episodes to record (default: **50**).
|
||||||
|
|
||||||
##### 4. Keyboard Controls During Recording
|
##### 4. Keyboard Controls During Recording
|
||||||
|
|
||||||
Control the data recording flow using keyboard shortcuts:
|
Control the data recording flow using keyboard shortcuts:
|
||||||
|
|
||||||
- Press **Right Arrow (`→`)**: Early stop the current episode or reset time and move to the next.
|
- Press **Right Arrow (`→`)**: Early stop the current episode or reset time and move to the next.
|
||||||
- Press **Left Arrow (`←`)**: Cancel the current episode and re-record it.
|
- Press **Left Arrow (`←`)**: Cancel the current episode and re-record it.
|
||||||
- Press **Escape (`ESC`)**: Immediately stop the session, encode videos, and upload the dataset.
|
- Press **Escape (`ESC`)**: Immediately stop the session, encode videos, and upload the dataset.
|
||||||
@@ -327,13 +355,14 @@ Avoid adding too much variation too quickly, as it may hinder your results.
|
|||||||
|
|
||||||
If you want to dive deeper into this important topic, you can check out the [blog post](https://huggingface.co/blog/lerobot-datasets#what-makes-a-good-dataset) we wrote on what makes a good dataset.
|
If you want to dive deeper into this important topic, you can check out the [blog post](https://huggingface.co/blog/lerobot-datasets#what-makes-a-good-dataset) we wrote on what makes a good dataset.
|
||||||
|
|
||||||
|
|
||||||
#### Troubleshooting:
|
#### Troubleshooting:
|
||||||
|
|
||||||
- On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux).
|
- On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux).
|
||||||
|
|
||||||
## Visualize a dataset
|
## Visualize a dataset
|
||||||
|
|
||||||
If you uploaded your dataset to the hub with `--control.push_to_hub=true`, you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id given by:
|
If you uploaded your dataset to the hub with `--control.push_to_hub=true`, you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id given by:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo ${HF_USER}/so101_test
|
echo ${HF_USER}/so101_test
|
||||||
```
|
```
|
||||||
@@ -356,6 +385,8 @@ python -m lerobot.replay \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -388,6 +419,8 @@ for idx in range(dataset.num_frames):
|
|||||||
|
|
||||||
robot.disconnect()
|
robot.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -396,6 +429,7 @@ Your robot should replicate movements similar to those you recorded. For example
|
|||||||
## Train a policy
|
## Train a policy
|
||||||
|
|
||||||
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--dataset.repo_id=${HF_USER}/so101_test \
|
--dataset.repo_id=${HF_USER}/so101_test \
|
||||||
@@ -408,14 +442,16 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Let's explain the command:
|
Let's explain the command:
|
||||||
|
|
||||||
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/so101_test`.
|
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/so101_test`.
|
||||||
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
||||||
4. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
3. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
||||||
5. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
4. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
||||||
|
|
||||||
Training should take several hours. You will find checkpoints in `outputs/train/act_so101_test/checkpoints`.
|
Training should take several hours. You will find checkpoints in `outputs/train/act_so101_test/checkpoints`.
|
||||||
|
|
||||||
To resume training from a checkpoint, below is an example command to resume from `last` checkpoint of the `act_so101_test` policy:
|
To resume training from a checkpoint, below is an example command to resume from `last` checkpoint of the `act_so101_test` policy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=outputs/train/act_so101_test/checkpoints/last/pretrained_model/train_config.json \
|
--config_path=outputs/train/act_so101_test/checkpoints/last/pretrained_model/train_config.json \
|
||||||
@@ -427,17 +463,20 @@ If you do not want to push your model to the hub after training use `--policy.pu
|
|||||||
Additionally you can provide extra `tags` or specify a `license` for your model or make the model repo `private` by adding this: `--policy.private=true --policy.tags=\[ppo,rl\] --policy.license=mit`
|
Additionally you can provide extra `tags` or specify a `license` for your model or make the model repo `private` by adding this: `--policy.private=true --policy.tags=\[ppo,rl\] --policy.license=mit`
|
||||||
|
|
||||||
#### Train using Collab
|
#### Train using Collab
|
||||||
|
|
||||||
If your local computer doesn't have a powerful GPU you could utilize Google Collab to train your model by following the [ACT training notebook](./notebooks#training-act).
|
If your local computer doesn't have a powerful GPU you could utilize Google Collab to train your model by following the [ACT training notebook](./notebooks#training-act).
|
||||||
|
|
||||||
#### Upload policy checkpoints
|
#### Upload policy checkpoints
|
||||||
|
|
||||||
Once training is done, upload the latest checkpoint with:
|
Once training is done, upload the latest checkpoint with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli upload ${HF_USER}/act_so101_test \
|
huggingface-cli upload ${HF_USER}/act_so101_test \
|
||||||
outputs/train/act_so101_test/checkpoints/last/pretrained_model
|
outputs/train/act_so101_test/checkpoints/last/pretrained_model
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also upload intermediate checkpoints with:
|
You can also upload intermediate checkpoints with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CKPT=010000
|
CKPT=010000
|
||||||
huggingface-cli upload ${HF_USER}/act_so101_test${CKPT} \
|
huggingface-cli upload ${HF_USER}/act_so101_test${CKPT} \
|
||||||
@@ -467,6 +506,8 @@ python -m lerobot.record \
|
|||||||
```
|
```
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
|
||||||
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
from lerobot.datasets.lerobot_dataset import LeRobotDataset
|
||||||
@@ -539,9 +580,12 @@ for episode_idx in range(NUM_EPISODES):
|
|||||||
robot.disconnect()
|
robot.disconnect()
|
||||||
dataset.push_to_hub()
|
dataset.push_to_hub()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
As you can see, it's almost the same command as previously used to record your training dataset. Two things changed:
|
As you can see, it's almost the same command as previously used to record your training dataset. Two things changed:
|
||||||
1. There is an additional `--control.policy.path` argument which indicates the path to your policy checkpoint with (e.g. `outputs/train/eval_act_so101_test/checkpoints/last/pretrained_model`). You can also use the model repository if you uploaded a model checkpoint to the hub (e.g. `${HF_USER}/act_so101_test`).
|
|
||||||
|
1. There is an additional `--control.policy.path` argument which indicates the path to your policy checkpoint with (e.g. `outputs/train/eval_act_so101_test/checkpoints/last/pretrained_model`). You can also use the model repository if you uploaded a model checkpoint to the hub (e.g. `${HF_USER}/act_so101_test`).
|
||||||
2. The name of dataset begins by `eval` to reflect that you are running inference (e.g. `${HF_USER}/eval_act_so101_test`).
|
2. The name of dataset begins by `eval` to reflect that you are running inference (e.g. `${HF_USER}/eval_act_so101_test`).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
This tutorial will explain how to train a neural network to control a robot in simulation with imitation learning.
|
This tutorial will explain how to train a neural network to control a robot in simulation with imitation learning.
|
||||||
|
|
||||||
**You'll learn:**
|
**You'll learn:**
|
||||||
|
|
||||||
1. How to record a dataset in simulation with [gym-hil](https://github.com/huggingface/gym-hil) and visualize the dataset.
|
1. How to record a dataset in simulation with [gym-hil](https://github.com/huggingface/gym-hil) and visualize the dataset.
|
||||||
2. How to train a policy using your data.
|
2. How to train a policy using your data.
|
||||||
3. How to evaluate your policy in simulation and visualize the results.
|
3. How to evaluate your policy in simulation and visualize the results.
|
||||||
@@ -55,13 +56,21 @@ Note that to teleoperate the robot you have to hold the "Human Take Over Pause P
|
|||||||
**Gamepad Controls**
|
**Gamepad Controls**
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/gamepad_guide.jpg?raw=true" alt="Figure shows the control mappings on a Logitech gamepad." title="Gamepad Control Mapping" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/gamepad_guide.jpg?raw=true"
|
||||||
|
alt="Figure shows the control mappings on a Logitech gamepad."
|
||||||
|
title="Gamepad Control Mapping"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>Gamepad button mapping for robot control and episode management</i>
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>Gamepad button mapping for robot control and episode management</i></p>
|
|
||||||
|
|
||||||
**Keyboard controls**
|
**Keyboard controls**
|
||||||
|
|
||||||
For keyboard controls use the `spacebar` to enable control and the following keys to move the robot:
|
For keyboard controls use the `spacebar` to enable control and the following keys to move the robot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Arrow keys: Move in X-Y plane
|
Arrow keys: Move in X-Y plane
|
||||||
Shift and Shift_R: Move in Z axis
|
Shift and Shift_R: Move in Z axis
|
||||||
@@ -74,14 +83,21 @@ For keyboard controls use the `spacebar` to enable control and the following key
|
|||||||
If you uploaded your dataset to the hub you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id.
|
If you uploaded your dataset to the hub you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/dataset_visualizer_sim.png" alt="Figure shows the dataset visualizer" title="Dataset visualization" width="100%"></img>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/dataset_visualizer_sim.png"
|
||||||
|
alt="Figure shows the dataset visualizer"
|
||||||
|
title="Dataset visualization"
|
||||||
|
width="100%"
|
||||||
|
></img>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<i>Dataset visualizer</i>
|
||||||
</p>
|
</p>
|
||||||
<p align="center"><i>Dataset visualizer</i></p>
|
|
||||||
|
|
||||||
|
|
||||||
## Train a policy
|
## Train a policy
|
||||||
|
|
||||||
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--dataset.repo_id=${HF_USER}/il_gym \
|
--dataset.repo_id=${HF_USER}/il_gym \
|
||||||
@@ -93,25 +109,29 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Let's explain the command:
|
Let's explain the command:
|
||||||
|
|
||||||
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/il_gym`.
|
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/il_gym`.
|
||||||
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
||||||
4. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
3. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
||||||
5. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
4. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
||||||
|
|
||||||
Training should take several hours, 100k steps (which is the default) will take about 1h on Nvidia A100. You will find checkpoints in `outputs/train/il_sim_test/checkpoints`.
|
Training should take several hours, 100k steps (which is the default) will take about 1h on Nvidia A100. You will find checkpoints in `outputs/train/il_sim_test/checkpoints`.
|
||||||
|
|
||||||
#### Train using Collab
|
#### Train using Collab
|
||||||
|
|
||||||
If your local computer doesn't have a powerful GPU you could utilize Google Collab to train your model by following the [ACT training notebook](./notebooks#training-act).
|
If your local computer doesn't have a powerful GPU you could utilize Google Collab to train your model by following the [ACT training notebook](./notebooks#training-act).
|
||||||
|
|
||||||
#### Upload policy checkpoints
|
#### Upload policy checkpoints
|
||||||
|
|
||||||
Once training is done, upload the latest checkpoint with:
|
Once training is done, upload the latest checkpoint with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli upload ${HF_USER}/il_sim_test \
|
huggingface-cli upload ${HF_USER}/il_sim_test \
|
||||||
outputs/train/il_sim_test/checkpoints/last/pretrained_model
|
outputs/train/il_sim_test/checkpoints/last/pretrained_model
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also upload intermediate checkpoints with:
|
You can also upload intermediate checkpoints with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CKPT=010000
|
CKPT=010000
|
||||||
huggingface-cli upload ${HF_USER}/il_sim_test${CKPT} \
|
huggingface-cli upload ${HF_USER}/il_sim_test${CKPT} \
|
||||||
@@ -144,9 +164,9 @@ mjpython -m lerobot.scripts.rl.eval_policy --config_path=path/to/eval_config_gym
|
|||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> While the main workflow of training ACT in simulation is straightforward, there is significant room for exploring how to set up the task, define the initial state of the environment, and determine the type of data required during collection to learn the most effective policy. If your trained policy doesn't perform well, investigate the quality of the dataset it was trained on using our visualizers, as well as the action values and various hyperparameters related to ACT and the simulation.
|
> While the main workflow of training ACT in simulation is straightforward, there is significant room for exploring how to set up the task, define the initial state of the environment, and determine the type of data required during collection to learn the most effective policy. If your trained policy doesn't perform well, investigate the quality of the dataset it was trained on using our visualizers, as well as the action values and various hyperparameters related to ACT and the simulation.
|
||||||
|
|
||||||
Congrats 🎉, you have finished this tutorial. If you want to continue with using LeRobot in simulation follow this [Tutorial on reinforcement learning in sim with HIL-SERL](https://huggingface.co/docs/lerobot/hilserl_sim)
|
Congrats 🎉, you have finished this tutorial. If you want to continue with using LeRobot in simulation follow this [Tutorial on reinforcement learning in sim with HIL-SERL](https://huggingface.co/docs/lerobot/hilserl_sim)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a target="_blank" href="https://huggingface.co/lerobot">
|
<a target="_blank" href="https://huggingface.co/lerobot">
|
||||||
<img alt="HuggingFace Expert Acceleration Program" src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/lerobot-logo-thumbnail.png" style="width: 100%"></img>
|
<img
|
||||||
|
alt="HuggingFace Expert Acceleration Program"
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/lerobot-logo-thumbnail.png"
|
||||||
|
style="width: 100%"
|
||||||
|
></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,45 +5,56 @@
|
|||||||
Currently only available from source.
|
Currently only available from source.
|
||||||
|
|
||||||
Download our source code:
|
Download our source code:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/huggingface/lerobot.git
|
git clone https://github.com/huggingface/lerobot.git
|
||||||
cd lerobot
|
cd lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a virtual environment with Python 3.10, using [`Miniconda`](https://docs.anaconda.com/miniconda/install/#quick-command-line-install)
|
Create a virtual environment with Python 3.10, using [`Miniconda`](https://docs.anaconda.com/miniconda/install/#quick-command-line-install)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda create -y -n lerobot python=3.10
|
conda create -y -n lerobot python=3.10
|
||||||
```
|
```
|
||||||
|
|
||||||
Then activate your conda environment, you have to do this each time you open a shell to use lerobot:
|
Then activate your conda environment, you have to do this each time you open a shell to use lerobot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda activate lerobot
|
conda activate lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
When using `miniconda`, install `ffmpeg` in your environment:
|
When using `miniconda`, install `ffmpeg` in your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda install ffmpeg -c conda-forge
|
conda install ffmpeg -c conda-forge
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can:
|
> This usually installs `ffmpeg 7.X` for your platform compiled with the `libsvtav1` encoder. If `libsvtav1` is not supported (check supported encoders with `ffmpeg -encoders`), you can:
|
||||||
> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using:
|
>
|
||||||
> ```bash
|
> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using:
|
||||||
> conda install ffmpeg=7.1.1 -c conda-forge
|
>
|
||||||
> ```
|
> ```bash
|
||||||
> - _[On Linux only]_ If you want to bring your own ffmpeg: Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`.
|
> conda install ffmpeg=7.1.1 -c conda-forge
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> - _[On Linux only]_ If you want to bring your own ffmpeg: Install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), and make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`.
|
||||||
|
|
||||||
Install 🤗 LeRobot:
|
Install 🤗 LeRobot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
If you encounter build errors, you may need to install additional dependencies: `cmake`, `build-essential`, and `ffmpeg libs`.
|
If you encounter build errors, you may need to install additional dependencies: `cmake`, `build-essential`, and `ffmpeg libs`.
|
||||||
To install these for linux run:
|
To install these for linux run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install cmake build-essential python-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev pkg-config
|
sudo apt-get install cmake build-essential python-dev pkg-config libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev pkg-config
|
||||||
```
|
```
|
||||||
|
|
||||||
For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg)
|
For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/installation.html#bring-your-own-ffmpeg)
|
||||||
|
|
||||||
## Optional dependencies
|
## Optional dependencies
|
||||||
@@ -51,20 +62,26 @@ For other systems, see: [Compiling PyAV](https://pyav.org/docs/develop/overview/
|
|||||||
LeRobot provides optional extras for specific functionalities. Multiple extras can be combined (e.g., `.[aloha,feetech]`). For all available extras, refer to `pyproject.toml`.
|
LeRobot provides optional extras for specific functionalities. Multiple extras can be combined (e.g., `.[aloha,feetech]`). For all available extras, refer to `pyproject.toml`.
|
||||||
|
|
||||||
### Simulations
|
### Simulations
|
||||||
|
|
||||||
Install environment packages: `aloha` ([gym-aloha](https://github.com/huggingface/gym-aloha)), `xarm` ([gym-xarm](https://github.com/huggingface/gym-xarm)), or `pusht` ([gym-pusht](https://github.com/huggingface/gym-pusht))
|
Install environment packages: `aloha` ([gym-aloha](https://github.com/huggingface/gym-aloha)), `xarm` ([gym-xarm](https://github.com/huggingface/gym-xarm)), or `pusht` ([gym-pusht](https://github.com/huggingface/gym-pusht))
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[aloha]" # or "[pusht]" for example
|
pip install -e ".[aloha]" # or "[pusht]" for example
|
||||||
```
|
```
|
||||||
|
|
||||||
### Motor Control
|
### Motor Control
|
||||||
|
|
||||||
For Koch v1.1 install the Dynamixel SDK, for SO100/SO101/Moss install the Feetech SDK.
|
For Koch v1.1 install the Dynamixel SDK, for SO100/SO101/Moss install the Feetech SDK.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[feetech]" # or "[dynamixel]" for example
|
pip install -e ".[feetech]" # or "[dynamixel]" for example
|
||||||
```
|
```
|
||||||
|
|
||||||
### Experiment Tracking
|
### Experiment Tracking
|
||||||
|
|
||||||
To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with
|
To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wandb login
|
wandb login
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,16 +21,13 @@ Please refer to the [`MotorsBus`](https://github.com/huggingface/lerobot/blob/ma
|
|||||||
For a good example of how it can be used, you can have a look at our own [SO101 follower implementation](https://github.com/huggingface/lerobot/blob/main/lerobot/robots/so101_follower/so101_follower.py)
|
For a good example of how it can be used, you can have a look at our own [SO101 follower implementation](https://github.com/huggingface/lerobot/blob/main/lerobot/robots/so101_follower/so101_follower.py)
|
||||||
|
|
||||||
Use these if compatible. Otherwise, you'll need to find or write a Python interface (not covered in this tutorial):
|
Use these if compatible. Otherwise, you'll need to find or write a Python interface (not covered in this tutorial):
|
||||||
|
|
||||||
- Find an existing SDK in Python (or use bindings to C/C++)
|
- Find an existing SDK in Python (or use bindings to C/C++)
|
||||||
- Or implement a basic communication wrapper (e.g., via pyserial, socket, or CANopen)
|
- Or implement a basic communication wrapper (e.g., via pyserial, socket, or CANopen)
|
||||||
|
|
||||||
You're not alone—many community contributions use custom boards or firmware!
|
You're not alone—many community contributions use custom boards or firmware!
|
||||||
|
|
||||||
For Feetech and Dynamixel, we currently support these servos:
|
For Feetech and Dynamixel, we currently support these servos: - Feetech: - STS & SMS series (protocol 0): `sts3215`, `sts3250`, `sm8512bl` - SCS series (protocol 1): `scs0009` - Dynamixel (protocol 2.0 only): `xl330-m077`, `xl330-m288`, `xl430-w250`, `xm430-w350`, `xm540-w270`, `xc430-w150`
|
||||||
- Feetech:
|
|
||||||
- STS & SMS series (protocol 0): `sts3215`, `sts3250`, `sm8512bl`
|
|
||||||
- SCS series (protocol 1): `scs0009`
|
|
||||||
- Dynamixel (protocol 2.0 only): `xl330-m077`, `xl330-m288`, `xl430-w250`, `xm430-w350`, `xm540-w270`, `xc430-w150`
|
|
||||||
|
|
||||||
If you are using Feetech or Dynamixel servos that are not in this list, you can add those in the [Feetech table](https://github.com/huggingface/lerobot/blob/main/lerobot/motors/feetech/tables.py) or [Dynamixel table](https://github.com/huggingface/lerobot/blob/main/lerobot/motors/dynamixel/tables.py). Depending on the model, this will require you to add model-specific information. In most cases though, there shouldn't be a lot of additions to do.
|
If you are using Feetech or Dynamixel servos that are not in this list, you can add those in the [Feetech table](https://github.com/huggingface/lerobot/blob/main/lerobot/motors/feetech/tables.py) or [Dynamixel table](https://github.com/huggingface/lerobot/blob/main/lerobot/motors/dynamixel/tables.py). Depending on the model, this will require you to add model-specific information. In most cases though, there shouldn't be a lot of additions to do.
|
||||||
|
|
||||||
@@ -41,6 +38,8 @@ In the next sections, we'll use a `FeetechMotorsBus` as the motors interface for
|
|||||||
You’ll first need to specify the config class and a string identifier (`name`) for your robot. If your robot has special needs that you'd like to be able to change easily, it should go here (e.g. port/address, baudrate).
|
You’ll first need to specify the config class and a string identifier (`name`) for your robot. If your robot has special needs that you'd like to be able to change easily, it should go here (e.g. port/address, baudrate).
|
||||||
|
|
||||||
Here, we'll add the port name and one camera by default for our robot:
|
Here, we'll add the port name and one camera by default for our robot:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
@@ -64,6 +63,7 @@ class MyCoolRobotConfig(RobotConfig):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
Have a look at our [Cameras tutorial](./cameras) to understand how to detect and add your camera.
|
Have a look at our [Cameras tutorial](./cameras) to understand how to detect and add your camera.
|
||||||
|
|
||||||
@@ -71,6 +71,7 @@ Next, we'll create our actual robot class which inherits from `Robot`. This abst
|
|||||||
|
|
||||||
Here we'll create a simple 5-DoF robot with one camera. It could be a simple arm but notice that the `Robot` abstract class does not assume anything on your robot's form factor. You can let you imagination run wild when designing new robots!
|
Here we'll create a simple 5-DoF robot with one camera. It could be a simple arm but notice that the `Robot` abstract class does not assume anything on your robot's form factor. You can let you imagination run wild when designing new robots!
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
from lerobot.motors import Motor, MotorNormMode
|
from lerobot.motors import Motor, MotorNormMode
|
||||||
@@ -96,10 +97,11 @@ class MyCoolRobot(Robot):
|
|||||||
)
|
)
|
||||||
self.cameras = make_cameras_from_configs(config.cameras)
|
self.cameras = make_cameras_from_configs(config.cameras)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Step 2: Define Observation and Action Features
|
## Step 2: Define Observation and Action Features
|
||||||
|
|
||||||
These two properties define the *interface contract* between your robot and tools that consume it (such as data collection or learning pipelines).
|
These two properties define the _interface contract_ between your robot and tools that consume it (such as data collection or learning pipelines).
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Note that these properties must be callable even if the robot is not yet connected, so avoid relying on runtime hardware state to define them.
|
> Note that these properties must be callable even if the robot is not yet connected, so avoid relying on runtime hardware state to define them.
|
||||||
@@ -109,6 +111,8 @@ These two properties define the *interface contract* between your robot and tool
|
|||||||
This property should return a dictionary describing the structure of sensor outputs from your robot. The keys match what `get_observation()` returns, and the values describe either the shape (for arrays/images) or the type (for simple values).
|
This property should return a dictionary describing the structure of sensor outputs from your robot. The keys match what `get_observation()` returns, and the values describe either the shape (for arrays/images) or the type (for simple values).
|
||||||
|
|
||||||
Example for our 5-DoF arm with one camera:
|
Example for our 5-DoF arm with one camera:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
@property
|
@property
|
||||||
def _motors_ft(self) -> dict[str, type]:
|
def _motors_ft(self) -> dict[str, type]:
|
||||||
@@ -130,6 +134,8 @@ def _cameras_ft(self) -> dict[str, tuple]:
|
|||||||
def observation_features(self) -> dict:
|
def observation_features(self) -> dict:
|
||||||
return {**self._motors_ft, **self._cameras_ft}
|
return {**self._motors_ft, **self._cameras_ft}
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
In this case, observations consist of a simple dict storing each motor's position and a camera image.
|
In this case, observations consist of a simple dict storing each motor's position and a camera image.
|
||||||
|
|
||||||
### `action_features`
|
### `action_features`
|
||||||
@@ -137,10 +143,13 @@ In this case, observations consist of a simple dict storing each motor's positio
|
|||||||
This property describes the commands your robot expects via `send_action()`. Again, keys must match the expected input format, and values define the shape/type of each command.
|
This property describes the commands your robot expects via `send_action()`. Again, keys must match the expected input format, and values define the shape/type of each command.
|
||||||
|
|
||||||
Here, we simply use the same joints proprioceptive features (`self._motors_ft`) as with `observation_features`: the action sent will simply the goal position for each motor.
|
Here, we simply use the same joints proprioceptive features (`self._motors_ft`) as with `observation_features`: the action sent will simply the goal position for each motor.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def action_features(self) -> dict:
|
def action_features(self) -> dict:
|
||||||
return self._motors_ft
|
return self._motors_ft
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Step 3: Handle Connection and Disconnection
|
## Step 3: Handle Connection and Disconnection
|
||||||
|
|
||||||
@@ -150,16 +159,19 @@ These methods should handle opening and closing communication with your hardware
|
|||||||
|
|
||||||
This property should simply reflect that communication with the robot's hardware is established. When this property is `True`, it should be possible to read and write to the hardware using `get_observation()` and `send_action()`.
|
This property should simply reflect that communication with the robot's hardware is established. When this property is `True`, it should be possible to read and write to the hardware using `get_observation()` and `send_action()`.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
@property
|
@property
|
||||||
def is_connected(self) -> bool:
|
def is_connected(self) -> bool:
|
||||||
return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values())
|
return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values())
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### `connect()`
|
### `connect()`
|
||||||
|
|
||||||
This method should establish communication with the hardware. Moreover, if your robot needs calibration and is not calibrated, it should start a calibration procedure by default. If your robot needs some specific configuration, this should also be called here.
|
This method should establish communication with the hardware. Moreover, if your robot needs calibration and is not calibrated, it should start a calibration procedure by default. If your robot needs some specific configuration, this should also be called here.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def connect(self, calibrate: bool = True) -> None:
|
def connect(self, calibrate: bool = True) -> None:
|
||||||
self.bus.connect()
|
self.bus.connect()
|
||||||
@@ -171,25 +183,31 @@ def connect(self, calibrate: bool = True) -> None:
|
|||||||
|
|
||||||
self.configure()
|
self.configure()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### `disconnect()`
|
### `disconnect()`
|
||||||
|
|
||||||
This method should gracefully terminate communication with the hardware: free any related resources (threads or processes), close ports, etc.
|
This method should gracefully terminate communication with the hardware: free any related resources (threads or processes), close ports, etc.
|
||||||
|
|
||||||
Here, we already handle this in our `MotorsBus` and `Camera` classes so we just need to call their own `disconnect()` methods:
|
Here, we already handle this in our `MotorsBus` and `Camera` classes so we just need to call their own `disconnect()` methods:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def disconnect(self) -> None:
|
def disconnect(self) -> None:
|
||||||
self.bus.disconnect()
|
self.bus.disconnect()
|
||||||
for cam in self.cameras.values():
|
for cam in self.cameras.values():
|
||||||
cam.disconnect()
|
cam.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Step 4: Support Calibration and Configuration
|
## Step 4: Support Calibration and Configuration
|
||||||
|
|
||||||
LeRobot supports saving and loading calibration data automatically. This is useful for joint offsets, zero positions, or sensor alignment.
|
LeRobot supports saving and loading calibration data automatically. This is useful for joint offsets, zero positions, or sensor alignment.
|
||||||
|
|
||||||
> Note that depending on your hardware, this may not apply. If that's the case, you can simply leave these methods as no-ops:
|
> Note that depending on your hardware, this may not apply. If that's the case, you can simply leave these methods as no-ops:
|
||||||
> ```python
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
```python
|
||||||
> @property
|
> @property
|
||||||
> def is_calibrated(self) -> bool:
|
> def is_calibrated(self) -> bool:
|
||||||
> return True
|
> return True
|
||||||
@@ -202,7 +220,8 @@ LeRobot supports saving and loading calibration data automatically. This is usef
|
|||||||
|
|
||||||
This should reflect whether your robot has the required calibration loaded.
|
This should reflect whether your robot has the required calibration loaded.
|
||||||
|
|
||||||
```python
|
```
|
||||||
|
<!-- prettier-ignore-end -->python
|
||||||
@property
|
@property
|
||||||
def is_calibrated(self) -> bool:
|
def is_calibrated(self) -> bool:
|
||||||
return self.bus.is_calibrated
|
return self.bus.is_calibrated
|
||||||
@@ -216,6 +235,8 @@ The goal of the calibration is twofold:
|
|||||||
|
|
||||||
It should implement the logic for calibration (if relevant) and update the `self.calibration` dictionary. If you are using Feetech or Dynamixel motors, our bus interfaces already include methods to help with this.
|
It should implement the logic for calibration (if relevant) and update the `self.calibration` dictionary. If you are using Feetech or Dynamixel motors, our bus interfaces already include methods to help with this.
|
||||||
|
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def calibrate(self) -> None:
|
def calibrate(self) -> None:
|
||||||
self.bus.disable_torque()
|
self.bus.disable_torque()
|
||||||
@@ -245,11 +266,13 @@ def calibrate(self) -> None:
|
|||||||
self._save_calibration()
|
self._save_calibration()
|
||||||
print("Calibration saved to", self.calibration_fpath)
|
print("Calibration saved to", self.calibration_fpath)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### `configure()`
|
### `configure()`
|
||||||
|
|
||||||
Use this to set up any configuration for your hardware (servos control modes, controller gains, etc.). This should usually be run at connection time and be idempotent.
|
Use this to set up any configuration for your hardware (servos control modes, controller gains, etc.). This should usually be run at connection time and be idempotent.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def configure(self) -> None:
|
def configure(self) -> None:
|
||||||
with self.bus.torque_disabled():
|
with self.bus.torque_disabled():
|
||||||
@@ -260,6 +283,7 @@ def configure(self) -> None:
|
|||||||
self.bus.write("I_Coefficient", motor, 0)
|
self.bus.write("I_Coefficient", motor, 0)
|
||||||
self.bus.write("D_Coefficient", motor, 32)
|
self.bus.write("D_Coefficient", motor, 32)
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Step 5: Implement Sensors Reading and Action Sending
|
## Step 5: Implement Sensors Reading and Action Sending
|
||||||
|
|
||||||
@@ -269,6 +293,7 @@ These are the most important runtime functions: the core I/O loop.
|
|||||||
|
|
||||||
Returns a dictionary of sensor values from the robot. These typically include motor states, camera frames, various sensors, etc. In the LeRobot framework, these observations are what will be fed to a policy in order to predict the actions to take. The dictionary keys and structure must match `observation_features`.
|
Returns a dictionary of sensor values from the robot. These typically include motor states, camera frames, various sensors, etc. In the LeRobot framework, these observations are what will be fed to a policy in order to predict the actions to take. The dictionary keys and structure must match `observation_features`.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def get_observation(self) -> dict[str, Any]:
|
def get_observation(self) -> dict[str, Any]:
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
@@ -284,6 +309,7 @@ def get_observation(self) -> dict[str, Any]:
|
|||||||
|
|
||||||
return obs_dict
|
return obs_dict
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
### `send_action()`
|
### `send_action()`
|
||||||
|
|
||||||
@@ -291,6 +317,7 @@ Takes a dictionary that matches `action_features`, and sends it to your hardware
|
|||||||
|
|
||||||
For simplicity, we won't be adding any modification of the actions in our example here.
|
For simplicity, we won't be adding any modification of the actions in our example here.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
|
def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
|
||||||
goal_pos = {key.removesuffix(".pos"): val for key, val in action.items()}
|
goal_pos = {key.removesuffix(".pos"): val for key, val in action.items()}
|
||||||
@@ -300,6 +327,7 @@ def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
|
|||||||
|
|
||||||
return action
|
return action
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
## Adding a Teleoperator
|
## Adding a Teleoperator
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ This repository contains example notebooks for using LeRobot. These notebooks de
|
|||||||
|
|
||||||
We provide a ready-to-run Google Colab notebook to help you train ACT policies using datasets from the Hugging Face Hub, with optional logging to Weights & Biases.
|
We provide a ready-to-run Google Colab notebook to help you train ACT policies using datasets from the Hugging Face Hub, with optional logging to Weights & Biases.
|
||||||
|
|
||||||
| Notebook | Colab |
|
| Notebook | Colab |
|
||||||
|:---------|:------|
|
| :------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [Train ACT with LeRobot](https://github.com/huggingface/notebooks/blob/main/lerobot/training-act.ipynb) | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/lerobot/training-act.ipynb) |
|
| [Train ACT with LeRobot](https://github.com/huggingface/notebooks/blob/main/lerobot/training-act.ipynb) | [](https://colab.research.google.com/github/huggingface/notebooks/blob/main/lerobot/training-act.ipynb) |
|
||||||
|
|
||||||
Expected training time for 100k steps: ~1.5 hours on an NVIDIA A100 GPU with batch size of `64`.
|
Expected training time for 100k steps: ~1.5 hours on an NVIDIA A100 GPU with batch size of `64`.
|
||||||
|
|||||||
@@ -3,9 +3,18 @@
|
|||||||
SmolVLA is Hugging Face’s lightweight foundation model for robotics. Designed for easy fine-tuning on LeRobot datasets, it helps accelerate your development!
|
SmolVLA is Hugging Face’s lightweight foundation model for robotics. Designed for easy fine-tuning on LeRobot datasets, it helps accelerate your development!
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://cdn-uploads.huggingface.co/production/uploads/640e21ef3c82bd463ee5a76d/aooU0a3DMtYmy_1IWMaIM.png" alt="SmolVLA architecture." width="500"/>
|
<img
|
||||||
<br/>
|
src="https://cdn-uploads.huggingface.co/production/uploads/640e21ef3c82bd463ee5a76d/aooU0a3DMtYmy_1IWMaIM.png"
|
||||||
<em>Figure 1. SmolVLA takes as input (i) multiple cameras views, (ii) the robot’s current sensorimotor state, and (iii) a natural language instruction, encoded into contextual features used to condition the action expert when generating an action chunk.</em>
|
alt="SmolVLA architecture."
|
||||||
|
width="500"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<em>
|
||||||
|
Figure 1. SmolVLA takes as input (i) multiple cameras views, (ii) the
|
||||||
|
robot’s current sensorimotor state, and (iii) a natural language
|
||||||
|
instruction, encoded into contextual features used to condition the action
|
||||||
|
expert when generating an action chunk.
|
||||||
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Set Up Your Environment
|
## Set Up Your Environment
|
||||||
@@ -32,6 +41,7 @@ We recommend checking out the dataset linked below for reference that was used i
|
|||||||
|
|
||||||
In this dataset, we recorded 50 episodes across 5 distinct cube positions. For each position, we collected 10 episodes of pick-and-place interactions. This structure, repeating each variation several times, helped the model generalize better. We tried similar dataset with 25 episodes, and it was not enough leading to a bad performance. So, the data quality and quantity is definitely a key.
|
In this dataset, we recorded 50 episodes across 5 distinct cube positions. For each position, we collected 10 episodes of pick-and-place interactions. This structure, repeating each variation several times, helped the model generalize better. We tried similar dataset with 25 episodes, and it was not enough leading to a bad performance. So, the data quality and quantity is definitely a key.
|
||||||
After you have your dataset available on the Hub, you are good to go to use our finetuning script to adapt SmolVLA to your application.
|
After you have your dataset available on the Hub, you are good to go to use our finetuning script to adapt SmolVLA to your application.
|
||||||
|
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
## Finetune SmolVLA on your data
|
## Finetune SmolVLA on your data
|
||||||
@@ -56,7 +66,8 @@ cd lerobot && python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
You can start with a small batch size and increase it incrementally, if the GPU allows it, as long as loading times remain short.
|
You can start with a small batch size and increase it incrementally, if the
|
||||||
|
GPU allows it, as long as loading times remain short.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
Fine-tuning is an art. For a complete overview of the options for finetuning, run
|
Fine-tuning is an art. For a complete overview of the options for finetuning, run
|
||||||
@@ -66,12 +77,20 @@ python -m lerobot.scripts.train --help
|
|||||||
```
|
```
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://cdn-uploads.huggingface.co/production/uploads/640e21ef3c82bd463ee5a76d/S-3vvVCulChREwHDkquoc.gif" alt="Comparison of SmolVLA across task variations." width="500"/>
|
<img
|
||||||
<br/>
|
src="https://cdn-uploads.huggingface.co/production/uploads/640e21ef3c82bd463ee5a76d/S-3vvVCulChREwHDkquoc.gif"
|
||||||
<em>Figure 2: Comparison of SmolVLA across task variations. From left to right: (1) pick-place cube counting, (2) pick-place cube counting, (3) pick-place cube counting under perturbations, and (4) generalization on pick-and-place of the lego block with real-world SO101.</em>
|
alt="Comparison of SmolVLA across task variations."
|
||||||
|
width="500"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<em>
|
||||||
|
Figure 2: Comparison of SmolVLA across task variations. From left to right:
|
||||||
|
(1) pick-place cube counting, (2) pick-place cube counting, (3) pick-place
|
||||||
|
cube counting under perturbations, and (4) generalization on pick-and-place
|
||||||
|
of the lego block with real-world SO101.
|
||||||
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
## Evaluate the finetuned model and run it in real-time
|
## Evaluate the finetuned model and run it in real-time
|
||||||
|
|
||||||
Similarly for when recording an episode, it is recommended that you are logged in to the HuggingFace Hub. You can follow the corresponding steps: [Record a dataset](./getting_started_real_world_robot#record-a-dataset).
|
Similarly for when recording an episode, it is recommended that you are logged in to the HuggingFace Hub. You can follow the corresponding steps: [Record a dataset](./getting_started_real_world_robot#record-a-dataset).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
This tutorial will explain the training script, how to use it, and particularly how to configure everything needed for the training run.
|
This tutorial will explain the training script, how to use it, and particularly how to configure everything needed for the training run.
|
||||||
> **Note:** The following assumes you're running these commands on a machine equipped with a cuda GPU. If you don't have one (or if you're using a Mac), you can add `--policy.device=cpu` (`--policy.device=mps` respectively). However, be advised that the code executes much slower on cpu.
|
|
||||||
|
|
||||||
|
> **Note:** The following assumes you're running these commands on a machine equipped with a cuda GPU. If you don't have one (or if you're using a Mac), you can add `--policy.device=cpu` (`--policy.device=mps` respectively). However, be advised that the code executes much slower on cpu.
|
||||||
|
|
||||||
## The training script
|
## The training script
|
||||||
|
|
||||||
@@ -15,17 +15,22 @@ LeRobot offers a training script at [`lerobot/scripts/train.py`](../src/lerobot/
|
|||||||
## Overview of the configuration system
|
## Overview of the configuration system
|
||||||
|
|
||||||
In the training script, the main function `train` expects a `TrainPipelineConfig` object:
|
In the training script, the main function `train` expects a `TrainPipelineConfig` object:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
# train.py
|
# train.py
|
||||||
@parser.wrap()
|
@parser.wrap()
|
||||||
def train(cfg: TrainPipelineConfig):
|
def train(cfg: TrainPipelineConfig):
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
You can inspect the `TrainPipelineConfig` defined in [`lerobot/configs/train.py`](../src/lerobot/configs/train.py) (which is heavily commented and meant to be a reference to understand any option)
|
You can inspect the `TrainPipelineConfig` defined in [`lerobot/configs/train.py`](../src/lerobot/configs/train.py) (which is heavily commented and meant to be a reference to understand any option)
|
||||||
|
|
||||||
When running the script, inputs for the command line are parsed thanks to the `@parser.wrap()` decorator and an instance of this class is automatically generated. Under the hood, this is done with [Draccus](https://github.com/dlwh/draccus) which is a tool dedicated to this purpose. If you're familiar with Hydra, Draccus can similarly load configurations from config files (.json, .yaml) and also override their values through command line inputs. Unlike Hydra, these configurations are pre-defined in the code through dataclasses rather than being defined entirely in config files. This allows for more rigorous serialization/deserialization, typing, and to manipulate configuration as objects directly in the code and not as dictionaries or namespaces (which enables nice features in an IDE such as autocomplete, jump-to-def, etc.)
|
When running the script, inputs for the command line are parsed thanks to the `@parser.wrap()` decorator and an instance of this class is automatically generated. Under the hood, this is done with [Draccus](https://github.com/dlwh/draccus) which is a tool dedicated to this purpose. If you're familiar with Hydra, Draccus can similarly load configurations from config files (.json, .yaml) and also override their values through command line inputs. Unlike Hydra, these configurations are pre-defined in the code through dataclasses rather than being defined entirely in config files. This allows for more rigorous serialization/deserialization, typing, and to manipulate configuration as objects directly in the code and not as dictionaries or namespaces (which enables nice features in an IDE such as autocomplete, jump-to-def, etc.)
|
||||||
|
|
||||||
Let's have a look at a simplified example. Amongst other attributes, the training config has the following attributes:
|
Let's have a look at a simplified example. Amongst other attributes, the training config has the following attributes:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class TrainPipelineConfig:
|
class TrainPipelineConfig:
|
||||||
@@ -33,7 +38,11 @@ class TrainPipelineConfig:
|
|||||||
env: envs.EnvConfig | None = None
|
env: envs.EnvConfig | None = None
|
||||||
policy: PreTrainedConfig | None = None
|
policy: PreTrainedConfig | None = None
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
in which `DatasetConfig` for example is defined as such:
|
in which `DatasetConfig` for example is defined as such:
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class DatasetConfig:
|
class DatasetConfig:
|
||||||
@@ -41,16 +50,17 @@ class DatasetConfig:
|
|||||||
episodes: list[int] | None = None
|
episodes: list[int] | None = None
|
||||||
video_backend: str = "pyav"
|
video_backend: str = "pyav"
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
This creates a hierarchical relationship where, for example assuming we have a `cfg` instance of `TrainPipelineConfig`, we can access the `repo_id` value with `cfg.dataset.repo_id`.
|
This creates a hierarchical relationship where, for example assuming we have a `cfg` instance of `TrainPipelineConfig`, we can access the `repo_id` value with `cfg.dataset.repo_id`.
|
||||||
From the command line, we can specify this value by using a very similar syntax `--dataset.repo_id=repo/id`.
|
From the command line, we can specify this value by using a very similar syntax `--dataset.repo_id=repo/id`.
|
||||||
|
|
||||||
By default, every field takes its default value specified in the dataclass. If a field doesn't have a default value, it needs to be specified either from the command line or from a config file – which path is also given in the command line (more in this below). In the example above, the `dataset` field doesn't have a default value which means it must be specified.
|
By default, every field takes its default value specified in the dataclass. If a field doesn't have a default value, it needs to be specified either from the command line or from a config file – which path is also given in the command line (more in this below). In the example above, the `dataset` field doesn't have a default value which means it must be specified.
|
||||||
|
|
||||||
|
|
||||||
## Specifying values from the CLI
|
## Specifying values from the CLI
|
||||||
|
|
||||||
Let's say that we want to train [Diffusion Policy](../src/lerobot/policies/diffusion) on the [pusht](https://huggingface.co/datasets/lerobot/pusht) dataset, using the [gym_pusht](https://github.com/huggingface/gym-pusht) environment for evaluation. The command to do so would look like this:
|
Let's say that we want to train [Diffusion Policy](../src/lerobot/policies/diffusion) on the [pusht](https://huggingface.co/datasets/lerobot/pusht) dataset, using the [gym_pusht](https://github.com/huggingface/gym-pusht) environment for evaluation. The command to do so would look like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--dataset.repo_id=lerobot/pusht \
|
--dataset.repo_id=lerobot/pusht \
|
||||||
@@ -59,11 +69,13 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Let's break this down:
|
Let's break this down:
|
||||||
|
|
||||||
- To specify the dataset, we just need to specify its `repo_id` on the hub which is the only required argument in the `DatasetConfig`. The rest of the fields have default values and in this case we are fine with those so we can just add the option `--dataset.repo_id=lerobot/pusht`.
|
- To specify the dataset, we just need to specify its `repo_id` on the hub which is the only required argument in the `DatasetConfig`. The rest of the fields have default values and in this case we are fine with those so we can just add the option `--dataset.repo_id=lerobot/pusht`.
|
||||||
- To specify the policy, we can just select diffusion policy using `--policy` appended with `.type`. Here, `.type` is a special argument which allows us to select config classes inheriting from `draccus.ChoiceRegistry` and that have been decorated with the `register_subclass()` method. To have a better explanation of this feature, have a look at this [Draccus demo](https://github.com/dlwh/draccus?tab=readme-ov-file#more-flexible-configuration-with-choice-types). In our code, we use this mechanism mainly to select policies, environments, robots, and some other components like optimizers. The policies available to select are located in [lerobot/policies](../src/lerobot/policies)
|
- To specify the policy, we can just select diffusion policy using `--policy` appended with `.type`. Here, `.type` is a special argument which allows us to select config classes inheriting from `draccus.ChoiceRegistry` and that have been decorated with the `register_subclass()` method. To have a better explanation of this feature, have a look at this [Draccus demo](https://github.com/dlwh/draccus?tab=readme-ov-file#more-flexible-configuration-with-choice-types). In our code, we use this mechanism mainly to select policies, environments, robots, and some other components like optimizers. The policies available to select are located in [lerobot/policies](../src/lerobot/policies)
|
||||||
- Similarly, we select the environment with `--env.type=pusht`. The different environment configs are available in [`lerobot/envs/configs.py`](../src/lerobot/envs/configs.py)
|
- Similarly, we select the environment with `--env.type=pusht`. The different environment configs are available in [`lerobot/envs/configs.py`](../src/lerobot/envs/configs.py)
|
||||||
|
|
||||||
Let's see another example. Let's say you've been training [ACT](../src/lerobot/policies/act) on [lerobot/aloha_sim_insertion_human](https://huggingface.co/datasets/lerobot/aloha_sim_insertion_human) using the [gym-aloha](https://github.com/huggingface/gym-aloha) environment for evaluation with:
|
Let's see another example. Let's say you've been training [ACT](../src/lerobot/policies/act) on [lerobot/aloha_sim_insertion_human](https://huggingface.co/datasets/lerobot/aloha_sim_insertion_human) using the [gym-aloha](https://github.com/huggingface/gym-aloha) environment for evaluation with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.type=act \
|
--policy.type=act \
|
||||||
@@ -71,10 +83,12 @@ python -m lerobot.scripts.train \
|
|||||||
--env.type=aloha \
|
--env.type=aloha \
|
||||||
--output_dir=outputs/train/act_aloha_insertion
|
--output_dir=outputs/train/act_aloha_insertion
|
||||||
```
|
```
|
||||||
|
|
||||||
> Notice we added `--output_dir` to explicitly tell where to write outputs from this run (checkpoints, training state, configs etc.). This is not mandatory and if you don't specify it, a default directory will be created from the current date and time, env.type and policy.type. This will typically look like `outputs/train/2025-01-24/16-10-05_aloha_act`.
|
> Notice we added `--output_dir` to explicitly tell where to write outputs from this run (checkpoints, training state, configs etc.). This is not mandatory and if you don't specify it, a default directory will be created from the current date and time, env.type and policy.type. This will typically look like `outputs/train/2025-01-24/16-10-05_aloha_act`.
|
||||||
|
|
||||||
We now want to train a different policy for aloha on another task. We'll change the dataset and use [lerobot/aloha_sim_transfer_cube_human](https://huggingface.co/datasets/lerobot/aloha_sim_transfer_cube_human) instead. Of course, we also need to change the task of the environment as well to match this other task.
|
We now want to train a different policy for aloha on another task. We'll change the dataset and use [lerobot/aloha_sim_transfer_cube_human](https://huggingface.co/datasets/lerobot/aloha_sim_transfer_cube_human) instead. Of course, we also need to change the task of the environment as well to match this other task.
|
||||||
Looking at the [`AlohaEnv`](../src/lerobot/envs/configs.py) config, the task is `"AlohaInsertion-v0"` by default, which corresponds to the task we trained on in the command above. The [gym-aloha](https://github.com/huggingface/gym-aloha?tab=readme-ov-file#description) environment also has the `AlohaTransferCube-v0` task which corresponds to this other task we want to train on. Putting this together, we can train this new policy on this different task using:
|
Looking at the [`AlohaEnv`](../src/lerobot/envs/configs.py) config, the task is `"AlohaInsertion-v0"` by default, which corresponds to the task we trained on in the command above. The [gym-aloha](https://github.com/huggingface/gym-aloha?tab=readme-ov-file#description) environment also has the `AlohaTransferCube-v0` task which corresponds to this other task we want to train on. Putting this together, we can train this new policy on this different task using:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.type=act \
|
--policy.type=act \
|
||||||
@@ -87,6 +101,7 @@ python -m lerobot.scripts.train \
|
|||||||
## Loading from a config file
|
## Loading from a config file
|
||||||
|
|
||||||
Now, let's assume that we want to reproduce the run just above. That run has produced a `train_config.json` file in its checkpoints, which serializes the `TrainPipelineConfig` instance it used:
|
Now, let's assume that we want to reproduce the run just above. That run has produced a `train_config.json` file in its checkpoints, which serializes the `TrainPipelineConfig` instance it used:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dataset": {
|
"dataset": {
|
||||||
@@ -110,34 +125,40 @@ Now, let's assume that we want to reproduce the run just above. That run has pro
|
|||||||
```
|
```
|
||||||
|
|
||||||
We can then simply load the config values from this file using:
|
We can then simply load the config values from this file using:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=outputs/train/act_aloha_transfer/checkpoints/last/pretrained_model/ \
|
--config_path=outputs/train/act_aloha_transfer/checkpoints/last/pretrained_model/ \
|
||||||
--output_dir=outputs/train/act_aloha_transfer_2
|
--output_dir=outputs/train/act_aloha_transfer_2
|
||||||
```
|
```
|
||||||
|
|
||||||
`--config_path` is also a special argument which allows to initialize the config from a local config file. It can point to a directory that contains `train_config.json` or to the config file itself directly.
|
`--config_path` is also a special argument which allows to initialize the config from a local config file. It can point to a directory that contains `train_config.json` or to the config file itself directly.
|
||||||
|
|
||||||
Similarly to Hydra, we can still override some parameters in the CLI if we want to, e.g.:
|
Similarly to Hydra, we can still override some parameters in the CLI if we want to, e.g.:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=outputs/train/act_aloha_transfer/checkpoints/last/pretrained_model/ \
|
--config_path=outputs/train/act_aloha_transfer/checkpoints/last/pretrained_model/ \
|
||||||
--output_dir=outputs/train/act_aloha_transfer_2
|
--output_dir=outputs/train/act_aloha_transfer_2
|
||||||
--policy.n_action_steps=80
|
--policy.n_action_steps=80
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note: While `--output_dir` is not required in general, in this case we need to specify it since it will otherwise take the value from the `train_config.json` (which is `outputs/train/act_aloha_transfer`). In order to prevent accidental deletion of previous run checkpoints, we raise an error if you're trying to write in an existing directory. This is not the case when resuming a run, which is what you'll learn next.
|
> Note: While `--output_dir` is not required in general, in this case we need to specify it since it will otherwise take the value from the `train_config.json` (which is `outputs/train/act_aloha_transfer`). In order to prevent accidental deletion of previous run checkpoints, we raise an error if you're trying to write in an existing directory. This is not the case when resuming a run, which is what you'll learn next.
|
||||||
|
|
||||||
`--config_path` can also accept the repo_id of a repo on the hub that contains a `train_config.json` file, e.g. running:
|
`--config_path` can also accept the repo_id of a repo on the hub that contains a `train_config.json` file, e.g. running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train --config_path=lerobot/diffusion_pusht
|
python -m lerobot.scripts.train --config_path=lerobot/diffusion_pusht
|
||||||
```
|
```
|
||||||
will start a training run with the same configuration used for training [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)
|
|
||||||
|
|
||||||
|
will start a training run with the same configuration used for training [lerobot/diffusion_pusht](https://huggingface.co/lerobot/diffusion_pusht)
|
||||||
|
|
||||||
## Resume training
|
## Resume training
|
||||||
|
|
||||||
Being able to resume a training run is important in case it crashed or aborted for any reason. We'll demonstrate how to do that here.
|
Being able to resume a training run is important in case it crashed or aborted for any reason. We'll demonstrate how to do that here.
|
||||||
|
|
||||||
Let's reuse the command from the previous run and add a few more options:
|
Let's reuse the command from the previous run and add a few more options:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.type=act \
|
--policy.type=act \
|
||||||
@@ -150,19 +171,24 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Here we've taken care to set up the log frequency and checkpointing frequency to low numbers so we can showcase resumption. You should be able to see some logging and have a first checkpoint within 1 minute (depending on hardware). Wait for the first checkpoint to happen, you should see a line that looks like this in your terminal:
|
Here we've taken care to set up the log frequency and checkpointing frequency to low numbers so we can showcase resumption. You should be able to see some logging and have a first checkpoint within 1 minute (depending on hardware). Wait for the first checkpoint to happen, you should see a line that looks like this in your terminal:
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO 2025-01-24 16:10:56 ts/train.py:263 Checkpoint policy after step 100
|
INFO 2025-01-24 16:10:56 ts/train.py:263 Checkpoint policy after step 100
|
||||||
```
|
```
|
||||||
|
|
||||||
Now let's simulate a crash by killing the process (hit `ctrl`+`c`). We can then simply resume this run from the last checkpoint available with:
|
Now let's simulate a crash by killing the process (hit `ctrl`+`c`). We can then simply resume this run from the last checkpoint available with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=outputs/train/run_resumption/checkpoints/last/pretrained_model/ \
|
--config_path=outputs/train/run_resumption/checkpoints/last/pretrained_model/ \
|
||||||
--resume=true
|
--resume=true
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see from the logging that your training picks up from where it left off.
|
You should see from the logging that your training picks up from where it left off.
|
||||||
|
|
||||||
Another reason for which you might want to resume a run is simply to extend training and add more training steps. The number of training steps is set by the option `--steps`, which is 100 000 by default.
|
Another reason for which you might want to resume a run is simply to extend training and add more training steps. The number of training steps is set by the option `--steps`, which is 100 000 by default.
|
||||||
You could double the number of steps of the previous run with:
|
You could double the number of steps of the previous run with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=outputs/train/run_resumption/checkpoints/last/pretrained_model/ \
|
--config_path=outputs/train/run_resumption/checkpoints/last/pretrained_model/ \
|
||||||
@@ -171,7 +197,9 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Outputs of a run
|
## Outputs of a run
|
||||||
|
|
||||||
In the output directory, there will be a folder called `checkpoints` with the following structure:
|
In the output directory, there will be a folder called `checkpoints` with the following structure:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
outputs/train/run_resumption/checkpoints
|
outputs/train/run_resumption/checkpoints
|
||||||
├── 000100 # checkpoint_dir for training step 100
|
├── 000100 # checkpoint_dir for training step 100
|
||||||
@@ -194,6 +222,7 @@ outputs/train/run_resumption/checkpoints
|
|||||||
In addition to the features currently in Draccus, we've added a special `.path` argument for the policy, which allows to load a policy as you would with `PreTrainedPolicy.from_pretrained()`. In that case, `path` can be a local directory that contains a checkpoint or a repo_id pointing to a pretrained policy on the hub.
|
In addition to the features currently in Draccus, we've added a special `.path` argument for the policy, which allows to load a policy as you would with `PreTrainedPolicy.from_pretrained()`. In that case, `path` can be a local directory that contains a checkpoint or a repo_id pointing to a pretrained policy on the hub.
|
||||||
|
|
||||||
For example, we could fine-tune a [policy pre-trained on the aloha transfer task](https://huggingface.co/lerobot/act_aloha_sim_transfer_cube_human) on the aloha insertion task. We can achieve this with:
|
For example, we could fine-tune a [policy pre-trained on the aloha transfer task](https://huggingface.co/lerobot/act_aloha_sim_transfer_cube_human) on the aloha insertion task. We can achieve this with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.path=lerobot/act_aloha_sim_transfer_cube_human \
|
--policy.path=lerobot/act_aloha_sim_transfer_cube_human \
|
||||||
@@ -209,15 +238,19 @@ When doing so, keep in mind that the features of the fine-tuning dataset would h
|
|||||||
When you start the training process, you will first see your full configuration being printed in the terminal. You can check it to make sure that you configured your run correctly. The final configuration will also be saved with the checkpoint.
|
When you start the training process, you will first see your full configuration being printed in the terminal. You can check it to make sure that you configured your run correctly. The final configuration will also be saved with the checkpoint.
|
||||||
|
|
||||||
After that, you will see training log like this one:
|
After that, you will see training log like this one:
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO 2024-08-14 13:35:12 ts/train.py:192 step:0 smpl:64 ep:1 epch:0.00 loss:1.112 grdn:15.387 lr:2.0e-07 updt_s:1.738 data_s:4.774
|
INFO 2024-08-14 13:35:12 ts/train.py:192 step:0 smpl:64 ep:1 epch:0.00 loss:1.112 grdn:15.387 lr:2.0e-07 updt_s:1.738 data_s:4.774
|
||||||
```
|
```
|
||||||
|
|
||||||
or evaluation log:
|
or evaluation log:
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO 2024-08-14 13:38:45 ts/train.py:226 step:100 smpl:6K ep:52 epch:0.25 ∑rwrd:20.693 success:0.0% eval_s:120.266
|
INFO 2024-08-14 13:38:45 ts/train.py:226 step:100 smpl:6K ep:52 epch:0.25 ∑rwrd:20.693 success:0.0% eval_s:120.266
|
||||||
```
|
```
|
||||||
|
|
||||||
These logs will also be saved in wandb if `wandb.enable` is set to `true`. Here are the meaning of some abbreviations:
|
These logs will also be saved in wandb if `wandb.enable` is set to `true`. Here are the meaning of some abbreviations:
|
||||||
|
|
||||||
- `smpl`: number of samples seen during training.
|
- `smpl`: number of samples seen during training.
|
||||||
- `ep`: number of episodes seen during training. An episode contains multiple samples in a complete manipulation task.
|
- `ep`: number of episodes seen during training. An episode contains multiple samples in a complete manipulation task.
|
||||||
- `epch`: number of time all unique samples are seen (epoch).
|
- `epch`: number of time all unique samples are seen (epoch).
|
||||||
@@ -235,6 +268,7 @@ Some metrics are useful for initial performance profiling. For example, if you f
|
|||||||
We'll summarize here the main use cases to remember from this tutorial.
|
We'll summarize here the main use cases to remember from this tutorial.
|
||||||
|
|
||||||
#### Train a policy from scratch – CLI
|
#### Train a policy from scratch – CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.type=act \ # <- select 'act' policy
|
--policy.type=act \ # <- select 'act' policy
|
||||||
@@ -243,6 +277,7 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Train a policy from scratch - config file + CLI
|
#### Train a policy from scratch - config file + CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=path/to/pretrained_model \ # <- can also be a repo_id
|
--config_path=path/to/pretrained_model \ # <- can also be a repo_id
|
||||||
@@ -250,6 +285,7 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Resume/continue a training run
|
#### Resume/continue a training run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--config_path=checkpoint/pretrained_model/ \
|
--config_path=checkpoint/pretrained_model/ \
|
||||||
@@ -258,6 +294,7 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Fine-tuning
|
#### Fine-tuning
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--policy.path=lerobot/act_aloha_sim_transfer_cube_human \ # <- can also be a local path to a checkpoint
|
--policy.path=lerobot/act_aloha_sim_transfer_cube_human \ # <- can also be a local path to a checkpoint
|
||||||
|
|||||||
202
pyproject.toml
202
pyproject.toml
@@ -12,8 +12,14 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
homepage = "https://github.com/huggingface/lerobot"
|
homepage = "https://huggingface.co/lerobot"
|
||||||
|
documentation = "https://huggingface.co/docs/lerobot/index"
|
||||||
|
source = "https://github.com/huggingface/lerobot"
|
||||||
issues = "https://github.com/huggingface/lerobot/issues"
|
issues = "https://github.com/huggingface/lerobot/issues"
|
||||||
discord = "https://discord.gg/s3KuuzsPFb"
|
discord = "https://discord.gg/s3KuuzsPFb"
|
||||||
|
|
||||||
@@ -21,109 +27,165 @@ discord = "https://discord.gg/s3KuuzsPFb"
|
|||||||
name = "lerobot"
|
name = "lerobot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch"
|
description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { text = "Apache-2.0" }
|
||||||
|
requires-python = ">=3.10"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Rémi Cadène", email = "re.cadene@gmail.com" },
|
{ name = "Rémi Cadène", email = "re.cadene@gmail.com" },
|
||||||
{ name = "Simon Alibert", email = "alibert.sim@gmail.com" },
|
{ name = "Simon Alibert", email = "alibert.sim@gmail.com" },
|
||||||
{ name = "Alexander Soare", email = "alexander.soare159@gmail.com" },
|
{ name = "Alexander Soare", email = "alexander.soare159@gmail.com" },
|
||||||
{ name = "Quentin Gallouédec", email = "quentin.gallouedec@ec-lyon.fr" },
|
{ name = "Quentin Gallouédec", email = "quentin.gallouedec@ec-lyon.fr" },
|
||||||
{ name = "Adil Zouitine", email = "adilzouitinegm@gmail.com" },
|
|
||||||
{ name = "Thomas Wolf", email = "thomaswolfcontact@gmail.com" },
|
|
||||||
{ name = "Steven Palma", email = "imstevenpmwork@ieee.org" },
|
{ name = "Steven Palma", email = "imstevenpmwork@ieee.org" },
|
||||||
|
{ name = "Pepijn Kooijmans", email = "pepijnkooijmans@outlook.com"},
|
||||||
|
{ name = "Michel Aractingi", email = "michel.aractingi@gmail.com"},
|
||||||
|
{ name = "Adil Zouitine", email = "adilzouitinegm@gmail.com" },
|
||||||
|
{ name = "Dana Aubakirova", email = "danaaubakirova17@gmail.com"},
|
||||||
|
{ name = "Caroline Pascal", email = "caroline8.pascal@gmail.com"},
|
||||||
|
{ name = "Martino Russi", email = "nopyeps@gmail.com"},
|
||||||
|
{ name = "Thomas Wolf", email = "thomaswolfcontact@gmail.com" },
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
|
||||||
license = { text = "Apache-2.0" }
|
|
||||||
requires-python = ">=3.10"
|
|
||||||
keywords = ["robotics", "deep learning", "pytorch"]
|
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: Education",
|
"Intended Audience :: Education",
|
||||||
"Intended Audience :: Science/Research",
|
"Intended Audience :: Science/Research",
|
||||||
"Topic :: Software Development :: Build Tools",
|
|
||||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
||||||
"License :: OSI Approved :: Apache Software License",
|
"License :: OSI Approved :: Apache Software License",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Topic :: Software Development :: Build Tools",
|
||||||
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||||
]
|
]
|
||||||
|
keywords = ["lerobot", "huggingface", "robotics", "machine learning", "artificial intelligence"]
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cmake>=3.29.0.1",
|
|
||||||
"datasets>=2.19.0,<=3.6.0",
|
# Hugging Face dependencies
|
||||||
"deepdiff>=7.0.1",
|
"datasets>=2.19.0,<=3.6.0", # TODO: Bumb dependency
|
||||||
"diffusers>=0.27.2",
|
"diffusers>=0.27.2",
|
||||||
"draccus==0.10.0",
|
"huggingface-hub[hf-transfer,cli]>=0.27.1",
|
||||||
|
|
||||||
|
# Core dependencies
|
||||||
|
"cmake>=3.29.0.1",
|
||||||
"einops>=0.8.0",
|
"einops>=0.8.0",
|
||||||
"flask>=3.0.3",
|
|
||||||
"gdown>=5.1.0",
|
|
||||||
"gymnasium==0.29.1", # TODO(rcadene, aliberts): Make gym 1.0.0 work
|
|
||||||
"h5py>=3.10.0",
|
|
||||||
"huggingface-hub[hf-transfer,cli]>=0.27.1 ; python_version < '4.0'",
|
|
||||||
"imageio[ffmpeg]>=2.34.0",
|
|
||||||
"jsonlines>=4.0.0",
|
|
||||||
"numba>=0.59.0",
|
|
||||||
"omegaconf>=2.3.0",
|
|
||||||
"opencv-python-headless>=4.9.0",
|
"opencv-python-headless>=4.9.0",
|
||||||
"packaging>=24.2",
|
|
||||||
"av>=14.2.0",
|
"av>=14.2.0",
|
||||||
"pymunk>=6.6.0,<7.0.0",
|
|
||||||
"pynput>=1.7.7",
|
|
||||||
"pyserial>=3.5",
|
|
||||||
"pyzmq>=26.2.1",
|
|
||||||
"rerun-sdk>=0.21.0",
|
|
||||||
"termcolor>=2.4.0",
|
|
||||||
"torch>=2.2.1",
|
"torch>=2.2.1",
|
||||||
"torchcodec>=0.2.1; sys_platform != 'win32' and (sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l')) and (sys_platform != 'darwin' or platform_machine != 'x86_64')",
|
"torchcodec>=0.2.1; sys_platform != 'win32' and (sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l')) and (sys_platform != 'darwin' or platform_machine != 'x86_64')",
|
||||||
"torchvision>=0.21.0",
|
"torchvision>=0.21.0",
|
||||||
|
"jsonlines>=4.0.0",
|
||||||
|
"packaging>=24.2",
|
||||||
|
"pynput>=1.7.7",
|
||||||
|
"pyserial>=3.5",
|
||||||
"wandb>=0.16.3",
|
"wandb>=0.16.3",
|
||||||
"zarr>=2.17.0",
|
|
||||||
|
"draccus==0.10.0", # TODO: Remove ==
|
||||||
|
"gymnasium>=0.29.1,<1.0.0", # TODO: Bumb dependency
|
||||||
|
"rerun-sdk>=0.21.0,<0.23.0", # TODO: Bumb dependency
|
||||||
|
|
||||||
|
# Support dependencies
|
||||||
|
"deepdiff>=7.0.1,<9.0.0",
|
||||||
|
"flask>=3.0.3,<4.0.0",
|
||||||
|
"imageio[ffmpeg]>=2.34.0,<3.0.0",
|
||||||
|
"termcolor>=2.4.0,<4.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Optional dependencies
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
aloha = ["gym-aloha>=0.1.1 ; python_version < '4.0'"]
|
|
||||||
docs = ["hf-doc-builder @ git+https://github.com/huggingface/doc-builder.git@main", "watchdog >= 6.0.0"]
|
# Common
|
||||||
dev = ["pre-commit>=3.7.0", "debugpy>=1.8.1", "grpcio-tools==1.71.0"]
|
pygame-dep = ["pygame>=2.5.1"]
|
||||||
dora = [
|
placo-dep = ["placo>=0.9.6"]
|
||||||
"gym-dora @ git+https://github.com/dora-rs/dora-lerobot.git#subdirectory=gym_dora ; python_version < '4.0'",
|
transformers-dep = ["transformers>=4.50.3,<4.52.0"] # TODO: Bumb dependency
|
||||||
]
|
grpcio-dep = ["grpcio==1.71.0"]
|
||||||
dynamixel = ["dynamixel-sdk>=3.7.31"]
|
|
||||||
|
# Motors
|
||||||
feetech = ["feetech-servo-sdk>=1.0.0"]
|
feetech = ["feetech-servo-sdk>=1.0.0"]
|
||||||
gamepad = ["pygame>=2.5.1", "hidapi>=0.14.0"]
|
dynamixel = ["dynamixel-sdk>=3.7.31"]
|
||||||
hopejr = ["feetech-servo-sdk>=1.0.0", "pygame>=2.5.1"]
|
|
||||||
kinematics = ["placo>=0.9.6"]
|
# Robots
|
||||||
|
gamepad = ["lerobot[pygame-dep]", "hidapi>=0.14.0"]
|
||||||
|
hopejr = ["lerobot[feetech]", "lerobot[pygame-dep]"]
|
||||||
|
lekiwi = ["lerobot[feetech]", "pyzmq>=26.2.1"]
|
||||||
|
kinematics = ["lerobot[placo-dep]"]
|
||||||
intelrealsense = [
|
intelrealsense = [
|
||||||
"pyrealsense2>=2.55.1.6486 ; sys_platform != 'darwin'",
|
"pyrealsense2>=2.55.1.6486 ; sys_platform != 'darwin'",
|
||||||
"pyrealsense2-macosx>=2.54 ; sys_platform == 'darwin'",
|
"pyrealsense2-macosx>=2.54 ; sys_platform == 'darwin'",
|
||||||
]
|
]
|
||||||
pi0 = ["transformers>=4.50.3"]
|
|
||||||
smolvla = ["transformers>=4.50.3", "num2words>=0.5.14", "accelerate>=1.7.0", "safetensors>=0.4.3"]
|
|
||||||
pusht = ["gym-pusht>=0.1.5 ; python_version < '4.0'"]
|
|
||||||
stretch = [
|
stretch = [
|
||||||
"hello-robot-stretch-body>=0.7.27 ; python_version < '4.0' and sys_platform == 'linux'",
|
"hello-robot-stretch-body>=0.7.27 ; sys_platform == 'linux'",
|
||||||
"pyrender @ git+https://github.com/mmatl/pyrender.git ; sys_platform == 'linux'",
|
"pyrender @ git+https://github.com/mmatl/pyrender.git ; sys_platform == 'linux'",
|
||||||
"pyrealsense2>=2.55.1.6486 ; sys_platform != 'darwin'"
|
"pyrealsense2>=2.55.1.6486 ; sys_platform != 'darwin'"
|
||||||
]
|
] # TODO: Currently not supported
|
||||||
test = ["pytest>=8.1.0", "pytest-timeout>=2.4.0", "pytest-cov>=5.0.0", "pyserial>=3.5", "mock-serial>=0.0.1 ; sys_platform != 'win32'"]
|
|
||||||
hilserl = ["transformers>=4.50.3", "gym-hil>=0.1.9", "protobuf>=5.29.3", "grpcio==1.71.0", "placo>=0.9.6"]
|
|
||||||
umi = ["imagecodecs>=2024.1.1"]
|
|
||||||
video_benchmark = ["scikit-image>=0.23.2", "pandas>=2.2.2"]
|
|
||||||
xarm = ["gym-xarm>=0.1.1 ; python_version < '4.0'"]
|
|
||||||
async = ["grpcio==1.71.0", "matplotlib>=3.10.3"]
|
|
||||||
|
|
||||||
[tool.poetry]
|
# Policies
|
||||||
requires-poetry = ">=2.1"
|
pi0 = ["lerobot[transformers-dep]"]
|
||||||
packages = [
|
smolvla = ["lerobot[transformers-dep]", "num2words>=0.5.14", "accelerate>=1.7.0", "safetensors>=0.4.3"]
|
||||||
{ include = "lerobot", from = "src" }
|
hilserl = ["lerobot[transformers-dep]", "gym-hil>=0.1.9", "protobuf>=5.29.3", "lerobot[grpcio-dep]", "lerobot[placo-dep]"]
|
||||||
]
|
|
||||||
|
# Features
|
||||||
|
async = ["lerobot[grpcio-dep]", "matplotlib>=3.10.3"]
|
||||||
|
|
||||||
|
# Development
|
||||||
|
docs = ["hf-doc-builder @ git+https://github.com/huggingface/doc-builder.git@main", "watchdog >= 6.0.0"]
|
||||||
|
dev = ["pre-commit>=3.7.0", "debugpy>=1.8.1", "grpcio-tools==1.71.0"]
|
||||||
|
test = ["pytest>=8.1.0", "pytest-timeout>=2.4.0", "pytest-cov>=5.0.0", "mock-serial>=0.0.1 ; sys_platform != 'win32'"]
|
||||||
|
video_benchmark = ["scikit-image>=0.23.2", "pandas>=2.2.2"]
|
||||||
|
|
||||||
|
# Simulation
|
||||||
|
aloha = ["gym-aloha>=0.1.1"]
|
||||||
|
pusht = ["gym-pusht>=0.1.5", "pymunk>=6.6.0,<7.0.0"] # TODO: Fix pymunk version in gym-pusht instead
|
||||||
|
xarm = ["gym-xarm>=0.1.1"]
|
||||||
|
|
||||||
|
# ---------------- Tool Configurations ----------------
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 110
|
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
|
line-length = 110
|
||||||
exclude = ["tests/artifacts/**/*.safetensors", "*_pb2.py", "*_pb2_grpc.py"]
|
exclude = ["tests/artifacts/**/*.safetensors", "*_pb2.py", "*_pb2_grpc.py"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E4", "E7", "E9", "F", "I", "N", "B", "C4", "SIM"]
|
# E, W: pycodestyle errors and warnings
|
||||||
|
# F: PyFlakes
|
||||||
|
# I: isort
|
||||||
|
# UP: pyupgrade
|
||||||
|
# B: flake8-bugbear (good practices, potential bugs)
|
||||||
|
# C4: flake8-comprehensions (more concise comprehensions)
|
||||||
|
# A: flake8-builtins (shadowing builtins)
|
||||||
|
# SIM: flake8-simplify
|
||||||
|
# RUF: Ruff-specific rules
|
||||||
|
# D: pydocstyle (for docstring style/formatting)
|
||||||
|
# S: flake8-bandit (some security checks, complements Bandit)
|
||||||
|
# T20: flake8-print (discourage print statements in production code)
|
||||||
|
# N: pep8-naming
|
||||||
|
# TODO: Uncomment rules when ready to use
|
||||||
|
select = [
|
||||||
|
"E", "W", "F", "I", "B", "C4", "T20", "N" # "SIM", "A", "S", "D", "RUF", "UP"
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"E501", # Line too long
|
||||||
|
"T201", # Print statement found
|
||||||
|
"T203", # Pprint statement found
|
||||||
|
"B008", # Perform function call in argument defaults
|
||||||
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401", "F403"]
|
"__init__.py" = ["F401", "F403"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
combine-as-imports = true
|
||||||
|
known-first-party = ["lerobot"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
convention = "google"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
line-ending = "auto"
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
[tool.bandit]
|
[tool.bandit]
|
||||||
exclude_dirs = [
|
exclude_dirs = [
|
||||||
"tests",
|
"tests",
|
||||||
@@ -148,6 +210,24 @@ default.extend-ignore-identifiers-re = [
|
|||||||
"ein",
|
"ein",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
# TODO: Uncomment when ready to use
|
||||||
requires = ["poetry-core"]
|
# [tool.interrogate]
|
||||||
build-backend = "poetry.core.masonry.api"
|
# ignore-init-module = true
|
||||||
|
# ignore-init-method = true
|
||||||
|
# ignore-nested-functions = false
|
||||||
|
# ignore-magic = false
|
||||||
|
# ignore-semiprivate = false
|
||||||
|
# ignore-private = false
|
||||||
|
# ignore-property-decorators = false
|
||||||
|
# ignore-module = false
|
||||||
|
# ignore-setters = false
|
||||||
|
# fail-under = 80
|
||||||
|
# output-format = "term-missing"
|
||||||
|
# color = true
|
||||||
|
# paths = ["src/lerobot"]
|
||||||
|
|
||||||
|
# [tool.mypy]
|
||||||
|
# python_version = "3.10"
|
||||||
|
# warn_return_any = true
|
||||||
|
# warn_unused_configs = true
|
||||||
|
# ignore_missing_imports = false
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class Camera(abc.ABC):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def find_cameras() -> List[Dict[str, Any]]:
|
def find_cameras() -> list[dict[str, Any]]:
|
||||||
"""Detects available cameras connected to the system.
|
"""Detects available cameras connected to the system.
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, Any]]: A list of dictionaries,
|
List[Dict[str, Any]]: A list of dictionaries,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import platform
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Event, Lock, Thread
|
from threading import Event, Lock, Thread
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
# Fix MSMF hardware transform compatibility for Windows before importing cv2
|
# Fix MSMF hardware transform compatibility for Windows before importing cv2
|
||||||
if platform.system() == "Windows" and "OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS" not in os.environ:
|
if platform.system() == "Windows" and "OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS" not in os.environ:
|
||||||
@@ -245,7 +245,7 @@ class OpenCVCamera(Camera):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_cameras() -> List[Dict[str, Any]]:
|
def find_cameras() -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Detects available OpenCV cameras connected to the system.
|
Detects available OpenCV cameras connected to the system.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Provides the RealSenseCamera class for capturing frames from Intel RealSense cam
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from threading import Event, Lock, Thread
|
from threading import Event, Lock, Thread
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -194,7 +194,7 @@ class RealSenseCamera(Camera):
|
|||||||
logger.info(f"{self} connected.")
|
logger.info(f"{self} connected.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_cameras() -> List[Dict[str, Any]]:
|
def find_cameras() -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Detects available Intel RealSense cameras connected to the system.
|
Detects available Intel RealSense cameras connected to the system.
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class RealSenseCameraConfig(CameraConfig):
|
|||||||
Example configurations for Intel RealSense D405:
|
Example configurations for Intel RealSense D405:
|
||||||
```python
|
```python
|
||||||
# Basic configurations
|
# Basic configurations
|
||||||
RealSenseCameraConfig("0123456789", 30, 1280, 720) # 1280x720 @ 30FPS
|
RealSenseCameraConfig("0123456789", 30, 1280, 720) # 1280x720 @ 30FPS
|
||||||
RealSenseCameraConfig("0123456789", 60, 640, 480) # 640x480 @ 60FPS
|
RealSenseCameraConfig("0123456789", 60, 640, 480) # 640x480 @ 60FPS
|
||||||
|
|
||||||
# Advanced configurations
|
# Advanced configurations
|
||||||
RealSenseCameraConfig("0123456789", 30, 640, 480, use_depth=True) # With depth sensing
|
RealSenseCameraConfig("0123456789", 30, 640, 480, use_depth=True) # With depth sensing
|
||||||
RealSenseCameraConfig("0123456789", 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
|
RealSenseCameraConfig("0123456789", 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
|
||||||
```
|
```
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import inspect
|
|||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentError
|
from argparse import ArgumentError
|
||||||
|
from collections.abc import Sequence
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
|
|
||||||
@@ -76,9 +76,8 @@ def parse_plugin_args(plugin_arg_suffix: str, args: Sequence[str]) -> dict:
|
|||||||
- Values are the corresponding argument values
|
- Values are the corresponding argument values
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
>>> args = ['--env.discover_packages_path=my_package',
|
>>> args = ["--env.discover_packages_path=my_package", "--other_arg=value"]
|
||||||
... '--other_arg=value']
|
>>> parse_plugin_args("discover_packages_path", args)
|
||||||
>>> parse_plugin_args('discover_packages_path', args)
|
|
||||||
{'env.discover_packages_path': 'my_package'}
|
{'env.discover_packages_path': 'my_package'}
|
||||||
"""
|
"""
|
||||||
plugin_args = {}
|
plugin_args = {}
|
||||||
@@ -111,7 +110,7 @@ def load_plugin(plugin_path: str) -> None:
|
|||||||
PluginLoadError: If the plugin cannot be loaded due to import errors or if the package path is invalid.
|
PluginLoadError: If the plugin cannot be loaded due to import errors or if the package path is invalid.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>>> load_plugin("external_plugin.core") # Loads plugin from external package
|
>>> load_plugin("external_plugin.core") # Loads plugin from external package
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- The plugin package should handle its own registration during import
|
- The plugin package should handle its own registration during import
|
||||||
|
|||||||
@@ -12,13 +12,14 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import abc
|
import abc
|
||||||
|
import builtins
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Type, TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
@@ -31,7 +32,6 @@ from lerobot.optim.schedulers import LRSchedulerConfig
|
|||||||
from lerobot.utils.hub import HubMixin
|
from lerobot.utils.hub import HubMixin
|
||||||
from lerobot.utils.utils import auto_select_torch_device, is_amp_available, is_torch_device_available
|
from lerobot.utils.utils import auto_select_torch_device, is_amp_available, is_torch_device_available
|
||||||
|
|
||||||
# Generic variable that is either PreTrainedConfig or a subclass thereof
|
|
||||||
T = TypeVar("T", bound="PreTrainedConfig")
|
T = TypeVar("T", bound="PreTrainedConfig")
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class PreTrainedConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
cls: Type[T],
|
cls: builtins.type[T],
|
||||||
pretrained_name_or_path: str | Path,
|
pretrained_name_or_path: str | Path,
|
||||||
*,
|
*,
|
||||||
force_download: bool = False,
|
force_download: bool = False,
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import builtins
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
@@ -135,7 +135,7 @@ class TrainPipelineConfig(HubMixin):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
cls: Type["TrainPipelineConfig"],
|
cls: builtins.type["TrainPipelineConfig"],
|
||||||
pretrained_name_or_path: str | Path,
|
pretrained_name_or_path: str | Path,
|
||||||
*,
|
*,
|
||||||
force_download: bool = False,
|
force_download: bool = False,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
# For reference on dataset card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/datasetcard.md?plain=1
|
# For reference on dataset card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/datasetcard.md?plain=1
|
||||||
# Doc / guide: https://huggingface.co/docs/hub/datasets-cards
|
# Doc / guide: https://huggingface.co/docs/hub/datasets-cards
|
||||||
{{ card_data }}
|
# prettier-ignore
|
||||||
|
{{card_data}}
|
||||||
---
|
---
|
||||||
|
|
||||||
This dataset was created using [LeRobot](https://github.com/huggingface/lerobot).
|
This dataset was created using [LeRobot](https://github.com/huggingface/lerobot).
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import datasets
|
import datasets
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import datasets
|
import datasets
|
||||||
import numpy
|
import numpy
|
||||||
@@ -77,7 +76,7 @@ def check_repo_id(repo_id: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
# TODO(aliberts): remove
|
# TODO(aliberts): remove
|
||||||
def calculate_episode_data_index(hf_dataset: datasets.Dataset) -> Dict[str, torch.Tensor]:
|
def calculate_episode_data_index(hf_dataset: datasets.Dataset) -> dict[str, torch.Tensor]:
|
||||||
"""
|
"""
|
||||||
Calculate episode data index for the provided HuggingFace Dataset. Relies on episode_index column of hf_dataset.
|
Calculate episode data index for the provided HuggingFace Dataset. Relies on episode_index column of hf_dataset.
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from typing import Iterator, Union
|
from collections.abc import Iterator
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ class EpisodeAwareSampler:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
episode_data_index: dict,
|
episode_data_index: dict,
|
||||||
episode_indices_to_use: Union[list, None] = None,
|
episode_indices_to_use: list | None = None,
|
||||||
drop_n_first_frames: int = 0,
|
drop_n_first_frames: int = 0,
|
||||||
drop_n_last_frames: int = 0,
|
drop_n_last_frames: int = 0,
|
||||||
shuffle: bool = False,
|
shuffle: bool = False,
|
||||||
|
|||||||
@@ -14,13 +14,16 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import collections
|
import collections
|
||||||
|
from collections.abc import Callable, Sequence
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Callable, Sequence
|
from typing import Any
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from torchvision.transforms import v2
|
from torchvision.transforms import v2
|
||||||
from torchvision.transforms.v2 import Transform
|
from torchvision.transforms.v2 import (
|
||||||
from torchvision.transforms.v2 import functional as F # noqa: N812
|
Transform,
|
||||||
|
functional as F, # noqa: N812
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RandomSubsetApply(Transform):
|
class RandomSubsetApply(Transform):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
|
|
||||||
@@ -179,10 +179,10 @@ class EnvTransformConfig:
|
|||||||
add_joint_velocity_to_observation: bool = False
|
add_joint_velocity_to_observation: bool = False
|
||||||
add_current_to_observation: bool = False
|
add_current_to_observation: bool = False
|
||||||
add_ee_pose_to_observation: bool = False
|
add_ee_pose_to_observation: bool = False
|
||||||
crop_params_dict: Optional[dict[str, tuple[int, int, int, int]]] = None
|
crop_params_dict: dict[str, tuple[int, int, int, int]] | None = None
|
||||||
resize_size: Optional[tuple[int, int]] = None
|
resize_size: tuple[int, int] | None = None
|
||||||
control_time_s: float = 20.0
|
control_time_s: float = 20.0
|
||||||
fixed_reset_joint_positions: Optional[Any] = None
|
fixed_reset_joint_positions: Any | None = None
|
||||||
reset_time_s: float = 5.0
|
reset_time_s: float = 5.0
|
||||||
use_gripper: bool = True
|
use_gripper: bool = True
|
||||||
gripper_quantization_threshold: float | None = 0.8
|
gripper_quantization_threshold: float | None = 0.8
|
||||||
@@ -195,21 +195,21 @@ class EnvTransformConfig:
|
|||||||
class HILSerlRobotEnvConfig(EnvConfig):
|
class HILSerlRobotEnvConfig(EnvConfig):
|
||||||
"""Configuration for the HILSerlRobotEnv environment."""
|
"""Configuration for the HILSerlRobotEnv environment."""
|
||||||
|
|
||||||
robot: Optional[RobotConfig] = None
|
robot: RobotConfig | None = None
|
||||||
teleop: Optional[TeleoperatorConfig] = None
|
teleop: TeleoperatorConfig | None = None
|
||||||
wrapper: Optional[EnvTransformConfig] = None
|
wrapper: EnvTransformConfig | None = None
|
||||||
fps: int = 10
|
fps: int = 10
|
||||||
name: str = "real_robot"
|
name: str = "real_robot"
|
||||||
mode: str = None # Either "record", "replay", None
|
mode: str = None # Either "record", "replay", None
|
||||||
repo_id: Optional[str] = None
|
repo_id: str | None = None
|
||||||
dataset_root: Optional[str] = None
|
dataset_root: str | None = None
|
||||||
task: str = ""
|
task: str = ""
|
||||||
num_episodes: int = 10 # only for record mode
|
num_episodes: int = 10 # only for record mode
|
||||||
episode: int = 0
|
episode: int = 0
|
||||||
device: str = "cuda"
|
device: str = "cuda"
|
||||||
push_to_hub: bool = True
|
push_to_hub: bool = True
|
||||||
pretrained_policy_name_or_path: Optional[str] = None
|
pretrained_policy_name_or_path: str | None = None
|
||||||
reward_classifier_pretrained_path: Optional[str] = None
|
reward_classifier_pretrained_path: str | None = None
|
||||||
# For the reward classifier, to record more positive examples after a success
|
# For the reward classifier, to record more positive examples after a success
|
||||||
number_of_steps_after_success: int = 0
|
number_of_steps_after_success: int = 0
|
||||||
|
|
||||||
@@ -248,18 +248,18 @@ class HILEnvConfig(EnvConfig):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
################# args from hilserlrobotenv
|
################# args from hilserlrobotenv
|
||||||
reward_classifier_pretrained_path: Optional[str] = None
|
reward_classifier_pretrained_path: str | None = None
|
||||||
robot_config: Optional[RobotConfig] = None
|
robot_config: RobotConfig | None = None
|
||||||
teleop_config: Optional[TeleoperatorConfig] = None
|
teleop_config: TeleoperatorConfig | None = None
|
||||||
wrapper: Optional[EnvTransformConfig] = None
|
wrapper: EnvTransformConfig | None = None
|
||||||
mode: str = None # Either "record", "replay", None
|
mode: str = None # Either "record", "replay", None
|
||||||
repo_id: Optional[str] = None
|
repo_id: str | None = None
|
||||||
dataset_root: Optional[str] = None
|
dataset_root: str | None = None
|
||||||
num_episodes: int = 10 # only for record mode
|
num_episodes: int = 10 # only for record mode
|
||||||
episode: int = 0
|
episode: int = 0
|
||||||
device: str = "cuda"
|
device: str = "cuda"
|
||||||
push_to_hub: bool = True
|
push_to_hub: bool = True
|
||||||
pretrained_policy_name_or_path: Optional[str] = None
|
pretrained_policy_name_or_path: str | None = None
|
||||||
# For the reward classifier, to record more positive examples after a success
|
# For the reward classifier, to record more positive examples after a success
|
||||||
number_of_steps_after_success: int = 0
|
number_of_steps_after_success: int = 0
|
||||||
############################
|
############################
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import concurrent.futures
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -46,14 +46,14 @@ from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraCon
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def find_all_opencv_cameras() -> List[Dict[str, Any]]:
|
def find_all_opencv_cameras() -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Finds all available OpenCV cameras plugged into the system.
|
Finds all available OpenCV cameras plugged into the system.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A list of all available OpenCV cameras with their metadata.
|
A list of all available OpenCV cameras with their metadata.
|
||||||
"""
|
"""
|
||||||
all_opencv_cameras_info: List[Dict[str, Any]] = []
|
all_opencv_cameras_info: list[dict[str, Any]] = []
|
||||||
logger.info("Searching for OpenCV cameras...")
|
logger.info("Searching for OpenCV cameras...")
|
||||||
try:
|
try:
|
||||||
opencv_cameras = OpenCVCamera.find_cameras()
|
opencv_cameras = OpenCVCamera.find_cameras()
|
||||||
@@ -66,14 +66,14 @@ def find_all_opencv_cameras() -> List[Dict[str, Any]]:
|
|||||||
return all_opencv_cameras_info
|
return all_opencv_cameras_info
|
||||||
|
|
||||||
|
|
||||||
def find_all_realsense_cameras() -> List[Dict[str, Any]]:
|
def find_all_realsense_cameras() -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Finds all available RealSense cameras plugged into the system.
|
Finds all available RealSense cameras plugged into the system.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A list of all available RealSense cameras with their metadata.
|
A list of all available RealSense cameras with their metadata.
|
||||||
"""
|
"""
|
||||||
all_realsense_cameras_info: List[Dict[str, Any]] = []
|
all_realsense_cameras_info: list[dict[str, Any]] = []
|
||||||
logger.info("Searching for RealSense cameras...")
|
logger.info("Searching for RealSense cameras...")
|
||||||
try:
|
try:
|
||||||
realsense_cameras = RealSenseCamera.find_cameras()
|
realsense_cameras = RealSenseCamera.find_cameras()
|
||||||
@@ -88,7 +88,7 @@ def find_all_realsense_cameras() -> List[Dict[str, Any]]:
|
|||||||
return all_realsense_cameras_info
|
return all_realsense_cameras_info
|
||||||
|
|
||||||
|
|
||||||
def find_and_print_cameras(camera_type_filter: str | None = None) -> List[Dict[str, Any]]:
|
def find_and_print_cameras(camera_type_filter: str | None = None) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Finds available cameras based on an optional filter and prints their information.
|
Finds available cameras based on an optional filter and prints their information.
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ def find_and_print_cameras(camera_type_filter: str | None = None) -> List[Dict[s
|
|||||||
Returns:
|
Returns:
|
||||||
A list of all available cameras matching the filter, with their metadata.
|
A list of all available cameras matching the filter, with their metadata.
|
||||||
"""
|
"""
|
||||||
all_cameras_info: List[Dict[str, Any]] = []
|
all_cameras_info: list[dict[str, Any]] = []
|
||||||
|
|
||||||
if camera_type_filter:
|
if camera_type_filter:
|
||||||
camera_type_filter = camera_type_filter.lower()
|
camera_type_filter = camera_type_filter.lower()
|
||||||
@@ -153,7 +153,7 @@ def save_image(
|
|||||||
logger.error(f"Failed to save image for camera {camera_identifier} (type {camera_type}): {e}")
|
logger.error(f"Failed to save image for camera {camera_identifier} (type {camera_type}): {e}")
|
||||||
|
|
||||||
|
|
||||||
def create_camera_instance(cam_meta: Dict[str, Any]) -> Dict[str, Any] | None:
|
def create_camera_instance(cam_meta: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
"""Create and connect to a camera instance based on metadata."""
|
"""Create and connect to a camera instance based on metadata."""
|
||||||
cam_type = cam_meta.get("type")
|
cam_type = cam_meta.get("type")
|
||||||
cam_id = cam_meta.get("id")
|
cam_id = cam_meta.get("id")
|
||||||
@@ -190,7 +190,7 @@ def create_camera_instance(cam_meta: Dict[str, Any]) -> Dict[str, Any] | None:
|
|||||||
|
|
||||||
|
|
||||||
def process_camera_image(
|
def process_camera_image(
|
||||||
cam_dict: Dict[str, Any], output_dir: Path, current_time: float
|
cam_dict: dict[str, Any], output_dir: Path, current_time: float
|
||||||
) -> concurrent.futures.Future | None:
|
) -> concurrent.futures.Future | None:
|
||||||
"""Capture and process an image from a single camera."""
|
"""Capture and process an image from a single camera."""
|
||||||
cam = cam_dict["instance"]
|
cam = cam_dict["instance"]
|
||||||
@@ -216,7 +216,7 @@ def process_camera_image(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def cleanup_cameras(cameras_to_use: List[Dict[str, Any]]):
|
def cleanup_cameras(cameras_to_use: list[dict[str, Any]]):
|
||||||
"""Disconnect all cameras."""
|
"""Disconnect all cameras."""
|
||||||
logger.info(f"Disconnecting {len(cameras_to_use)} cameras...")
|
logger.info(f"Disconnecting {len(cameras_to_use)} cameras...")
|
||||||
for cam_dict in cameras_to_use:
|
for cam_dict in cameras_to_use:
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class MotorsBus(abc.ABC):
|
|||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_port.py
|
python -m lerobot.find_port.py
|
||||||
>>> Finding all available ports for the MotorsBus.
|
>>> Finding all available ports for the MotorsBus.
|
||||||
>>> ['/dev/tty.usbmodem575E0032081', '/dev/tty.usbmodem575E0031751']
|
>>> ["/dev/tty.usbmodem575E0032081", "/dev/tty.usbmodem575E0031751"]
|
||||||
>>> Remove the usb cable from your MotorsBus and press Enter when done.
|
>>> Remove the usb cable from your MotorsBus and press Enter when done.
|
||||||
>>> The port of this MotorsBus is /dev/tty.usbmodem575E0031751.
|
>>> The port of this MotorsBus is /dev/tty.usbmodem575E0031751.
|
||||||
>>> Reconnect the usb cable.
|
>>> Reconnect the usb cable.
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ The majority of changes here involve removing unused code, unifying naming, and
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from collections.abc import Callable
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -216,7 +216,7 @@ class ACTTemporalEnsembler:
|
|||||||
continue
|
continue
|
||||||
avg *= exp_weights[:i].sum()
|
avg *= exp_weights[:i].sum()
|
||||||
avg += item * exp_weights[i]
|
avg += item * exp_weights[i]
|
||||||
avg /= exp_weights[:i+1].sum()
|
avg /= exp_weights[: i + 1].sum()
|
||||||
print("online", avg)
|
print("online", avg)
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ TODO(alexander-soare):
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.version
|
import torch.version
|
||||||
@@ -228,12 +227,12 @@ class PaliGemmaWithExpertModel(PreTrainedModel):
|
|||||||
# TODO: break down this huge forward into modules or functions
|
# TODO: break down this huge forward into modules or functions
|
||||||
def forward(
|
def forward(
|
||||||
self,
|
self,
|
||||||
attention_mask: Optional[torch.Tensor] = None,
|
attention_mask: torch.Tensor | None = None,
|
||||||
position_ids: Optional[torch.LongTensor] = None,
|
position_ids: torch.LongTensor | None = None,
|
||||||
past_key_values: Optional[Union[List[torch.FloatTensor], Cache]] = None,
|
past_key_values: list[torch.FloatTensor] | Cache | None = None,
|
||||||
inputs_embeds: List[torch.FloatTensor] = None,
|
inputs_embeds: list[torch.FloatTensor] = None,
|
||||||
use_cache: Optional[bool] = None,
|
use_cache: bool | None = None,
|
||||||
fill_kv_cache: Optional[bool] = None,
|
fill_kv_cache: bool | None = None,
|
||||||
):
|
):
|
||||||
models = [self.paligemma.language_model, self.gemma_expert.model]
|
models = [self.paligemma.language_model, self.gemma_expert.model]
|
||||||
|
|
||||||
|
|||||||
@@ -12,20 +12,20 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import abc
|
import abc
|
||||||
|
import builtins
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from importlib.resources import files
|
from importlib.resources import files
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import List, Type, TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
import packaging
|
import packaging
|
||||||
import safetensors
|
import safetensors
|
||||||
from huggingface_hub import HfApi, ModelCard, ModelCardData, hf_hub_download
|
from huggingface_hub import HfApi, ModelCard, ModelCardData, hf_hub_download
|
||||||
from huggingface_hub.constants import SAFETENSORS_SINGLE_FILE
|
from huggingface_hub.constants import SAFETENSORS_SINGLE_FILE
|
||||||
from huggingface_hub.errors import HfHubHTTPError
|
from huggingface_hub.errors import HfHubHTTPError
|
||||||
from safetensors.torch import load_model as load_model_as_safetensor
|
from safetensors.torch import load_model as load_model_as_safetensor, save_model as save_model_as_safetensor
|
||||||
from safetensors.torch import save_model as save_model_as_safetensor
|
|
||||||
from torch import Tensor, nn
|
from torch import Tensor, nn
|
||||||
|
|
||||||
from lerobot.configs.policies import PreTrainedConfig
|
from lerobot.configs.policies import PreTrainedConfig
|
||||||
@@ -67,7 +67,7 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
cls: Type[T],
|
cls: builtins.type[T],
|
||||||
pretrained_name_or_path: str | Path,
|
pretrained_name_or_path: str | Path,
|
||||||
*,
|
*,
|
||||||
config: PreTrainedConfig | None = None,
|
config: PreTrainedConfig | None = None,
|
||||||
@@ -223,7 +223,7 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
|||||||
logging.info(f"Model pushed to {commit_info.repo_url.url}")
|
logging.info(f"Model pushed to {commit_info.repo_url.url}")
|
||||||
|
|
||||||
def generate_model_card(
|
def generate_model_card(
|
||||||
self, dataset_repo_id: str, model_type: str, license: str | None, tags: List[str] | None
|
self, dataset_repo_id: str, model_type: str, license: str | None, tags: list[str] | None
|
||||||
) -> ModelCard:
|
) -> ModelCard:
|
||||||
base_model = "lerobot/smolvla_base" if model_type == "smolvla" else None # Set a base model
|
base_model = "lerobot/smolvla_base" if model_type == "smolvla" else None # Set a base model
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import Callable, Literal
|
from typing import Literal
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from torch import nn
|
from torch import nn
|
||||||
@@ -403,12 +402,12 @@ class SmolVLMWithExpertModel(nn.Module):
|
|||||||
|
|
||||||
def forward(
|
def forward(
|
||||||
self,
|
self,
|
||||||
attention_mask: Optional[torch.Tensor] = None,
|
attention_mask: torch.Tensor | None = None,
|
||||||
position_ids: Optional[torch.LongTensor] = None,
|
position_ids: torch.LongTensor | None = None,
|
||||||
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
past_key_values: list[torch.FloatTensor] | None = None,
|
||||||
inputs_embeds: List[torch.FloatTensor] = None,
|
inputs_embeds: list[torch.FloatTensor] = None,
|
||||||
use_cache: Optional[bool] = None,
|
use_cache: bool | None = None,
|
||||||
fill_kv_cache: Optional[bool] = None,
|
fill_kv_cache: bool | None = None,
|
||||||
):
|
):
|
||||||
models = [self.get_vlm_model().text_model, self.lm_expert]
|
models = [self.get_vlm_model().text_model, self.lm_expert]
|
||||||
model_layers = self.get_model_layers(models)
|
model_layers = self.get_model_layers(models)
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ The comments in this code may sometimes refer to these references:
|
|||||||
# ruff: noqa: N806
|
# ruff: noqa: N806
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from collections.abc import Callable
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Callable, List
|
from collections.abc import Callable
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -901,7 +901,7 @@ class MLP(torch.nn.Sequential):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
in_channels: int,
|
in_channels: int,
|
||||||
hidden_channels: List[int],
|
hidden_channels: list[int],
|
||||||
):
|
):
|
||||||
layers = []
|
layers = []
|
||||||
in_dim = in_channels
|
in_dim = in_channels
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
from collections.abc import Callable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from random import randrange
|
from random import randrange
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.distributed as distributed
|
import torch.distributed as distributed
|
||||||
@@ -198,7 +198,7 @@ class GPT(nn.Module):
|
|||||||
|
|
||||||
# report number of parameters
|
# report number of parameters
|
||||||
n_params = sum(p.numel() for p in self.parameters())
|
n_params = sum(p.numel() for p in self.parameters())
|
||||||
print("number of parameters: {:.2f}M".format(n_params / 1e6))
|
print(f"number of parameters: {n_params / 1e6:.2f}M")
|
||||||
|
|
||||||
def forward(self, input, targets=None):
|
def forward(self, input, targets=None):
|
||||||
device = input.device
|
device = input.device
|
||||||
@@ -255,7 +255,7 @@ class GPT(nn.Module):
|
|||||||
blacklist_weight_modules = (torch.nn.LayerNorm, torch.nn.Embedding)
|
blacklist_weight_modules = (torch.nn.LayerNorm, torch.nn.Embedding)
|
||||||
for mn, m in self.named_modules():
|
for mn, m in self.named_modules():
|
||||||
for pn, _p in m.named_parameters():
|
for pn, _p in m.named_parameters():
|
||||||
fpn = "{}.{}".format(mn, pn) if mn else pn # full param name
|
fpn = f"{mn}.{pn}" if mn else pn # full param name
|
||||||
if pn.endswith("bias"):
|
if pn.endswith("bias"):
|
||||||
# all biases will not be decayed
|
# all biases will not be decayed
|
||||||
no_decay.add(fpn)
|
no_decay.add(fpn)
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ import time
|
|||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from lerobot.cameras import ( # noqa: F401
|
from lerobot.cameras import ( # noqa: F401
|
||||||
CameraConfig, # noqa: F401
|
CameraConfig, # noqa: F401
|
||||||
@@ -190,7 +189,7 @@ def record_loop(
|
|||||||
events: dict,
|
events: dict,
|
||||||
fps: int,
|
fps: int,
|
||||||
dataset: LeRobotDataset | None = None,
|
dataset: LeRobotDataset | None = None,
|
||||||
teleop: Teleoperator | List[Teleoperator] | None = None,
|
teleop: Teleoperator | list[Teleoperator] | None = None,
|
||||||
policy: PreTrainedPolicy | None = None,
|
policy: PreTrainedPolicy | None = None,
|
||||||
control_time_s: int | None = None,
|
control_time_s: int | None = None,
|
||||||
single_task: str | None = None,
|
single_task: str | None = None,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
Follow the [installation instructions](https://github.com/huggingface/lerobot#installation) to install LeRobot.
|
Follow the [installation instructions](https://github.com/huggingface/lerobot#installation) to install LeRobot.
|
||||||
|
|
||||||
Install LeRobot with HopeJR dependencies:
|
Install LeRobot with HopeJR dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[hopejr]"
|
pip install -e ".[hopejr]"
|
||||||
```
|
```
|
||||||
@@ -40,35 +41,39 @@ python -m lerobot.calibrate \
|
|||||||
When running the calibration script, a calibration GUI will pop up. Finger joints are named as follows:
|
When running the calibration script, a calibration GUI will pop up. Finger joints are named as follows:
|
||||||
|
|
||||||
**Thumb**:
|
**Thumb**:
|
||||||
|
|
||||||
- **CMC**: base joint connecting thumb to hand
|
- **CMC**: base joint connecting thumb to hand
|
||||||
- **MCP**: knuckle joint
|
- **MCP**: knuckle joint
|
||||||
- **PIP**: first finger joint
|
- **PIP**: first finger joint
|
||||||
- **DIP** : fingertip joint
|
- **DIP** : fingertip joint
|
||||||
|
|
||||||
**Index, Middle, Ring, and Pinky fingers**:
|
**Index, Middle, Ring, and Pinky fingers**:
|
||||||
|
|
||||||
- **Radial flexor**: Moves base of finger towards the thumb
|
- **Radial flexor**: Moves base of finger towards the thumb
|
||||||
- **Ulnar flexor**: Moves base of finger towards the pinky
|
- **Ulnar flexor**: Moves base of finger towards the pinky
|
||||||
- **PIP/DIP**: Flexes the distal and proximal phalanx of the finger
|
- **PIP/DIP**: Flexes the distal and proximal phalanx of the finger
|
||||||
|
|
||||||
Each one of these will need to be calibrated individually via the GUI.
|
Each one of these will need to be calibrated individually via the GUI.
|
||||||
Note that ulnar and radial flexors should have ranges of the same size (but with different offsets) in order to get symmetric movement.
|
Note that ulnar and radial flexors should have ranges of the same size (but with different offsets) in order to get symmetric movement.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_1.png"
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_1.png"
|
||||||
alt="Setting boundaries in the hand calibration GUI"
|
alt="Setting boundaries in the hand calibration GUI"
|
||||||
title="Setting boundaries in the hand calibration GUI"
|
title="Setting boundaries in the hand calibration GUI"
|
||||||
width="100%">
|
width="100%"
|
||||||
</img>
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Use the calibration interface to set the range boundaries for each joint as shown above.
|
Use the calibration interface to set the range boundaries for each joint as shown above.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_2.png"
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_2.png"
|
||||||
alt="Saving calibration values"
|
alt="Saving calibration values"
|
||||||
title="Saving calibration values"
|
title="Saving calibration values"
|
||||||
width="100%">
|
width="100%"
|
||||||
</img>
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Once you have set the appropriate boundaries for all joints, click "Save" to save the calibration values to the motors.
|
Once you have set the appropriate boundaries for all joints, click "Save" to save the calibration values to the motors.
|
||||||
@@ -122,16 +127,18 @@ python -m lerobot.calibrate \
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will open a calibration GUI where you can set the range limits for each motor. The arm motions are organized as follows:
|
This will open a calibration GUI where you can set the range limits for each motor. The arm motions are organized as follows:
|
||||||
|
|
||||||
- **Shoulder**: pitch, yaw, and roll
|
- **Shoulder**: pitch, yaw, and roll
|
||||||
- **Elbow**: flex
|
- **Elbow**: flex
|
||||||
- **Wrist**: pitch, yaw, and roll
|
- **Wrist**: pitch, yaw, and roll
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_2.png"
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibration_gui_2.png"
|
||||||
alt="Setting boundaries in the arm calibration GUI"
|
alt="Setting boundaries in the arm calibration GUI"
|
||||||
title="Setting boundaries in the arm calibration GUI"
|
title="Setting boundaries in the arm calibration GUI"
|
||||||
width="100%">
|
width="100%"
|
||||||
</img>
|
></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Use the calibration interface to set the range boundaries for each joint. Move each joint through its full range of motion and adjust the minimum and maximum values accordingly. Once you have set the appropriate boundaries for all joints, save the calibration.
|
Use the calibration interface to set the range boundaries for each joint. Move each joint through its full range of motion and adjust the minimum and maximum values accordingly. Once you have set the appropriate boundaries for all joints, save the calibration.
|
||||||
@@ -169,6 +176,7 @@ Calibration saved to /Users/your_username/.cache/huggingface/lerobot/calibration
|
|||||||
Due to global variable conflicts in the Feetech middleware, teleoperation for arm and hand must run in separate shell sessions:
|
Due to global variable conflicts in the Feetech middleware, teleoperation for arm and hand must run in separate shell sessions:
|
||||||
|
|
||||||
### Hand
|
### Hand
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.teleoperate \
|
python -m lerobot.teleoperate \
|
||||||
--robot.type=hope_jr_hand \
|
--robot.type=hope_jr_hand \
|
||||||
@@ -184,6 +192,7 @@ python -m lerobot.teleoperate \
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Arm
|
### Arm
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.teleoperate \
|
python -m lerobot.teleoperate \
|
||||||
--robot.type=hope_jr_arm \
|
--robot.type=hope_jr_arm \
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ For a visual walkthrough of the assembly process, you can refer to [this video t
|
|||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Since the production of this video, we simplified the configuration phase. Because of this, two things differ from the instructions in that video:
|
> Since the production of this video, we simplified the configuration phase. Because of this, two things differ from the instructions in that video:
|
||||||
|
>
|
||||||
> - Don't plug in all the motor cables right away and wait to be instructed to do so in [Configure the motors](#configure-the-motors).
|
> - Don't plug in all the motor cables right away and wait to be instructed to do so in [Configure the motors](#configure-the-motors).
|
||||||
> - Don't screw in the controller board (PCB) to the base right away and wait for being instructed to do so in [Configure the motors](#configure-the-motors).
|
> - Don't screw in the controller board (PCB) to the base right away and wait for being instructed to do so in [Configure the motors](#configure-the-motors).
|
||||||
|
|
||||||
|
|
||||||
## Install LeRobot 🤗
|
## Install LeRobot 🤗
|
||||||
|
|
||||||
To install LeRobot follow, our [Installation Guide](./installation)
|
To install LeRobot follow, our [Installation Guide](./installation)
|
||||||
|
|
||||||
In addition to these instructions, you need to install the Dynamixel SDK:
|
In addition to these instructions, you need to install the Dynamixel SDK:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[dynamixel]"
|
pip install -e ".[dynamixel]"
|
||||||
```
|
```
|
||||||
@@ -28,6 +29,7 @@ pip install -e ".[dynamixel]"
|
|||||||
### 1. Find the USB ports associated with each arm
|
### 1. Find the USB ports associated with each arm
|
||||||
|
|
||||||
To find the port for each bus servo adapter, run this script:
|
To find the port for each bus servo adapter, run this script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_port
|
python -m lerobot.find_port
|
||||||
```
|
```
|
||||||
@@ -54,6 +56,7 @@ Where the found port is: `/dev/tty.usbmodem575E0032081` corresponding to your le
|
|||||||
<hfoption id="Linux">
|
<hfoption id="Linux">
|
||||||
|
|
||||||
On Linux, you might need to give access to the USB ports by running:
|
On Linux, you might need to give access to the USB ports by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chmod 666 /dev/ttyACM0
|
sudo chmod 666 /dev/ttyACM0
|
||||||
sudo chmod 666 /dev/ttyACM1
|
sudo chmod 666 /dev/ttyACM1
|
||||||
@@ -99,9 +102,11 @@ python -m lerobot.setup_motors \
|
|||||||
--robot.type=koch_follower \
|
--robot.type=koch_follower \
|
||||||
--robot.port=/dev/tty.usbmodem575E0031751 # <- paste here the port found at previous step
|
--robot.port=/dev/tty.usbmodem575E0031751 # <- paste here the port found at previous step
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.koch_follower import KochFollower, KochFollowerConfig
|
from lerobot.robots.koch_follower import KochFollower, KochFollowerConfig
|
||||||
|
|
||||||
@@ -112,10 +117,13 @@ config = KochFollowerConfig(
|
|||||||
follower = KochFollower(config)
|
follower = KochFollower(config)
|
||||||
follower.setup_motors()
|
follower.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
You should see the following instruction.
|
You should see the following instruction.
|
||||||
|
|
||||||
```
|
```
|
||||||
Connect the controller board to the 'gripper' motor only and press enter.
|
Connect the controller board to the 'gripper' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -125,22 +133,26 @@ As instructed, plug the gripper's motor. Make sure it's the only motor connected
|
|||||||
<details>
|
<details>
|
||||||
<summary>Troubleshooting</summary>
|
<summary>Troubleshooting</summary>
|
||||||
|
|
||||||
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
||||||
<ul>
|
|
||||||
<li>Power supply</li>
|
<ul>
|
||||||
<li>USB cable between your computer and the controller board</li>
|
<li>Power supply</li>
|
||||||
<li>The 3-pin cable from the controller board to the motor</li>
|
<li>USB cable between your computer and the controller board</li>
|
||||||
</ul>
|
<li>The 3-pin cable from the controller board to the motor</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
||||||
|
|
||||||
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
You should then see the following message:
|
You should then see the following message:
|
||||||
|
|
||||||
```
|
```
|
||||||
'gripper' motor id set to 6
|
'gripper' motor id set to 6
|
||||||
```
|
```
|
||||||
|
|
||||||
Followed by the next instruction:
|
Followed by the next instruction:
|
||||||
|
|
||||||
```
|
```
|
||||||
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -155,6 +167,7 @@ Repeat the operation for each motor as instructed.
|
|||||||
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
||||||
|
|
||||||
#### Leader
|
#### Leader
|
||||||
|
|
||||||
Do the same steps for the leader arm but modify the command or script accordingly.
|
Do the same steps for the leader arm but modify the command or script accordingly.
|
||||||
|
|
||||||
<hfoptions id="setup_motors">
|
<hfoptions id="setup_motors">
|
||||||
@@ -165,9 +178,11 @@ python -m lerobot.setup_motors \
|
|||||||
--teleop.type=koch_leader \
|
--teleop.type=koch_leader \
|
||||||
--teleop.port=/dev/tty.usbmodem575E0031751 \ # <- paste here the port found at previous step
|
--teleop.port=/dev/tty.usbmodem575E0031751 \ # <- paste here the port found at previous step
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.koch_leader import KochLeader, KochLeaderConfig
|
from lerobot.teleoperators.koch_leader import KochLeader, KochLeaderConfig
|
||||||
|
|
||||||
@@ -178,6 +193,8 @@ config = KochLeaderConfig(
|
|||||||
leader = KochLeader(config)
|
leader = KochLeader(config)
|
||||||
leader.setup_motors()
|
leader.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -199,9 +216,11 @@ python -m lerobot.calibrate \
|
|||||||
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.koch_follower import KochFollowerConfig, KochFollower
|
from lerobot.robots.koch_follower import KochFollowerConfig, KochFollower
|
||||||
|
|
||||||
@@ -215,6 +234,8 @@ follower.connect(calibrate=False)
|
|||||||
follower.calibrate()
|
follower.calibrate()
|
||||||
follower.disconnect()
|
follower.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -233,9 +254,11 @@ python -m lerobot.calibrate \
|
|||||||
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.koch_leader import KochLeaderConfig, KochLeader
|
from lerobot.teleoperators.koch_leader import KochLeaderConfig, KochLeader
|
||||||
|
|
||||||
@@ -249,10 +272,12 @@ leader.connect(calibrate=False)
|
|||||||
leader.calibrate()
|
leader.calibrate()
|
||||||
leader.disconnect()
|
leader.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|||||||
@@ -8,31 +8,43 @@ Follow this [README](https://github.com/SIGRobotics-UIUC/LeKiwi). It contains th
|
|||||||
And advise if it's your first time printing or if you don't own a 3D printer.
|
And advise if it's your first time printing or if you don't own a 3D printer.
|
||||||
|
|
||||||
### Wired version
|
### Wired version
|
||||||
|
|
||||||
If you have the **wired** LeKiwi version, you can skip the installation of the Raspberry Pi and setting up SSH. You can also run all commands directly on your PC for both the LeKiwi scripts and the leader arm scripts for teleoperating.
|
If you have the **wired** LeKiwi version, you can skip the installation of the Raspberry Pi and setting up SSH. You can also run all commands directly on your PC for both the LeKiwi scripts and the leader arm scripts for teleoperating.
|
||||||
|
|
||||||
## Install software on Pi
|
## Install software on Pi
|
||||||
|
|
||||||
Now we have to set up the remote PC that will run on the LeKiwi Robot. This is normally a Raspberry Pi, but can be any PC that can run on 5V and has enough usb ports (2 or more) for the cameras and motor control board.
|
Now we have to set up the remote PC that will run on the LeKiwi Robot. This is normally a Raspberry Pi, but can be any PC that can run on 5V and has enough usb ports (2 or more) for the cameras and motor control board.
|
||||||
|
|
||||||
### Install OS
|
### Install OS
|
||||||
|
|
||||||
For setting up the Raspberry Pi and its SD-card see: [Setup PI](https://www.raspberrypi.com/documentation/computers/getting-started.html). Here is explained how to download the [Imager](https://www.raspberrypi.com/software/) to install Raspberry Pi OS or Ubuntu.
|
For setting up the Raspberry Pi and its SD-card see: [Setup PI](https://www.raspberrypi.com/documentation/computers/getting-started.html). Here is explained how to download the [Imager](https://www.raspberrypi.com/software/) to install Raspberry Pi OS or Ubuntu.
|
||||||
|
|
||||||
### Setup SSH
|
### Setup SSH
|
||||||
|
|
||||||
After setting up your Pi, you should enable and set up [SSH](https://www.raspberrypi.com/news/coding-on-raspberry-pi-remotely-with-visual-studio-code/) (Secure Shell Protocol) so you can log in to the Pi from your laptop without requiring a screen, keyboard, and mouse on the Pi. A great tutorial on how to do this can be found [here](https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh). Logging into your Pi can be done in your Command Prompt (cmd) or, if you use VSCode you can use [this](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) extension.
|
After setting up your Pi, you should enable and set up [SSH](https://www.raspberrypi.com/news/coding-on-raspberry-pi-remotely-with-visual-studio-code/) (Secure Shell Protocol) so you can log in to the Pi from your laptop without requiring a screen, keyboard, and mouse on the Pi. A great tutorial on how to do this can be found [here](https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh). Logging into your Pi can be done in your Command Prompt (cmd) or, if you use VSCode you can use [this](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) extension.
|
||||||
|
|
||||||
### Install LeRobot on Pi 🤗
|
### Install LeRobot on Pi 🤗
|
||||||
|
|
||||||
On your Raspberry Pi install LeRobot using our [Installation Guide](./installation)
|
On your Raspberry Pi install LeRobot using our [Installation Guide](./installation)
|
||||||
|
|
||||||
In addition to these instructions, you need to install the Feetech sdk on your Pi:
|
In addition to these instructions, you need to install the Feetech SDK & ZeroMQ on your Pi:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[feetech]"
|
pip install -e ".[lekiwi]"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install LeRobot locally
|
## Install LeRobot locally
|
||||||
|
|
||||||
If you already have installed LeRobot on your laptop/pc you can skip this step; otherwise, please follow along as we do the same steps we did on the Pi.
|
If you already have installed LeRobot on your laptop/pc you can skip this step; otherwise, please follow along as we do the same steps we did on the Pi.
|
||||||
|
|
||||||
Follow our [Installation Guide](./installation)
|
Follow our [Installation Guide](./installation)
|
||||||
|
|
||||||
|
In addition to these instructions, you need to install the Feetech SDK & ZeroMQ on your laptop/pc:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e ".[lekiwi]"
|
||||||
|
```
|
||||||
|
|
||||||
Great :hugs:! You are now done installing LeRobot, and we can begin assembling the SO100/SO101 arms and the mobile base :robot:.
|
Great :hugs:! You are now done installing LeRobot, and we can begin assembling the SO100/SO101 arms and the mobile base :robot:.
|
||||||
Every time you now want to use LeRobot, you can go to the `~/lerobot` folder where we installed LeRobot and run one of the commands.
|
Every time you now want to use LeRobot, you can go to the `~/lerobot` folder where we installed LeRobot and run one of the commands.
|
||||||
|
|
||||||
@@ -46,6 +58,7 @@ First, we will assemble the two SO100/SO101 arms. One to attach to the mobile ba
|
|||||||
### Find the USB ports associated with motor board
|
### Find the USB ports associated with motor board
|
||||||
|
|
||||||
To find the port for each bus servo adapter, run this script:
|
To find the port for each bus servo adapter, run this script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_port
|
python -m lerobot.find_port
|
||||||
```
|
```
|
||||||
@@ -72,6 +85,7 @@ Where the found port is: `/dev/tty.usbmodem575E0032081` corresponding to your bo
|
|||||||
<hfoption id="Linux">
|
<hfoption id="Linux">
|
||||||
|
|
||||||
On Linux, you might need to give access to the USB ports by running:
|
On Linux, you might need to give access to the USB ports by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chmod 666 /dev/ttyACM0
|
sudo chmod 666 /dev/ttyACM0
|
||||||
sudo chmod 666 /dev/ttyACM1
|
sudo chmod 666 /dev/ttyACM1
|
||||||
@@ -96,6 +110,7 @@ Where the found port is: `/dev/ttyACM0` corresponding to your board.
|
|||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
### Configure motors
|
### Configure motors
|
||||||
|
|
||||||
The instructions for configuring the motors can be found in the SO101 [docs](./so101#configure-the-motors). Besides the ids for the arm motors, we also need to set the motor ids for the mobile base. These need to be in a specific order to work. Below an image of the motor ids and motor mounting positions for the mobile base. Note that we only use one Motor Control board on LeKiwi. This means the motor ids for the wheels are 7, 8 and 9.
|
The instructions for configuring the motors can be found in the SO101 [docs](./so101#configure-the-motors). Besides the ids for the arm motors, we also need to set the motor ids for the mobile base. These need to be in a specific order to work. Below an image of the motor ids and motor mounting positions for the mobile base. Note that we only use one Motor Control board on LeKiwi. This means the motor ids for the wheels are 7, 8 and 9.
|
||||||
|
|
||||||
You can run this command to setup motors for LeKiwi. It will first setup the motors for arm (id 6..1) and then setup motors for wheels (9,8,7)
|
You can run this command to setup motors for LeKiwi. It will first setup the motors for arm (id 6..1) and then setup motors for wheels (9,8,7)
|
||||||
@@ -113,27 +128,36 @@ python -m lerobot.setup_motors \
|
|||||||
If you are having trouble connecting to the Mobile SO100, follow these steps to diagnose and resolve the issue.
|
If you are having trouble connecting to the Mobile SO100, follow these steps to diagnose and resolve the issue.
|
||||||
|
|
||||||
#### 1. Verify IP Address Configuration
|
#### 1. Verify IP Address Configuration
|
||||||
|
|
||||||
Make sure that the correct IP for the Pi is used in the commands or in your code. To check the Raspberry Pi's IP address, run (on the Pi command line):
|
Make sure that the correct IP for the Pi is used in the commands or in your code. To check the Raspberry Pi's IP address, run (on the Pi command line):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hostname -I
|
hostname -I
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Check if Pi is reachable from laptop/pc
|
#### 2. Check if Pi is reachable from laptop/pc
|
||||||
|
|
||||||
Try pinging the Raspberry Pi from your laptop:
|
Try pinging the Raspberry Pi from your laptop:
|
||||||
|
|
||||||
```bach
|
```bach
|
||||||
ping <your_pi_ip_address>
|
ping <your_pi_ip_address>
|
||||||
```
|
```
|
||||||
|
|
||||||
If the ping fails:
|
If the ping fails:
|
||||||
|
|
||||||
- Ensure the Pi is powered on and connected to the same network.
|
- Ensure the Pi is powered on and connected to the same network.
|
||||||
- Check if SSH is enabled on the Pi.
|
- Check if SSH is enabled on the Pi.
|
||||||
|
|
||||||
#### 3. Try SSH connection
|
#### 3. Try SSH connection
|
||||||
|
|
||||||
If you can't SSH into the Pi, it might not be properly connected. Use:
|
If you can't SSH into the Pi, it might not be properly connected. Use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh <your_pi_user_name>@<your_pi_ip_address>
|
ssh <your_pi_user_name>@<your_pi_ip_address>
|
||||||
```
|
```
|
||||||
|
|
||||||
If you get a connection error:
|
If you get a connection error:
|
||||||
|
|
||||||
- Ensure SSH is enabled on the Pi by running:
|
- Ensure SSH is enabled on the Pi by running:
|
||||||
```bash
|
```bash
|
||||||
sudo raspi-config
|
sudo raspi-config
|
||||||
@@ -158,10 +182,13 @@ python -m lerobot.calibrate \
|
|||||||
We unified the calibration method for most robots, thus, the calibration steps for this SO100 arm are the same as the steps for the Koch and SO101. First, we have to move the robot to the position where each joint is in the middle of its range, then we press `Enter`. Secondly, we move all joints through their full range of motion. A video of this same process for the SO101 as reference can be found [here](https://huggingface.co/docs/lerobot/en/so101#calibration-video).
|
We unified the calibration method for most robots, thus, the calibration steps for this SO100 arm are the same as the steps for the Koch and SO101. First, we have to move the robot to the position where each joint is in the middle of its range, then we press `Enter`. Secondly, we move all joints through their full range of motion. A video of this same process for the SO101 as reference can be found [here](https://huggingface.co/docs/lerobot/en/so101#calibration-video).
|
||||||
|
|
||||||
### Wired version
|
### Wired version
|
||||||
|
|
||||||
If you have the **wired** LeKiwi version, please run all commands on your laptop.
|
If you have the **wired** LeKiwi version, please run all commands on your laptop.
|
||||||
|
|
||||||
### Calibrate leader arm
|
### Calibrate leader arm
|
||||||
|
|
||||||
Then, to calibrate the leader arm (which is attached to the laptop/pc). Run the following command of API example on your laptop:
|
Then, to calibrate the leader arm (which is attached to the laptop/pc). Run the following command of API example on your laptop:
|
||||||
|
|
||||||
<hfoptions id="calibrate_leader">
|
<hfoptions id="calibrate_leader">
|
||||||
<hfoption id="Command">
|
<hfoption id="Command">
|
||||||
|
|
||||||
@@ -171,9 +198,11 @@ python -m lerobot.calibrate \
|
|||||||
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so100_leader import SO100LeaderConfig, SO100Leader
|
from lerobot.teleoperators.so100_leader import SO100LeaderConfig, SO100Leader
|
||||||
|
|
||||||
@@ -187,6 +216,8 @@ leader.connect(calibrate=False)
|
|||||||
leader.calibrate()
|
leader.calibrate()
|
||||||
leader.disconnect()
|
leader.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -196,6 +227,7 @@ leader.disconnect()
|
|||||||
> If you're using a Mac, you might need to give Terminal permission to access your keyboard for teleoperation. Go to System Preferences > Security & Privacy > Input Monitoring and check the box for Terminal.
|
> If you're using a Mac, you might need to give Terminal permission to access your keyboard for teleoperation. Go to System Preferences > Security & Privacy > Input Monitoring and check the box for Terminal.
|
||||||
|
|
||||||
To teleoperate, SSH into your Raspberry Pi, and run `conda activate lerobot` and this command:
|
To teleoperate, SSH into your Raspberry Pi, and run `conda activate lerobot` and this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.robots.lekiwi.lekiwi_host --robot.id=my_awesome_kiwi
|
python -m lerobot.robots.lekiwi.lekiwi_host --robot.id=my_awesome_kiwi
|
||||||
```
|
```
|
||||||
@@ -206,7 +238,7 @@ Then on your laptop, also run `conda activate lerobot` and run the API example,
|
|||||||
python examples/lekiwi/teleoperate.py
|
python examples/lekiwi/teleoperate.py
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see on your laptop something like this: ```[INFO] Connected to remote robot at tcp://172.17.133.91:5555 and video stream at tcp://172.17.133.91:5556.``` Now you can move the leader arm and use the keyboard (w,a,s,d) to drive forward, left, backwards, right. And use (z,x) to turn left or turn right. You can use (r,f) to increase and decrease the speed of the mobile robot. There are three speed modes, see the table below:
|
You should see on your laptop something like this: `[INFO] Connected to remote robot at tcp://172.17.133.91:5555 and video stream at tcp://172.17.133.91:5556.` Now you can move the leader arm and use the keyboard (w,a,s,d) to drive forward, left, backwards, right. And use (z,x) to turn left or turn right. You can use (r,f) to increase and decrease the speed of the mobile robot. There are three speed modes, see the table below:
|
||||||
|
|
||||||
| Speed Mode | Linear Speed (m/s) | Rotation Speed (deg/s) |
|
| Speed Mode | Linear Speed (m/s) | Rotation Speed (deg/s) |
|
||||||
| ---------- | ------------------ | ---------------------- |
|
| ---------- | ------------------ | ---------------------- |
|
||||||
@@ -214,7 +246,6 @@ You should see on your laptop something like this: ```[INFO] Connected to remote
|
|||||||
| Medium | 0.25 | 60 |
|
| Medium | 0.25 | 60 |
|
||||||
| Slow | 0.1 | 30 |
|
| Slow | 0.1 | 30 |
|
||||||
|
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
| --- | -------------- |
|
| --- | -------------- |
|
||||||
| W | Move forward |
|
| W | Move forward |
|
||||||
@@ -227,9 +258,10 @@ You should see on your laptop something like this: ```[INFO] Connected to remote
|
|||||||
| F | Decrease speed |
|
| F | Decrease speed |
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you use a different keyboard, you can change the keys for each command in the [`LeKiwiConfig`](../src/lerobot/robot_devices/robots/configs.py).
|
> If you use a different keyboard, you can change the keys for each command in the [`LeKiwiConfig`](../src/lerobot/robot_devices/robots/configs.py).
|
||||||
|
|
||||||
### Wired version
|
### Wired version
|
||||||
|
|
||||||
If you have the **wired** LeKiwi version, please run all commands on your laptop.
|
If you have the **wired** LeKiwi version, please run all commands on your laptop.
|
||||||
|
|
||||||
## Record a dataset
|
## Record a dataset
|
||||||
@@ -239,26 +271,32 @@ Once you're familiar with teleoperation, you can record your first dataset.
|
|||||||
We use the Hugging Face hub features for uploading your dataset. If you haven't previously used the Hub, make sure you can login via the cli using a write-access token, this token can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens).
|
We use the Hugging Face hub features for uploading your dataset. If you haven't previously used the Hub, make sure you can login via the cli using a write-access token, this token can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens).
|
||||||
|
|
||||||
Add your token to the CLI by running this command:
|
Add your token to the CLI by running this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
||||||
```
|
```
|
||||||
|
|
||||||
Then store your Hugging Face repository name in a variable:
|
Then store your Hugging Face repository name in a variable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HF_USER=$(huggingface-cli whoami | head -n 1)
|
HF_USER=$(huggingface-cli whoami | head -n 1)
|
||||||
echo $HF_USER
|
echo $HF_USER
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can record a dataset. To record episodes and upload your dataset to the hub, execute this API example tailored for LeKiwi. Make sure to first adapt the `remote_ip`, `repo_id`, `port` and `task` in the script. If you would like to run the script for longer you can increase `NB_CYCLES_CLIENT_CONNECTION`.
|
Now you can record a dataset. To record episodes and upload your dataset to the hub, execute this API example tailored for LeKiwi. Make sure to first adapt the `remote_ip`, `repo_id`, `port` and `task` in the script. If you would like to run the script for longer you can increase `NB_CYCLES_CLIENT_CONNECTION`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python examples/lekiwi/record.py
|
python examples/lekiwi/record.py
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Dataset upload
|
#### Dataset upload
|
||||||
|
|
||||||
Locally, your dataset is stored in this folder: `~/.cache/huggingface/lerobot/{repo-id}`. At the end of data recording, your dataset will be uploaded on your Hugging Face page (e.g. https://huggingface.co/datasets/cadene/so101_test) that you can obtain by running:
|
Locally, your dataset is stored in this folder: `~/.cache/huggingface/lerobot/{repo-id}`. At the end of data recording, your dataset will be uploaded on your Hugging Face page (e.g. https://huggingface.co/datasets/cadene/so101_test) that you can obtain by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo https://huggingface.co/datasets/${HF_USER}/so101_test
|
echo https://huggingface.co/datasets/${HF_USER}/so101_test
|
||||||
```
|
```
|
||||||
|
|
||||||
Your dataset will be automatically tagged with `LeRobot` for the community to find it easily, and you can also add custom tags (in this case `tutorial` for example).
|
Your dataset will be automatically tagged with `LeRobot` for the community to find it easily, and you can also add custom tags (in this case `tutorial` for example).
|
||||||
|
|
||||||
You can look for other LeRobot datasets on the hub by searching for `LeRobot` [tags](https://huggingface.co/datasets?other=LeRobot).
|
You can look for other LeRobot datasets on the hub by searching for `LeRobot` [tags](https://huggingface.co/datasets?other=LeRobot).
|
||||||
@@ -274,14 +312,13 @@ Avoid adding too much variation too quickly, as it may hinder your results.
|
|||||||
If you want to dive deeper into this important topic, you can check out the [blog post](https://huggingface.co/blog/lerobot-datasets#what-makes-a-good-dataset) we wrote on what makes a good dataset.
|
If you want to dive deeper into this important topic, you can check out the [blog post](https://huggingface.co/blog/lerobot-datasets#what-makes-a-good-dataset) we wrote on what makes a good dataset.
|
||||||
|
|
||||||
#### Troubleshooting:
|
#### Troubleshooting:
|
||||||
- On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux).
|
|
||||||
|
|
||||||
|
- On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux).
|
||||||
|
|
||||||
## Replay an episode
|
## Replay an episode
|
||||||
|
|
||||||
To replay an episode run the API example below, make sure to change `remote_ip`, `port`, LeRobotDatasetId and episode index.
|
To replay an episode run the API example below, make sure to change `remote_ip`, `port`, LeRobotDatasetId and episode index.
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python examples/lekiwi/replay.py
|
python examples/lekiwi/replay.py
|
||||||
```
|
```
|
||||||
@@ -297,4 +334,4 @@ python examples/lekiwi/evaluate.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|||||||
@@ -18,11 +18,10 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import zmq
|
|
||||||
|
|
||||||
from lerobot.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
from lerobot.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
||||||
|
|
||||||
@@ -35,6 +34,9 @@ class LeKiwiClient(Robot):
|
|||||||
name = "lekiwi_client"
|
name = "lekiwi_client"
|
||||||
|
|
||||||
def __init__(self, config: LeKiwiClientConfig):
|
def __init__(self, config: LeKiwiClientConfig):
|
||||||
|
import zmq
|
||||||
|
|
||||||
|
self._zmq = zmq
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.id = config.id
|
self.id = config.id
|
||||||
@@ -117,6 +119,7 @@ class LeKiwiClient(Robot):
|
|||||||
"LeKiwi Daemon is already connected. Do not run `robot.connect()` twice."
|
"LeKiwi Daemon is already connected. Do not run `robot.connect()` twice."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
zmq = self._zmq
|
||||||
self.zmq_context = zmq.Context()
|
self.zmq_context = zmq.Context()
|
||||||
self.zmq_cmd_socket = self.zmq_context.socket(zmq.PUSH)
|
self.zmq_cmd_socket = self.zmq_context.socket(zmq.PUSH)
|
||||||
zmq_cmd_locator = f"tcp://{self.remote_ip}:{self.port_zmq_cmd}"
|
zmq_cmd_locator = f"tcp://{self.remote_ip}:{self.port_zmq_cmd}"
|
||||||
@@ -139,8 +142,9 @@ class LeKiwiClient(Robot):
|
|||||||
def calibrate(self) -> None:
|
def calibrate(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _poll_and_get_latest_message(self) -> Optional[str]:
|
def _poll_and_get_latest_message(self) -> str | None:
|
||||||
"""Polls the ZMQ socket for a limited time and returns the latest message string."""
|
"""Polls the ZMQ socket for a limited time and returns the latest message string."""
|
||||||
|
zmq = self._zmq
|
||||||
poller = zmq.Poller()
|
poller = zmq.Poller()
|
||||||
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
||||||
|
|
||||||
@@ -167,7 +171,7 @@ class LeKiwiClient(Robot):
|
|||||||
|
|
||||||
return last_msg
|
return last_msg
|
||||||
|
|
||||||
def _parse_observation_json(self, obs_string: str) -> Optional[Dict[str, Any]]:
|
def _parse_observation_json(self, obs_string: str) -> dict[str, Any] | None:
|
||||||
"""Parses the JSON observation string."""
|
"""Parses the JSON observation string."""
|
||||||
try:
|
try:
|
||||||
return json.loads(obs_string)
|
return json.loads(obs_string)
|
||||||
@@ -175,7 +179,7 @@ class LeKiwiClient(Robot):
|
|||||||
logging.error(f"Error decoding JSON observation: {e}")
|
logging.error(f"Error decoding JSON observation: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _decode_image_from_b64(self, image_b64: str) -> Optional[np.ndarray]:
|
def _decode_image_from_b64(self, image_b64: str) -> np.ndarray | None:
|
||||||
"""Decodes a base64 encoded image string to an OpenCV image."""
|
"""Decodes a base64 encoded image string to an OpenCV image."""
|
||||||
if not image_b64:
|
if not image_b64:
|
||||||
return None
|
return None
|
||||||
@@ -191,18 +195,18 @@ class LeKiwiClient(Robot):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _remote_state_from_obs(
|
def _remote_state_from_obs(
|
||||||
self, observation: Dict[str, Any]
|
self, observation: dict[str, Any]
|
||||||
) -> Tuple[Dict[str, np.ndarray], Dict[str, Any]]:
|
) -> tuple[dict[str, np.ndarray], dict[str, Any]]:
|
||||||
"""Extracts frames, and state from the parsed observation."""
|
"""Extracts frames, and state from the parsed observation."""
|
||||||
|
|
||||||
flat_state = {key: observation.get(key, 0.0) for key in self._state_order}
|
flat_state = {key: observation.get(key, 0.0) for key in self._state_order}
|
||||||
|
|
||||||
state_vec = np.array([flat_state[key] for key in self._state_order], dtype=np.float32)
|
state_vec = np.array([flat_state[key] for key in self._state_order], dtype=np.float32)
|
||||||
|
|
||||||
obs_dict: Dict[str, Any] = {**flat_state, "observation.state": state_vec}
|
obs_dict: dict[str, Any] = {**flat_state, "observation.state": state_vec}
|
||||||
|
|
||||||
# Decode images
|
# Decode images
|
||||||
current_frames: Dict[str, np.ndarray] = {}
|
current_frames: dict[str, np.ndarray] = {}
|
||||||
for cam_name, image_b64 in observation.items():
|
for cam_name, image_b64 in observation.items():
|
||||||
if cam_name not in self._cameras_ft:
|
if cam_name not in self._cameras_ft:
|
||||||
continue
|
continue
|
||||||
@@ -212,7 +216,7 @@ class LeKiwiClient(Robot):
|
|||||||
|
|
||||||
return current_frames, obs_dict
|
return current_frames, obs_dict
|
||||||
|
|
||||||
def _get_data(self) -> Tuple[Dict[str, np.ndarray], Dict[str, Any], Dict[str, Any]]:
|
def _get_data(self) -> tuple[dict[str, np.ndarray], dict[str, Any], dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Polls the video socket for the latest observation data.
|
Polls the video socket for the latest observation data.
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import builtins
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Type
|
from typing import Any
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ class Robot(abc.ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Set these in ALL subclasses
|
# Set these in ALL subclasses
|
||||||
config_class: Type[RobotConfig]
|
config_class: builtins.type[RobotConfig]
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
def __init__(self, config: RobotConfig):
|
def __init__(self, config: RobotConfig):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Follow this [README](https://github.com/TheRobotStudio/SO-ARM100/blob/main/SO100
|
|||||||
To install LeRobot, follow our [Installation Guide](./installation)
|
To install LeRobot, follow our [Installation Guide](./installation)
|
||||||
|
|
||||||
In addition to these instructions, you need to install the Feetech SDK:
|
In addition to these instructions, you need to install the Feetech SDK:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[feetech]"
|
pip install -e ".[feetech]"
|
||||||
```
|
```
|
||||||
@@ -23,6 +24,7 @@ Unlike the SO-101, the motor connectors are not easily accessible once the arm i
|
|||||||
### 1. Find the USB ports associated with each arm
|
### 1. Find the USB ports associated with each arm
|
||||||
|
|
||||||
To find the port for each bus servo adapter, run this script:
|
To find the port for each bus servo adapter, run this script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_port
|
python -m lerobot.find_port
|
||||||
```
|
```
|
||||||
@@ -49,6 +51,7 @@ Where the found port is: `/dev/tty.usbmodem575E0032081` corresponding to your le
|
|||||||
<hfoption id="Linux">
|
<hfoption id="Linux">
|
||||||
|
|
||||||
On Linux, you might need to give access to the USB ports by running:
|
On Linux, you might need to give access to the USB ports by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chmod 666 /dev/ttyACM0
|
sudo chmod 666 /dev/ttyACM0
|
||||||
sudo chmod 666 /dev/ttyACM1
|
sudo chmod 666 /dev/ttyACM1
|
||||||
@@ -94,9 +97,11 @@ python -m lerobot.setup_motors \
|
|||||||
--robot.type=so100_follower \
|
--robot.type=so100_follower \
|
||||||
--robot.port=/dev/tty.usbmodem585A0076841 # <- paste here the port found at previous step
|
--robot.port=/dev/tty.usbmodem585A0076841 # <- paste here the port found at previous step
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.so100_follower import SO100Follower, SO100FollowerConfig
|
from lerobot.robots.so100_follower import SO100Follower, SO100FollowerConfig
|
||||||
|
|
||||||
@@ -107,10 +112,13 @@ config = SO100FollowerConfig(
|
|||||||
follower = SO100Follower(config)
|
follower = SO100Follower(config)
|
||||||
follower.setup_motors()
|
follower.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
You should see the following instruction
|
You should see the following instruction
|
||||||
|
|
||||||
```
|
```
|
||||||
Connect the controller board to the 'gripper' motor only and press enter.
|
Connect the controller board to the 'gripper' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -120,22 +128,26 @@ As instructed, plug the gripper's motor. Make sure it's the only motor connected
|
|||||||
<details>
|
<details>
|
||||||
<summary>Troubleshooting</summary>
|
<summary>Troubleshooting</summary>
|
||||||
|
|
||||||
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
||||||
<ul>
|
|
||||||
<li>Power supply</li>
|
<ul>
|
||||||
<li>USB cable between your computer and the controller board</li>
|
<li>Power supply</li>
|
||||||
<li>The 3-pin cable from the controller board to the motor</li>
|
<li>USB cable between your computer and the controller board</li>
|
||||||
</ul>
|
<li>The 3-pin cable from the controller board to the motor</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
You should then see the following message:
|
You should then see the following message:
|
||||||
|
|
||||||
```
|
```
|
||||||
'gripper' motor id set to 6
|
'gripper' motor id set to 6
|
||||||
```
|
```
|
||||||
|
|
||||||
Followed by the next instruction:
|
Followed by the next instruction:
|
||||||
|
|
||||||
```
|
```
|
||||||
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -150,6 +162,7 @@ Repeat the operation for each motor as instructed.
|
|||||||
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
||||||
|
|
||||||
#### Leader
|
#### Leader
|
||||||
|
|
||||||
Do the same steps for the leader arm.
|
Do the same steps for the leader arm.
|
||||||
|
|
||||||
<hfoptions id="setup_motors">
|
<hfoptions id="setup_motors">
|
||||||
@@ -162,6 +175,7 @@ python -m lerobot.setup_motors \
|
|||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so100_leader import SO100Leader, SO100LeaderConfig
|
from lerobot.teleoperators.so100_leader import SO100Leader, SO100LeaderConfig
|
||||||
|
|
||||||
@@ -172,6 +186,8 @@ config = SO100LeaderConfig(
|
|||||||
leader = SO100Leader(config)
|
leader = SO100Leader(config)
|
||||||
leader.setup_motors()
|
leader.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -184,7 +200,10 @@ leader.setup_motors()
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://github.com/user-attachments/assets/0c95b88c-5b85-413d-ba19-aee2f864f2a7" type="video/mp4" />
|
<source
|
||||||
|
src="https://github.com/user-attachments/assets/0c95b88c-5b85-413d-ba19-aee2f864f2a7"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -193,6 +212,7 @@ leader.setup_motors()
|
|||||||
Follow the video for removing gears. You need to remove the gear for the motors of the leader arm. As a result, you will only use the position encoding of the motor and reduce friction to more easily operate the leader arm.
|
Follow the video for removing gears. You need to remove the gear for the motors of the leader arm. As a result, you will only use the position encoding of the motor and reduce friction to more easily operate the leader arm.
|
||||||
|
|
||||||
### Clean Parts
|
### Clean Parts
|
||||||
|
|
||||||
Remove all support material from the 3D-printed parts. The easiest way to do this is using a small screwdriver to get underneath the support material.
|
Remove all support material from the 3D-printed parts. The easiest way to do this is using a small screwdriver to get underneath the support material.
|
||||||
|
|
||||||
### Additional Guidance
|
### Additional Guidance
|
||||||
@@ -202,7 +222,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://github.com/user-attachments/assets/488a39de-0189-4461-9de3-05b015f90cca" type="video/mp4" />
|
<source
|
||||||
|
src="https://github.com/user-attachments/assets/488a39de-0189-4461-9de3-05b015f90cca"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -216,75 +239,117 @@ This video provides visual guidance for assembling the arms, but it doesn't spec
|
|||||||
### First Motor
|
### First Motor
|
||||||
|
|
||||||
**Step 2: Insert Wires**
|
**Step 2: Insert Wires**
|
||||||
|
|
||||||
- Insert two wires into the first motor.
|
- Insert two wires into the first motor.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_1.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_1.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 3: Install in Base**
|
**Step 3: Install in Base**
|
||||||
|
|
||||||
- Place the first motor into the base.
|
- Place the first motor into the base.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_2.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_2.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 4: Secure Motor**
|
**Step 4: Secure Motor**
|
||||||
|
|
||||||
- Fasten the motor with 4 screws. Two from the bottom and two from top.
|
- Fasten the motor with 4 screws. Two from the bottom and two from top.
|
||||||
|
|
||||||
**Step 5: Attach Motor Holder**
|
**Step 5: Attach Motor Holder**
|
||||||
|
|
||||||
- Slide over the first motor holder and fasten it using two screws (one on each side).
|
- Slide over the first motor holder and fasten it using two screws (one on each side).
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_4.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_4.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 6: Attach Motor Horns**
|
**Step 6: Attach Motor Horns**
|
||||||
|
|
||||||
- Install both motor horns, securing the top horn with a screw. Try not to move the motor position when attaching the motor horn, especially for the leader arms, where we removed the gears.
|
- Install both motor horns, securing the top horn with a screw. Try not to move the motor position when attaching the motor horn, especially for the leader arms, where we removed the gears.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_5.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_5.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>Video adding motor horn</strong></summary>
|
<summary>
|
||||||
|
<strong>Video adding motor horn</strong>
|
||||||
|
</summary>
|
||||||
<video src="https://github.com/user-attachments/assets/ef3391a4-ad05-4100-b2bd-1699bf86c969"></video>
|
<video src="https://github.com/user-attachments/assets/ef3391a4-ad05-4100-b2bd-1699bf86c969"></video>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
**Step 7: Attach Shoulder Part**
|
**Step 7: Attach Shoulder Part**
|
||||||
|
|
||||||
- Route one wire to the back of the robot and the other to the left or towards you (see photo).
|
- Route one wire to the back of the robot and the other to the left or towards you (see photo).
|
||||||
- Attach the shoulder part.
|
- Attach the shoulder part.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_6.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_6.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 8: Secure Shoulder**
|
**Step 8: Secure Shoulder**
|
||||||
|
|
||||||
- Tighten the shoulder part with 4 screws on top and 4 on the bottom
|
- Tighten the shoulder part with 4 screws on top and 4 on the bottom
|
||||||
*(access bottom holes by turning the shoulder).*
|
_(access bottom holes by turning the shoulder)._
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Second Motor Assembly
|
### Second Motor Assembly
|
||||||
|
|
||||||
**Step 9: Install Motor 2**
|
**Step 9: Install Motor 2**
|
||||||
|
|
||||||
- Slide the second motor in from the top and link the wire from motor 1 to motor 2.
|
- Slide the second motor in from the top and link the wire from motor 1 to motor 2.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_8.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_8.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 10: Attach Shoulder Holder**
|
**Step 10: Attach Shoulder Holder**
|
||||||
|
|
||||||
- Add the shoulder motor holder.
|
- Add the shoulder motor holder.
|
||||||
- Ensure the wire from motor 1 to motor 2 goes behind the holder while the other wire is routed upward (see photo).
|
- Ensure the wire from motor 1 to motor 2 goes behind the holder while the other wire is routed upward (see photo).
|
||||||
- This part can be tight to assemble, you can use a workbench like the image or a similar setup to push the part around the motor.
|
- This part can be tight to assemble, you can use a workbench like the image or a similar setup to push the part around the motor.
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_9.webp" style="height:250px;"/>
|
<img
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_10.webp" style="height:250px;"/>
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_9.webp"
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_12.webp" style="height:250px;"/>
|
style="height:250px;"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_10.webp"
|
||||||
|
style="height:250px;"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_12.webp"
|
||||||
|
style="height:250px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**Step 11: Secure Motor 2**
|
**Step 11: Secure Motor 2**
|
||||||
|
|
||||||
- Fasten the second motor with 4 screws.
|
- Fasten the second motor with 4 screws.
|
||||||
|
|
||||||
**Step 12: Attach Motor Horn**
|
**Step 12: Attach Motor Horn**
|
||||||
|
|
||||||
- Attach both motor horns to motor 2, again use the horn screw.
|
- Attach both motor horns to motor 2, again use the horn screw.
|
||||||
|
|
||||||
**Step 13: Attach Base**
|
**Step 13: Attach Base**
|
||||||
|
|
||||||
- Install the base attachment using 2 screws.
|
- Install the base attachment using 2 screws.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_11.webp" style="height:300px;">
|
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_11.webp" style="height:300px;">
|
||||||
|
|
||||||
**Step 14: Attach Upper Arm**
|
**Step 14: Attach Upper Arm**
|
||||||
|
|
||||||
- Attach the upper arm with 4 screws on each side.
|
- Attach the upper arm with 4 screws on each side.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_13.webp" style="height:300px;">
|
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_13.webp" style="height:300px;">
|
||||||
@@ -294,89 +359,144 @@ This video provides visual guidance for assembling the arms, but it doesn't spec
|
|||||||
### Third Motor Assembly
|
### Third Motor Assembly
|
||||||
|
|
||||||
**Step 15: Install Motor 3**
|
**Step 15: Install Motor 3**
|
||||||
|
|
||||||
- Route the motor cable from motor 2 through the cable holder to motor 3, then secure motor 3 with 4 screws.
|
- Route the motor cable from motor 2 through the cable holder to motor 3, then secure motor 3 with 4 screws.
|
||||||
|
|
||||||
**Step 16: Attach Motor Horn**
|
**Step 16: Attach Motor Horn**
|
||||||
|
|
||||||
- Attach both motor horns to motor 3 and secure one again with a horn screw.
|
- Attach both motor horns to motor 3 and secure one again with a horn screw.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_14.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_14.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 17: Attach Forearm**
|
**Step 17: Attach Forearm**
|
||||||
|
|
||||||
- Connect the forearm to motor 3 using 4 screws on each side.
|
- Connect the forearm to motor 3 using 4 screws on each side.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_15.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_15.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Fourth Motor Assembly
|
### Fourth Motor Assembly
|
||||||
|
|
||||||
**Step 18: Install Motor 4**
|
**Step 18: Install Motor 4**
|
||||||
|
|
||||||
- Slide in motor 4, attach the cable from motor 3, and secure the cable in its holder with a screw.
|
- Slide in motor 4, attach the cable from motor 3, and secure the cable in its holder with a screw.
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_16.webp" style="height:300px;"/>
|
<img
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_19.webp" style="height:300px;"/>
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_16.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_19.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**Step 19: Attach Motor Holder 4**
|
**Step 19: Attach Motor Holder 4**
|
||||||
|
|
||||||
- Install the fourth motor holder (a tight fit). Ensure one wire is routed upward and the wire from motor 3 is routed downward (see photo).
|
- Install the fourth motor holder (a tight fit). Ensure one wire is routed upward and the wire from motor 3 is routed downward (see photo).
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_17.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_17.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 20: Secure Motor 4 & Attach Horn**
|
**Step 20: Secure Motor 4 & Attach Horn**
|
||||||
|
|
||||||
- Fasten motor 4 with 4 screws and attach its motor horns, use for one a horn screw.
|
- Fasten motor 4 with 4 screws and attach its motor horns, use for one a horn screw.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_18.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_18.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Wrist Assembly
|
### Wrist Assembly
|
||||||
|
|
||||||
**Step 21: Install Motor 5**
|
**Step 21: Install Motor 5**
|
||||||
|
|
||||||
- Insert motor 5 into the wrist holder and secure it with 2 front screws.
|
- Insert motor 5 into the wrist holder and secure it with 2 front screws.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_20.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_20.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 22: Attach Wrist**
|
**Step 22: Attach Wrist**
|
||||||
|
|
||||||
- Connect the wire from motor 4 to motor 5. And already insert the other wire for the gripper.
|
- Connect the wire from motor 4 to motor 5. And already insert the other wire for the gripper.
|
||||||
- Secure the wrist to motor 4 using 4 screws on both sides.
|
- Secure the wrist to motor 4 using 4 screws on both sides.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_22.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_22.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 23: Attach Wrist Horn**
|
**Step 23: Attach Wrist Horn**
|
||||||
|
|
||||||
- Install only one motor horn on the wrist motor and secure it with a horn screw.
|
- Install only one motor horn on the wrist motor and secure it with a horn screw.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_23.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_23.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Follower Configuration
|
### Follower Configuration
|
||||||
|
|
||||||
**Step 24: Attach Gripper**
|
**Step 24: Attach Gripper**
|
||||||
|
|
||||||
- Attach the gripper to motor 5.
|
- Attach the gripper to motor 5.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_24.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_24.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 25: Install Gripper Motor**
|
**Step 25: Install Gripper Motor**
|
||||||
|
|
||||||
- Insert the gripper motor, connect the motor wire from motor 5 to motor 6, and secure it with 3 screws on each side.
|
- Insert the gripper motor, connect the motor wire from motor 5 to motor 6, and secure it with 3 screws on each side.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_25.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_25.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 26: Attach Gripper Horn & Claw**
|
**Step 26: Attach Gripper Horn & Claw**
|
||||||
|
|
||||||
- Attach the motor horns and again use a horn screw.
|
- Attach the motor horns and again use a horn screw.
|
||||||
- Install the gripper claw and secure it with 4 screws on both sides.
|
- Install the gripper claw and secure it with 4 screws on both sides.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_26.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_26.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 27: Mount Controller**
|
**Step 27: Mount Controller**
|
||||||
|
|
||||||
- Attach the motor controller to the back of the robot.
|
- Attach the motor controller to the back of the robot.
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_27.webp" style="height:300px;"/>
|
<img
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_28.webp" style="height:300px;"/>
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_27.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_28.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
*Assembly complete – proceed to Leader arm assembly.*
|
_Assembly complete – proceed to Leader arm assembly._
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -385,31 +505,54 @@ This video provides visual guidance for assembling the arms, but it doesn't spec
|
|||||||
For the leader configuration, perform **Steps 1–23**. Make sure that you removed the motor gears from the motors.
|
For the leader configuration, perform **Steps 1–23**. Make sure that you removed the motor gears from the motors.
|
||||||
|
|
||||||
**Step 24: Attach Leader Holder**
|
**Step 24: Attach Leader Holder**
|
||||||
|
|
||||||
- Mount the leader holder onto the wrist and secure it with a screw.
|
- Mount the leader holder onto the wrist and secure it with a screw.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_29.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_29.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 25: Attach Handle**
|
**Step 25: Attach Handle**
|
||||||
|
|
||||||
- Attach the handle to motor 5 using 4 screws.
|
- Attach the handle to motor 5 using 4 screws.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_30.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_30.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 26: Install Gripper Motor**
|
**Step 26: Install Gripper Motor**
|
||||||
|
|
||||||
- Insert the gripper motor, secure it with 3 screws on each side, attach a motor horn using a horn screw, and connect the motor wire.
|
- Insert the gripper motor, secure it with 3 screws on each side, attach a motor horn using a horn screw, and connect the motor wire.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_31.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_31.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 27: Attach Trigger**
|
**Step 27: Attach Trigger**
|
||||||
|
|
||||||
- Attach the follower trigger with 4 screws.
|
- Attach the follower trigger with 4 screws.
|
||||||
|
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_32.webp" style="height:300px;"/>
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_32.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
|
||||||
**Step 28: Mount Controller**
|
**Step 28: Mount Controller**
|
||||||
|
|
||||||
- Attach the motor controller to the back of the robot.
|
- Attach the motor controller to the back of the robot.
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_27.webp" style="height:300px;"/>
|
<img
|
||||||
<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_28.webp" style="height:300px;"/>
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_27.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/so100_assembly_28.webp"
|
||||||
|
style="height:300px;"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Calibrate
|
## Calibrate
|
||||||
@@ -430,9 +573,11 @@ python -m lerobot.calibrate \
|
|||||||
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.so100_follower import SO100FollowerConfig, SO100Follower
|
from lerobot.robots.so100_follower import SO100FollowerConfig, SO100Follower
|
||||||
|
|
||||||
@@ -446,6 +591,8 @@ follower.connect(calibrate=False)
|
|||||||
follower.calibrate()
|
follower.calibrate()
|
||||||
follower.disconnect()
|
follower.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -464,9 +611,11 @@ python -m lerobot.calibrate \
|
|||||||
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so100_leader import SO100LeaderConfig, SO100Leader
|
from lerobot.teleoperators.so100_leader import SO100LeaderConfig, SO100Leader
|
||||||
|
|
||||||
@@ -480,10 +629,12 @@ leader.connect(calibrate=False)
|
|||||||
leader.calibrate()
|
leader.calibrate()
|
||||||
leader.disconnect()
|
leader.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ And advise if it's your first time printing or if you don't own a 3D printer.
|
|||||||
To install LeRobot, follow our [Installation Guide](./installation)
|
To install LeRobot, follow our [Installation Guide](./installation)
|
||||||
|
|
||||||
In addition to these instructions, you need to install the Feetech SDK:
|
In addition to these instructions, you need to install the Feetech SDK:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[feetech]"
|
pip install -e ".[feetech]"
|
||||||
```
|
```
|
||||||
@@ -20,16 +21,17 @@ pip install -e ".[feetech]"
|
|||||||
|
|
||||||
The follower arm uses 6x STS3215 motors with 1/345 gearing. The leader, however, uses three differently geared motors to make sure it can both sustain its own weight and it can be moved without requiring much force. Which motor is needed for which joint is shown in the table below.
|
The follower arm uses 6x STS3215 motors with 1/345 gearing. The leader, however, uses three differently geared motors to make sure it can both sustain its own weight and it can be moved without requiring much force. Which motor is needed for which joint is shown in the table below.
|
||||||
|
|
||||||
| Leader-Arm Axis | Motor | Gear Ratio |
|
| Leader-Arm Axis | Motor | Gear Ratio |
|
||||||
|-----------------|:-------:|:----------:|
|
| ------------------- | :---: | :--------: |
|
||||||
| Base / Shoulder Pan | 1 | 1 / 191 |
|
| Base / Shoulder Pan | 1 | 1 / 191 |
|
||||||
| Shoulder Lift | 2 | 1 / 345 |
|
| Shoulder Lift | 2 | 1 / 345 |
|
||||||
| Elbow Flex | 3 | 1 / 191 |
|
| Elbow Flex | 3 | 1 / 191 |
|
||||||
| Wrist Flex | 4 | 1 / 147 |
|
| Wrist Flex | 4 | 1 / 147 |
|
||||||
| Wrist Roll | 5 | 1 / 147 |
|
| Wrist Roll | 5 | 1 / 147 |
|
||||||
| Gripper | 6 | 1 / 147 |
|
| Gripper | 6 | 1 / 147 |
|
||||||
|
|
||||||
### Clean Parts
|
### Clean Parts
|
||||||
|
|
||||||
Remove all support material from the 3D-printed parts. The easiest way to do this is using a small screwdriver to get underneath the support material.
|
Remove all support material from the 3D-printed parts. The easiest way to do this is using a small screwdriver to get underneath the support material.
|
||||||
|
|
||||||
### Joint 1
|
### Joint 1
|
||||||
@@ -44,7 +46,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint1_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint1_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,7 +62,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint2_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint2_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,7 +77,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint3_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint3_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -81,7 +92,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint4_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint4_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -93,7 +107,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint5_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint5_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -109,7 +126,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Gripper_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Gripper_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -123,7 +143,10 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Leader_v2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Leader_v2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -135,6 +158,7 @@ Remove all support material from the 3D-printed parts. The easiest way to do thi
|
|||||||
### 1. Find the USB ports associated with each arm
|
### 1. Find the USB ports associated with each arm
|
||||||
|
|
||||||
To find the port for each bus servo adapter, run this script:
|
To find the port for each bus servo adapter, run this script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.find_port
|
python -m lerobot.find_port
|
||||||
```
|
```
|
||||||
@@ -161,6 +185,7 @@ Where the found port is: `/dev/tty.usbmodem575E0032081` corresponding to your le
|
|||||||
<hfoption id="Linux">
|
<hfoption id="Linux">
|
||||||
|
|
||||||
On Linux, you might need to give access to the USB ports by running:
|
On Linux, you might need to give access to the USB ports by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chmod 666 /dev/ttyACM0
|
sudo chmod 666 /dev/ttyACM0
|
||||||
sudo chmod 666 /dev/ttyACM1
|
sudo chmod 666 /dev/ttyACM1
|
||||||
@@ -198,7 +223,10 @@ The video below shows the sequence of steps for setting the motor ids.
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/setup_motors_so101_2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/setup_motors_so101_2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -214,9 +242,11 @@ python -m lerobot.setup_motors \
|
|||||||
--robot.type=so101_follower \
|
--robot.type=so101_follower \
|
||||||
--robot.port=/dev/tty.usbmodem585A0076841 # <- paste here the port found at previous step
|
--robot.port=/dev/tty.usbmodem585A0076841 # <- paste here the port found at previous step
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.so101_follower import SO101Follower, SO101FollowerConfig
|
from lerobot.robots.so101_follower import SO101Follower, SO101FollowerConfig
|
||||||
|
|
||||||
@@ -227,10 +257,13 @@ config = SO101FollowerConfig(
|
|||||||
follower = SO101Follower(config)
|
follower = SO101Follower(config)
|
||||||
follower.setup_motors()
|
follower.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
You should see the following instruction
|
You should see the following instruction
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Connect the controller board to the 'gripper' motor only and press enter.
|
Connect the controller board to the 'gripper' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -240,22 +273,26 @@ As instructed, plug the gripper's motor. Make sure it's the only motor connected
|
|||||||
<details>
|
<details>
|
||||||
<summary>Troubleshooting</summary>
|
<summary>Troubleshooting</summary>
|
||||||
|
|
||||||
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
If you get an error at that point, check your cables and make sure they are plugged in properly:
|
||||||
<ul>
|
|
||||||
<li>Power supply</li>
|
<ul>
|
||||||
<li>USB cable between your computer and the controller board</li>
|
<li>Power supply</li>
|
||||||
<li>The 3-pin cable from the controller board to the motor</li>
|
<li>USB cable between your computer and the controller board</li>
|
||||||
</ul>
|
<li>The 3-pin cable from the controller board to the motor</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
||||||
|
|
||||||
If you are using a Waveshare controller board, make sure that the two jumpers are set on the `B` channel (USB).
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
You should then see the following message:
|
You should then see the following message:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
'gripper' motor id set to 6
|
'gripper' motor id set to 6
|
||||||
```
|
```
|
||||||
|
|
||||||
Followed by the next instruction:
|
Followed by the next instruction:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
Connect the controller board to the 'wrist_roll' motor only and press enter.
|
||||||
```
|
```
|
||||||
@@ -270,6 +307,7 @@ Repeat the operation for each motor as instructed.
|
|||||||
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
When you are done, the script will simply finish, at which point the motors are ready to be used. You can now plug the 3-pin cable from each motor to the next one, and the cable from the first motor (the 'shoulder pan' with id=1) to the controller board, which can now be attached to the base of the arm.
|
||||||
|
|
||||||
#### Leader
|
#### Leader
|
||||||
|
|
||||||
Do the same steps for the leader arm.
|
Do the same steps for the leader arm.
|
||||||
|
|
||||||
<hfoptions id="setup_motors">
|
<hfoptions id="setup_motors">
|
||||||
@@ -280,9 +318,11 @@ python -m lerobot.setup_motors \
|
|||||||
--teleop.type=so101_leader \
|
--teleop.type=so101_leader \
|
||||||
--teleop.port=/dev/tty.usbmodem575E0031751 # <- paste here the port found at previous step
|
--teleop.port=/dev/tty.usbmodem575E0031751 # <- paste here the port found at previous step
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so101_leader import SO101Leader, SO101LeaderConfig
|
from lerobot.teleoperators.so101_leader import SO101Leader, SO101LeaderConfig
|
||||||
|
|
||||||
@@ -293,6 +333,8 @@ config = SO101LeaderConfig(
|
|||||||
leader = SO101Leader(config)
|
leader = SO101Leader(config)
|
||||||
leader.setup_motors()
|
leader.setup_motors()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -314,9 +356,11 @@ python -m lerobot.calibrate \
|
|||||||
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--robot.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
--robot.id=my_awesome_follower_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.robots.so101_follower import SO101FollowerConfig, SO101Follower
|
from lerobot.robots.so101_follower import SO101FollowerConfig, SO101Follower
|
||||||
|
|
||||||
@@ -330,6 +374,8 @@ follower.connect(calibrate=False)
|
|||||||
follower.calibrate()
|
follower.calibrate()
|
||||||
follower.disconnect()
|
follower.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
@@ -339,7 +385,10 @@ The video below shows how to perform the calibration. First you need to move the
|
|||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<video controls width="600">
|
<video controls width="600">
|
||||||
<source src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibrate_so101_2.mp4" type="video/mp4" />
|
<source
|
||||||
|
src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/calibrate_so101_2.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -356,9 +405,11 @@ python -m lerobot.calibrate \
|
|||||||
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
--teleop.port=/dev/tty.usbmodem58760431551 \ # <- The port of your robot
|
||||||
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
--teleop.id=my_awesome_leader_arm # <- Give the robot a unique name
|
||||||
```
|
```
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
<hfoption id="API example">
|
<hfoption id="API example">
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
```python
|
```python
|
||||||
from lerobot.teleoperators.so101_leader import SO101LeaderConfig, SO101Leader
|
from lerobot.teleoperators.so101_leader import SO101LeaderConfig, SO101Leader
|
||||||
|
|
||||||
@@ -372,10 +423,12 @@ leader.connect(calibrate=False)
|
|||||||
leader.calibrate()
|
leader.calibrate()
|
||||||
leader.disconnect()
|
leader.disconnect()
|
||||||
```
|
```
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|
||||||
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
Congrats 🎉, your robot is all set to learn a task on its own. Start training it by following this tutorial: [Getting started with real-world robots](./getting_started_real_world_robot)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
> If you have any questions or need help, please reach out on [Discord](https://discord.com/invite/s3KuuzsPFb).
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ This tutorial explains how to use [Stretch 3](https://hello-robot.com/stretch-3-
|
|||||||
Familiarize yourself with Stretch by following its [tutorials](https://docs.hello-robot.com/0.3/getting_started/hello_robot/) (recommended).
|
Familiarize yourself with Stretch by following its [tutorials](https://docs.hello-robot.com/0.3/getting_started/hello_robot/) (recommended).
|
||||||
|
|
||||||
To use LeRobot on Stretch, 3 options are available:
|
To use LeRobot on Stretch, 3 options are available:
|
||||||
|
|
||||||
- [tethered setup](https://docs.hello-robot.com/0.3/getting_started/connecting_to_stretch/#tethered-setup)
|
- [tethered setup](https://docs.hello-robot.com/0.3/getting_started/connecting_to_stretch/#tethered-setup)
|
||||||
- [untethered setup](https://docs.hello-robot.com/0.3/getting_started/connecting_to_stretch/#untethered-setup)
|
- [untethered setup](https://docs.hello-robot.com/0.3/getting_started/connecting_to_stretch/#untethered-setup)
|
||||||
- ssh directly into Stretch (you will first need to install and configure openssh-server on stretch using one of the two above setups)
|
- ssh directly into Stretch (you will first need to install and configure openssh-server on stretch using one of the two above setups)
|
||||||
|
|
||||||
|
|
||||||
## Install LeRobot
|
## Install LeRobot
|
||||||
|
|
||||||
On Stretch's CLI, follow these steps:
|
On Stretch's CLI, follow these steps:
|
||||||
|
|
||||||
1. [Install Miniconda](https://docs.anaconda.com/miniconda/#quick-command-line-install):
|
1. [Install Miniconda](https://docs.anaconda.com/miniconda/#quick-command-line-install):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/miniconda3
|
mkdir -p ~/miniconda3
|
||||||
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
|
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
|
||||||
@@ -24,6 +25,7 @@ rm ~/miniconda3/miniconda.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. Comment out these lines in `~/.profile` (this can mess up paths used by conda and ~/.local/bin should already be in your PATH)
|
2. Comment out these lines in `~/.profile` (this can mess up paths used by conda and ~/.local/bin should already be in your PATH)
|
||||||
|
|
||||||
```
|
```
|
||||||
# set PATH so it includes user's private bin if it exists
|
# set PATH so it includes user's private bin if it exists
|
||||||
if [ -d "$HOME/.local/bin" ] ; then
|
if [ -d "$HOME/.local/bin" ] ; then
|
||||||
@@ -34,21 +36,25 @@ fi
|
|||||||
3. Restart shell or `source ~/.bashrc`
|
3. Restart shell or `source ~/.bashrc`
|
||||||
|
|
||||||
4. Create and activate a fresh conda environment for lerobot
|
4. Create and activate a fresh conda environment for lerobot
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda create -y -n lerobot python=3.10 && conda activate lerobot
|
conda create -y -n lerobot python=3.10 && conda activate lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Clone LeRobot:
|
5. Clone LeRobot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/huggingface/lerobot.git ~/lerobot
|
git clone https://github.com/huggingface/lerobot.git ~/lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
6. When using `miniconda`, install `ffmpeg` in your environment:
|
6. When using `miniconda`, install `ffmpeg` in your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda install ffmpeg -c conda-forge
|
conda install ffmpeg -c conda-forge
|
||||||
```
|
```
|
||||||
|
|
||||||
7. Install LeRobot with stretch dependencies:
|
7. Install LeRobot with stretch dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/lerobot && pip install -e ".[stretch]"
|
cd ~/lerobot && pip install -e ".[stretch]"
|
||||||
```
|
```
|
||||||
@@ -56,6 +62,7 @@ cd ~/lerobot && pip install -e ".[stretch]"
|
|||||||
> **Note:** If you get this message, you can ignore it: `ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.`
|
> **Note:** If you get this message, you can ignore it: `ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.`
|
||||||
|
|
||||||
8. Run a [system check](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#system-check) to make sure your robot is ready:
|
8. Run a [system check](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#system-check) to make sure your robot is ready:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
stretch_system_check.py
|
stretch_system_check.py
|
||||||
```
|
```
|
||||||
@@ -63,6 +70,7 @@ stretch_system_check.py
|
|||||||
> **Note:** You may need to free the "robot process" after booting Stretch by running `stretch_free_robot_process.py`. For more info this Stretch's [doc](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#turning-off-gamepad-teleoperation).
|
> **Note:** You may need to free the "robot process" after booting Stretch by running `stretch_free_robot_process.py`. For more info this Stretch's [doc](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#turning-off-gamepad-teleoperation).
|
||||||
|
|
||||||
You should get something like this:
|
You should get something like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
For use with S T R E T C H (R) from Hello Robot Inc.
|
For use with S T R E T C H (R) from Hello Robot Inc.
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
@@ -89,11 +97,13 @@ Serial Number = stretch-se3-3054
|
|||||||
|
|
||||||
**Calibrate (Optional)**
|
**Calibrate (Optional)**
|
||||||
Before operating Stretch, you need to [home](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#homing) it first. Be mindful about giving Stretch some space as this procedure will move the robot's arm and gripper. Now run this command:
|
Before operating Stretch, you need to [home](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#homing) it first. Be mindful about giving Stretch some space as this procedure will move the robot's arm and gripper. Now run this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=stretch \
|
--robot.type=stretch \
|
||||||
--control.type=calibrate
|
--control.type=calibrate
|
||||||
```
|
```
|
||||||
|
|
||||||
This is equivalent to running `stretch_robot_home.py`
|
This is equivalent to running `stretch_robot_home.py`
|
||||||
|
|
||||||
> **Note:** If you run any of the LeRobot scripts below and Stretch is not properly homed, it will automatically home/calibrate first.
|
> **Note:** If you run any of the LeRobot scripts below and Stretch is not properly homed, it will automatically home/calibrate first.
|
||||||
@@ -104,28 +114,33 @@ Before trying teleoperation, you need to activate the gamepad controller by pres
|
|||||||
Now try out teleoperation (see above documentation to learn about the gamepad controls):
|
Now try out teleoperation (see above documentation to learn about the gamepad controls):
|
||||||
|
|
||||||
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
|
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=stretch \
|
--robot.type=stretch \
|
||||||
--control.type=teleoperate
|
--control.type=teleoperate
|
||||||
```
|
```
|
||||||
|
|
||||||
This is essentially the same as running `stretch_gamepad_teleop.py`
|
This is essentially the same as running `stretch_gamepad_teleop.py`
|
||||||
|
|
||||||
**Record a dataset**
|
**Record a dataset**
|
||||||
Once you're familiar with the gamepad controls and after a bit of practice, you can try to record your first dataset with Stretch.
|
Once you're familiar with the gamepad controls and after a bit of practice, you can try to record your first dataset with Stretch.
|
||||||
|
|
||||||
If you want to use the Hugging Face hub features for uploading your dataset and you haven't previously done it, make sure you've logged in using a write-access token, which can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens):
|
If you want to use the Hugging Face hub features for uploading your dataset and you haven't previously done it, make sure you've logged in using a write-access token, which can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
||||||
```
|
```
|
||||||
|
|
||||||
Store your Hugging Face repository name in a variable to run these commands:
|
Store your Hugging Face repository name in a variable to run these commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HF_USER=$(huggingface-cli whoami | head -n 1)
|
HF_USER=$(huggingface-cli whoami | head -n 1)
|
||||||
echo $HF_USER
|
echo $HF_USER
|
||||||
```
|
```
|
||||||
|
|
||||||
Record one episode:
|
Record one episode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=stretch \
|
--robot.type=stretch \
|
||||||
@@ -145,6 +160,7 @@ python lerobot/scripts/control_robot.py \
|
|||||||
|
|
||||||
**Replay an episode**
|
**Replay an episode**
|
||||||
Now try to replay this episode (make sure the robot's initial position is the same):
|
Now try to replay this episode (make sure the robot's initial position is the same):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=stretch \
|
--robot.type=stretch \
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ This tutorial explains how to use [Aloha and Aloha 2 stationary](https://www.tro
|
|||||||
|
|
||||||
Follow the [documentation from Trossen Robotics](https://docs.trossenrobotics.com/aloha_docs/2.0/getting_started/stationary/hardware_setup.html) for setting up the hardware and plugging the 4 arms and 4 cameras to your computer.
|
Follow the [documentation from Trossen Robotics](https://docs.trossenrobotics.com/aloha_docs/2.0/getting_started/stationary/hardware_setup.html) for setting up the hardware and plugging the 4 arms and 4 cameras to your computer.
|
||||||
|
|
||||||
|
|
||||||
## Install LeRobot
|
## Install LeRobot
|
||||||
|
|
||||||
On your computer:
|
On your computer:
|
||||||
|
|
||||||
1. [Install Miniconda](https://docs.anaconda.com/miniconda/#quick-command-line-install):
|
1. [Install Miniconda](https://docs.anaconda.com/miniconda/#quick-command-line-install):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/miniconda3
|
mkdir -p ~/miniconda3
|
||||||
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
|
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
|
||||||
@@ -21,29 +21,34 @@ rm ~/miniconda3/miniconda.sh
|
|||||||
2. Restart shell or `source ~/.bashrc`
|
2. Restart shell or `source ~/.bashrc`
|
||||||
|
|
||||||
3. Create and activate a fresh conda environment for lerobot
|
3. Create and activate a fresh conda environment for lerobot
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda create -y -n lerobot python=3.10 && conda activate lerobot
|
conda create -y -n lerobot python=3.10 && conda activate lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Clone LeRobot:
|
4. Clone LeRobot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/huggingface/lerobot.git ~/lerobot
|
git clone https://github.com/huggingface/lerobot.git ~/lerobot
|
||||||
```
|
```
|
||||||
|
|
||||||
5. When using `miniconda`, install `ffmpeg` in your environment:
|
5. When using `miniconda`, install `ffmpeg` in your environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda install ffmpeg -c conda-forge
|
conda install ffmpeg -c conda-forge
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Install LeRobot with dependencies for the Aloha motors (dynamixel) and cameras (intelrealsense):
|
6. Install LeRobot with dependencies for the Aloha motors (dynamixel) and cameras (intelrealsense):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/lerobot && pip install -e ".[dynamixel, intelrealsense]"
|
cd ~/lerobot && pip install -e ".[dynamixel, intelrealsense]"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Teleoperate
|
## Teleoperate
|
||||||
|
|
||||||
**/!\ FOR SAFETY, READ THIS /!\**
|
\*\*/!\ FOR SAFETY, READ THIS /!\*\*
|
||||||
Teleoperation consists in manually operating the leader arms to move the follower arms. Importantly:
|
Teleoperation consists in manually operating the leader arms to move the follower arms. Importantly:
|
||||||
|
|
||||||
1. Make sure your leader arms are in the same position as the follower arms, so that the follower arms don't move too fast to match the leader arms,
|
1. Make sure your leader arms are in the same position as the follower arms, so that the follower arms don't move too fast to match the leader arms,
|
||||||
2. Our code assumes that your robot has been assembled following Trossen Robotics instructions. This allows us to skip calibration, as we use the pre-defined calibration files in `.cache/calibration/aloha_default`. If you replace a motor, make sure you follow the exact instructions from Trossen Robotics.
|
2. Our code assumes that your robot has been assembled following Trossen Robotics instructions. This allows us to skip calibration, as we use the pre-defined calibration files in `.cache/calibration/aloha_default`. If you replace a motor, make sure you follow the exact instructions from Trossen Robotics.
|
||||||
|
|
||||||
@@ -59,6 +64,7 @@ python lerobot/scripts/control_robot.py \
|
|||||||
```
|
```
|
||||||
|
|
||||||
By adding `--robot.max_relative_target=5`, we override the default value for `max_relative_target` defined in [`AlohaRobotConfig`](lerobot/robot_devices/robots/configs.py). It is expected to be `5` to limit the magnitude of the movement for more safety, but the teleoperation won't be smooth. When you feel confident, you can disable this limit by adding `--robot.max_relative_target=null` to the command line:
|
By adding `--robot.max_relative_target=5`, we override the default value for `max_relative_target` defined in [`AlohaRobotConfig`](lerobot/robot_devices/robots/configs.py). It is expected to be `5` to limit the magnitude of the movement for more safety, but the teleoperation won't be smooth. When you feel confident, you can disable this limit by adding `--robot.max_relative_target=null` to the command line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=aloha \
|
--robot.type=aloha \
|
||||||
@@ -71,17 +77,20 @@ python lerobot/scripts/control_robot.py \
|
|||||||
Once you're familiar with teleoperation, you can record your first dataset with Aloha.
|
Once you're familiar with teleoperation, you can record your first dataset with Aloha.
|
||||||
|
|
||||||
If you want to use the Hugging Face hub features for uploading your dataset and you haven't previously done it, make sure you've logged in using a write-access token, which can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens):
|
If you want to use the Hugging Face hub features for uploading your dataset and you haven't previously done it, make sure you've logged in using a write-access token, which can be generated from the [Hugging Face settings](https://huggingface.co/settings/tokens):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
huggingface-cli login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
|
||||||
```
|
```
|
||||||
|
|
||||||
Store your Hugging Face repository name in a variable to run these commands:
|
Store your Hugging Face repository name in a variable to run these commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HF_USER=$(huggingface-cli whoami | head -n 1)
|
HF_USER=$(huggingface-cli whoami | head -n 1)
|
||||||
echo $HF_USER
|
echo $HF_USER
|
||||||
```
|
```
|
||||||
|
|
||||||
Record 2 episodes and upload your dataset to the hub:
|
Record 2 episodes and upload your dataset to the hub:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=aloha \
|
--robot.type=aloha \
|
||||||
@@ -101,11 +110,13 @@ python lerobot/scripts/control_robot.py \
|
|||||||
## Visualize a dataset
|
## Visualize a dataset
|
||||||
|
|
||||||
If you uploaded your dataset to the hub with `--control.push_to_hub=true`, you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id given by:
|
If you uploaded your dataset to the hub with `--control.push_to_hub=true`, you can [visualize your dataset online](https://huggingface.co/spaces/lerobot/visualize_dataset) by copy pasting your repo id given by:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo ${HF_USER}/aloha_test
|
echo ${HF_USER}/aloha_test
|
||||||
```
|
```
|
||||||
|
|
||||||
If you didn't upload with `--control.push_to_hub=false`, you can also visualize it locally with:
|
If you didn't upload with `--control.push_to_hub=false`, you can also visualize it locally with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.visualize_dataset_html \
|
python -m lerobot.scripts.visualize_dataset_html \
|
||||||
--repo-id ${HF_USER}/aloha_test
|
--repo-id ${HF_USER}/aloha_test
|
||||||
@@ -113,10 +124,11 @@ python -m lerobot.scripts.visualize_dataset_html \
|
|||||||
|
|
||||||
## Replay an episode
|
## Replay an episode
|
||||||
|
|
||||||
**/!\ FOR SAFETY, READ THIS /!\**
|
\*\*/!\ FOR SAFETY, READ THIS /!\*\*
|
||||||
Replay consists in automatically replaying the sequence of actions (i.e. goal positions for your motors) recorded in a given dataset episode. Make sure the current initial position of your robot is similar to the one in your episode, so that your follower arms don't move too fast to go to the first goal positions. For safety, you might want to add `--robot.max_relative_target=5` to your command line as explained above.
|
Replay consists in automatically replaying the sequence of actions (i.e. goal positions for your motors) recorded in a given dataset episode. Make sure the current initial position of your robot is similar to the one in your episode, so that your follower arms don't move too fast to go to the first goal positions. For safety, you might want to add `--robot.max_relative_target=5` to your command line as explained above.
|
||||||
|
|
||||||
Now try to replay the first episode on your robot:
|
Now try to replay the first episode on your robot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=aloha \
|
--robot.type=aloha \
|
||||||
@@ -130,6 +142,7 @@ python lerobot/scripts/control_robot.py \
|
|||||||
## Train a policy
|
## Train a policy
|
||||||
|
|
||||||
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
To train a policy to control your robot, use the [`python -m lerobot.scripts.train`](../src/lerobot/scripts/train.py) script. A few arguments are required. Here is an example command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m lerobot.scripts.train \
|
python -m lerobot.scripts.train \
|
||||||
--dataset.repo_id=${HF_USER}/aloha_test \
|
--dataset.repo_id=${HF_USER}/aloha_test \
|
||||||
@@ -141,10 +154,11 @@ python -m lerobot.scripts.train \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Let's explain it:
|
Let's explain it:
|
||||||
|
|
||||||
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/aloha_test`.
|
1. We provided the dataset as argument with `--dataset.repo_id=${HF_USER}/aloha_test`.
|
||||||
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
2. We provided the policy with `policy.type=act`. This loads configurations from [`configuration_act.py`](../src/lerobot/policies/act/configuration_act.py). Importantly, this policy will automatically adapt to the number of motor states, motor actions and cameras of your robot (e.g. `laptop` and `phone`) which have been saved in your dataset.
|
||||||
4. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
3. We provided `policy.device=cuda` since we are training on a Nvidia GPU, but you could use `policy.device=mps` to train on Apple silicon.
|
||||||
5. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
4. We provided `wandb.enable=true` to use [Weights and Biases](https://docs.wandb.ai/quickstart) for visualizing training plots. This is optional but if you use it, make sure you are logged in by running `wandb login`.
|
||||||
|
|
||||||
For more information on the `train` script see the previous tutorial: [`examples/4_train_policy_with_script.md`](../examples/4_train_policy_with_script.md)
|
For more information on the `train` script see the previous tutorial: [`examples/4_train_policy_with_script.md`](../examples/4_train_policy_with_script.md)
|
||||||
|
|
||||||
@@ -153,6 +167,7 @@ Training should take several hours. You will find checkpoints in `outputs/train/
|
|||||||
## Evaluate your policy
|
## Evaluate your policy
|
||||||
|
|
||||||
You can use the `record` function from [`lerobot/scripts/control_robot.py`](../src/lerobot/scripts/control_robot.py) but with a policy checkpoint as input. For instance, run this command to record 10 evaluation episodes:
|
You can use the `record` function from [`lerobot/scripts/control_robot.py`](../src/lerobot/scripts/control_robot.py) but with a policy checkpoint as input. For instance, run this command to record 10 evaluation episodes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python lerobot/scripts/control_robot.py \
|
python lerobot/scripts/control_robot.py \
|
||||||
--robot.type=aloha \
|
--robot.type=aloha \
|
||||||
@@ -171,7 +186,8 @@ python lerobot/scripts/control_robot.py \
|
|||||||
```
|
```
|
||||||
|
|
||||||
As you can see, it's almost the same command as previously used to record your training dataset. Two things changed:
|
As you can see, it's almost the same command as previously used to record your training dataset. Two things changed:
|
||||||
1. There is an additional `--control.policy.path` argument which indicates the path to your policy checkpoint with (e.g. `outputs/train/eval_act_aloha_test/checkpoints/last/pretrained_model`). You can also use the model repository if you uploaded a model checkpoint to the hub (e.g. `${HF_USER}/act_aloha_test`).
|
|
||||||
|
1. There is an additional `--control.policy.path` argument which indicates the path to your policy checkpoint with (e.g. `outputs/train/eval_act_aloha_test/checkpoints/last/pretrained_model`). You can also use the model repository if you uploaded a model checkpoint to the hub (e.g. `${HF_USER}/act_aloha_test`).
|
||||||
2. The name of dataset begins by `eval` to reflect that you are running inference (e.g. `${HF_USER}/eval_act_aloha_test`).
|
2. The name of dataset begins by `eval` to reflect that you are running inference (e.g. `${HF_USER}/eval_act_aloha_test`).
|
||||||
3. We use `--control.num_image_writer_processes=1` instead of the default value (`0`). On our computer, using a dedicated process to write images from the 4 cameras on disk allows to reach constant 30 fps during inference. Feel free to explore different values for `--control.num_image_writer_processes`.
|
3. We use `--control.num_image_writer_processes=1` instead of the default value (`0`). On our computer, using a dedicated process to write images from the 4 cameras on disk allows to reach constant 30 fps during inference. Feel free to explore different values for `--control.num_image_writer_processes`.
|
||||||
|
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import einops
|
import einops
|
||||||
import gymnasium as gym
|
import gymnasium as gym
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Tuple
|
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import torch
|
import torch
|
||||||
@@ -162,10 +161,10 @@ def get_image_from_lerobot_dataset(dataset: LeRobotDataset):
|
|||||||
|
|
||||||
def convert_lerobot_dataset_to_cropper_lerobot_dataset(
|
def convert_lerobot_dataset_to_cropper_lerobot_dataset(
|
||||||
original_dataset: LeRobotDataset,
|
original_dataset: LeRobotDataset,
|
||||||
crop_params_dict: Dict[str, Tuple[int, int, int, int]],
|
crop_params_dict: dict[str, tuple[int, int, int, int]],
|
||||||
new_repo_id: str,
|
new_repo_id: str,
|
||||||
new_dataset_root: str,
|
new_dataset_root: str,
|
||||||
resize_size: Tuple[int, int] = (128, 128),
|
resize_size: tuple[int, int] = (128, 128),
|
||||||
push_to_hub: bool = False,
|
push_to_hub: bool = False,
|
||||||
task: str = "",
|
task: str = "",
|
||||||
) -> LeRobotDataset:
|
) -> LeRobotDataset:
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ Example:
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from collections.abc import Sequence
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Annotated, Any, Sequence
|
from typing import Annotated, Any
|
||||||
|
|
||||||
import gymnasium as gym
|
import gymnasium as gym
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|||||||
@@ -87,12 +87,10 @@ from lerobot.utils.process import ProcessSignalHandler
|
|||||||
from lerobot.utils.random_utils import set_seed
|
from lerobot.utils.random_utils import set_seed
|
||||||
from lerobot.utils.train_utils import (
|
from lerobot.utils.train_utils import (
|
||||||
get_step_checkpoint_dir,
|
get_step_checkpoint_dir,
|
||||||
|
load_training_state as utils_load_training_state,
|
||||||
save_checkpoint,
|
save_checkpoint,
|
||||||
update_last_checkpoint,
|
update_last_checkpoint,
|
||||||
)
|
)
|
||||||
from lerobot.utils.train_utils import (
|
|
||||||
load_training_state as utils_load_training_state,
|
|
||||||
)
|
|
||||||
from lerobot.utils.transition import move_state_dict_to_device, move_transition_to_device
|
from lerobot.utils.transition import move_state_dict_to_device, move_transition_to_device
|
||||||
from lerobot.utils.utils import (
|
from lerobot.utils.utils import (
|
||||||
format_big_number,
|
format_big_number,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ import logging
|
|||||||
import pickle # nosec
|
import pickle # nosec
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
import grpc
|
import grpc
|
||||||
@@ -231,7 +232,7 @@ class RobotClient:
|
|||||||
def _aggregate_action_queues(
|
def _aggregate_action_queues(
|
||||||
self,
|
self,
|
||||||
incoming_actions: list[TimedAction],
|
incoming_actions: list[TimedAction],
|
||||||
aggregate_fn: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None,
|
aggregate_fn: Callable[[torch.Tensor, torch.Tensor], torch.Tensor] | None = None,
|
||||||
):
|
):
|
||||||
"""Finds the same timestep actions in the queue and aggregates them using the aggregate_fn"""
|
"""Finds the same timestep actions in the queue and aggregates them using the aggregate_fn"""
|
||||||
if aggregate_fn is None:
|
if aggregate_fn is None:
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ import argparse
|
|||||||
import gc
|
import gc
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import rerun as rr
|
import rerun as rr
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Deque, Dict, Optional
|
from typing import Deque
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class HomunculusArm(Teleoperator):
|
|||||||
self.n: int = n
|
self.n: int = n
|
||||||
self.alpha: float = 2 / (n + 1)
|
self.alpha: float = 2 / (n + 1)
|
||||||
# one deque *per joint* so we can inspect raw history if needed
|
# one deque *per joint* so we can inspect raw history if needed
|
||||||
self._buffers: Dict[str, Deque[int]] = {
|
self._buffers: dict[str, Deque[int]] = {
|
||||||
joint: deque(maxlen=n)
|
joint: deque(maxlen=n)
|
||||||
for joint in (
|
for joint in (
|
||||||
"shoulder_pitch",
|
"shoulder_pitch",
|
||||||
@@ -73,7 +73,7 @@ class HomunculusArm(Teleoperator):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
# running EMA value per joint – lazily initialised on first read
|
# running EMA value per joint – lazily initialised on first read
|
||||||
self._ema: Dict[str, Optional[float]] = dict.fromkeys(self._buffers)
|
self._ema: dict[str, float | None] = dict.fromkeys(self._buffers)
|
||||||
|
|
||||||
self._state: dict[str, float] | None = None
|
self._state: dict[str, float] | None = None
|
||||||
self.new_state_event = threading.Event()
|
self.new_state_event = threading.Event()
|
||||||
@@ -217,9 +217,9 @@ class HomunculusArm(Teleoperator):
|
|||||||
|
|
||||||
return normalized_values
|
return normalized_values
|
||||||
|
|
||||||
def _apply_ema(self, raw: Dict[str, int]) -> Dict[str, float]:
|
def _apply_ema(self, raw: dict[str, int]) -> dict[str, float]:
|
||||||
"""Update buffers & running EMA values; return smoothed dict."""
|
"""Update buffers & running EMA values; return smoothed dict."""
|
||||||
smoothed: Dict[str, float] = {}
|
smoothed: dict[str, float] = {}
|
||||||
for joint, value in raw.items():
|
for joint, value in raw.items():
|
||||||
# maintain raw history
|
# maintain raw history
|
||||||
self._buffers[joint].append(value)
|
self._buffers[joint].append(value)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Deque, Dict, Optional
|
from typing import Deque
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ class HomunculusGlove(Teleoperator):
|
|||||||
self.n: int = n
|
self.n: int = n
|
||||||
self.alpha: float = 2 / (n + 1)
|
self.alpha: float = 2 / (n + 1)
|
||||||
# one deque *per joint* so we can inspect raw history if needed
|
# one deque *per joint* so we can inspect raw history if needed
|
||||||
self._buffers: Dict[str, Deque[int]] = {joint: deque(maxlen=n) for joint in self.joints}
|
self._buffers: dict[str, Deque[int]] = {joint: deque(maxlen=n) for joint in self.joints}
|
||||||
# running EMA value per joint – lazily initialised on first read
|
# running EMA value per joint – lazily initialised on first read
|
||||||
self._ema: Dict[str, Optional[float]] = dict.fromkeys(self._buffers)
|
self._ema: dict[str, float | None] = dict.fromkeys(self._buffers)
|
||||||
|
|
||||||
self._state: dict[str, float] | None = None
|
self._state: dict[str, float] | None = None
|
||||||
self.new_state_event = threading.Event()
|
self.new_state_event = threading.Event()
|
||||||
@@ -248,9 +248,9 @@ class HomunculusGlove(Teleoperator):
|
|||||||
|
|
||||||
return normalized_values
|
return normalized_values
|
||||||
|
|
||||||
def _apply_ema(self, raw: Dict[str, int]) -> Dict[str, int]:
|
def _apply_ema(self, raw: dict[str, int]) -> dict[str, int]:
|
||||||
"""Update buffers & running EMA values; return smoothed dict as integers."""
|
"""Update buffers & running EMA values; return smoothed dict as integers."""
|
||||||
smoothed: Dict[str, int] = {}
|
smoothed: dict[str, int] = {}
|
||||||
for joint, value in raw.items():
|
for joint, value in raw.items():
|
||||||
# maintain raw history
|
# maintain raw history
|
||||||
self._buffers[joint].append(value)
|
self._buffers[joint].append(value)
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import builtins
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Type
|
from typing import Any
|
||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class Teleoperator(abc.ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Set these in ALL subclasses
|
# Set these in ALL subclasses
|
||||||
config_class: Type[TeleoperatorConfig]
|
config_class: builtins.type[TeleoperatorConfig]
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
def __init__(self, config: TeleoperatorConfig):
|
def __init__(self, config: TeleoperatorConfig):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
|
# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
|
||||||
# Doc / guide: https://huggingface.co/docs/hub/model-cards
|
# Doc / guide: https://huggingface.co/docs/hub/model-cards
|
||||||
{{ card_data }}
|
# prettier-ignore
|
||||||
|
{{card_data}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Model Card for {{ model_name | default("Model ID", true) }}
|
# Model Card for {{ model_name | default("Model ID", true) }}
|
||||||
@@ -53,7 +54,7 @@ python -m lerobot.scripts.train \
|
|||||||
--wandb.enable=true
|
--wandb.enable=true
|
||||||
```
|
```
|
||||||
|
|
||||||
*Writes checkpoints to `outputs/train/<desired_policy_repo_id>/checkpoints/`.*
|
_Writes checkpoints to `outputs/train/<desired_policy_repo_id>/checkpoints/`._
|
||||||
|
|
||||||
### Evaluate the policy/run inference
|
### Evaluate the policy/run inference
|
||||||
|
|
||||||
@@ -71,4 +72,4 @@ Prefix the dataset repo with **eval\_** and supply `--policy.path` pointing to a
|
|||||||
|
|
||||||
## Model Details
|
## Model Details
|
||||||
|
|
||||||
* **License:** {{ license | default("\[More Information Needed]", true) }}
|
- **License:** {{ license | default("\[More Information Needed]", true) }}
|
||||||
|
|||||||
@@ -46,11 +46,13 @@ class TimeBenchmark(ContextDecorator):
|
|||||||
|
|
||||||
benchmark = TimeBenchmark()
|
benchmark = TimeBenchmark()
|
||||||
|
|
||||||
|
|
||||||
def context_manager_example():
|
def context_manager_example():
|
||||||
with benchmark:
|
with benchmark:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
print(f"Block took {benchmark.result_ms:.2f} milliseconds")
|
print(f"Block took {benchmark.result_ms:.2f} milliseconds")
|
||||||
|
|
||||||
|
|
||||||
threads = []
|
threads = []
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
t1 = threading.Thread(target=context_manager_example)
|
t1 = threading.Thread(target=context_manager_example)
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
from collections.abc import Callable, Sequence
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Callable, Sequence, TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn.functional as F # noqa: N812
|
import torch.nn.functional as F # noqa: N812
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import builtins
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any, Type, TypeVar
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
from huggingface_hub import HfApi
|
from huggingface_hub import HfApi
|
||||||
from huggingface_hub.utils import validate_hf_hub_args
|
from huggingface_hub.utils import validate_hf_hub_args
|
||||||
@@ -85,7 +86,7 @@ class HubMixin:
|
|||||||
@classmethod
|
@classmethod
|
||||||
@validate_hf_hub_args
|
@validate_hf_hub_args
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
cls: Type[T],
|
cls: builtins.type[T],
|
||||||
pretrained_name_or_path: str | Path,
|
pretrained_name_or_path: str | Path,
|
||||||
*,
|
*,
|
||||||
force_download: bool = False,
|
force_download: bool = False,
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import random
|
import random
|
||||||
|
from collections.abc import Generator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Generator
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|||||||
@@ -185,10 +185,10 @@ def print_cuda_memory_usage():
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
# Also clear the cache if you want to fully release the memory
|
# Also clear the cache if you want to fully release the memory
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
print("Current GPU Memory Allocated: {:.2f} MB".format(torch.cuda.memory_allocated(0) / 1024**2))
|
print(f"Current GPU Memory Allocated: {torch.cuda.memory_allocated(0) / 1024**2:.2f} MB")
|
||||||
print("Maximum GPU Memory Allocated: {:.2f} MB".format(torch.cuda.max_memory_allocated(0) / 1024**2))
|
print(f"Maximum GPU Memory Allocated: {torch.cuda.max_memory_allocated(0) / 1024**2:.2f} MB")
|
||||||
print("Current GPU Memory Reserved: {:.2f} MB".format(torch.cuda.memory_reserved(0) / 1024**2))
|
print(f"Current GPU Memory Reserved: {torch.cuda.memory_reserved(0) / 1024**2:.2f} MB")
|
||||||
print("Maximum GPU Memory Reserved: {:.2f} MB".format(torch.cuda.max_memory_reserved(0) / 1024**2))
|
print(f"Maximum GPU Memory Reserved: {torch.cuda.max_memory_reserved(0) / 1024**2:.2f} MB")
|
||||||
|
|
||||||
|
|
||||||
def capture_timestamp_utc():
|
def capture_timestamp_utc():
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from collections.abc import Generator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import dynamixel_sdk as dxl
|
import dynamixel_sdk as dxl
|
||||||
import serial
|
import serial
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import scservo_sdk as scs
|
import scservo_sdk as scs
|
||||||
import serial
|
import serial
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Generator
|
from collections.abc import Generator
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Generator
|
from collections.abc import Generator
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|||||||
Reference in New Issue
Block a user