# Project export: MARC: Marker Actuated Robotic Controller

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Meet MARC, the robot that turns text prompts into crisp drawings. In minutes, MARC uses LLM & LinAlg-based framework to drive a robot arm, ready for any setting where ink, motion, & imagination meet.
- Devpost: https://devpost.com/software/marc-marker-actuated-robotic-controller
- GitHub: https://github.com/yyardi/cal-hacks-marc
- Video: https://www.youtube.com/embed/qy0NNvkc4do?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (BitRobot Network: Best Robotics Hack - 2nd Place)
- Team: 2 GitHub contributor(s) — Yash Yardi (53 commits), OrangePainting (1 commits)

## Devpost submission (written by the team)

### Inspiration

Our inspiration behind MARC stems from wanting to close the gaps between imagination and creation. Today, turning a digital idea into something physical can take hours of calibration and setup, killing spontaneous creativity. High costs also make creative robotics inaccessible. For example, many precision robot arms cost over $10,000, putting them far beyond the reach of students, artists, and hobbyists. And perhaps most importantly, over 285 million people with visual impairments are excluded from visual creation tools; while AI-generated art continues to evolve, those who can’t see it are still unable to feel what it creates. Our project aims to change that by making the process of turning ideas into tangible, touchable art faster, cheaper, and more inclusive.

### What it does

The system takes natural language text prompts and optional image references to generate original artwork, then automatically translates these digital creations into precise physical drawings executed by a robotic arm. Through advanced coordinate transformation and real-time motion control, MARC bridges the gap between AI-generated images and tangible art, delivering sub-2mm positioning accuracy as it brings digital designs to life on paper.

### How we built it

The system architecture consists of four integrated stages. First, AI generation uses a Stable Diffusion XL pipeline with SD 1.5 fallback to create artwork from text prompts. Next, smart vectorization converts PNG outputs to SVG format using Portrace and SVGpathtools while fitting the artwork into 15 cm x 17.3 cm page dimensions. The third stage handles precise coordinate transformation by detecting paper corners via camera vision to build a homography matrix that maps pixels to millimeters, then applies 2D affine transformation formulas to convert page coordinates into the robot's base frame. Finally, robotics execution is achieved through a 6-DOF SO-ARM 101 with degrees-mode control, a custom 3-link planar IK solver, and real-time joint angle streaming for smooth, accurate drawing movements.

### Challenges we ran into

We encountered three major technical challenges during development. First, inaccurate motors caused servo backlash, USB packet loss, and position error accumulation. We solved this by implementing a closed-loop inverse kinematics controller with real-time error correction, achieving sub-2mm accuracy. Second, integrating three frameworks with incompatible APIs (LeRobot using degrees, RustyPot using 0-1, and PySerial using bytes) cost us over 20 hours in rewrites. We built a thin abstraction layer under 200 lines, prototyped all three approaches in 6 hours, and selected LeRobot for its community support and degrees-native interface. Finally, marker physics issues like nib compression and inconsistent ink flow limited us to one marker shape. We designed a universal 3D-printed spring-loaded adapter with 5-18mm diameter compatibility that maintains consistent pressure while absorbing vibrations.

### Accomplishments we're proud of

We're incredibly proud of building a system that accurately translates drawing coordinates into precise robotic movements, achieving pinpoint accuracy within 2mm. Figuring out how to convert real-world positions into instructions the robot could understand was a major breakthrough, requiring us to use camera vision to map the physical paper space to the robot's coordinate system. Beyond the technical work, we're proud of handling the pressure and rapid problem-solving of our first hackathon, transforming an ambitious idea into a working system that takes a text prompt and produces a physical drawing on paper in minutes.

### What we learned

This project taught us invaluable lessons about collaboration and resourcefulness. We discovered the power of open-source communities, finding extensive documentation and ready-to-use tools like LeRobot that accelerated our development and saved us countless hours of building from scratch. We learned the importance of parallel experimentation, testing multiple approaches simultaneously rather than betting everything on a single solution, which proved crucial when we had to evaluate three different frameworks in just six hours. Most importantly, we realized that constant communication is essential in a fast-paced hackathon environment. Keeping everyone aligned on goals, progress, and challenges minimized confusion and prevented wasted effort from misunderstandings. Working under pressure taught us to balance ambitious technical goals with practical time constraints, and that success comes from both individual problem-solving and effective teamwork.

### What's next

Looking ahead, we plan to expand MARC's capabilities in several exciting directions. First, we'll add multi-color support to enable more vibrant and complex artwork. We're committed to open-source publishing, making our code and designs available for others to build upon and learn from. To make MARC more accessible, we'll develop a remote control system with a queue feature, allowing multiple users to submit drawing requests from anywhere. We also envision an adaptive learning system that improves drawing quality over time by learning from past movements and corrections. Finally, we want to transform MARC into a multi-modal fabrication platform that goes beyond drawing, potentially supporting different tools and creative outputs to bridge the gap between digital creativity and physical making.

## README (from the GitHub repository)

<p align="center">
  <img alt="LeRobot, Hugging Face Robotics Library" src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/lerobot-logo-thumbnail.png" width="100%">
  <br/>
  <br/>
</p>

<div align="center">

[![Tests](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml/badge.svg?branch=main)](https://github.com/huggingface/lerobot/actions/workflows/nightly.yml?query=branch%3Amain)
[![Python versions](https://img.shields.io/pypi/pyversions/lerobot)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/huggingface/lerobot/blob/main/LICENSE)
[![Status](https://img.shields.io/pypi/status/lerobot)](https://pypi.org/project/lerobot/)
[![Version](https://img.shields.io/pypi/v/lerobot)](https://pypi.org/project/lerobot/)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.1-ff69b4.svg)](https://github.com/huggingface/lerobot/blob/main/CODE_OF_CONDUCT.md)
[![Discord](https://dcbadge.vercel.app/api/server/C5P34WJ68S?style=flat)](https://discord.gg/s3KuuzsPFb)

<!-- [![Coverage](https://codecov.io/gh/huggingface/lerobot/branch/main/graph/badge.svg?token=TODO)](https://codecov.io/gh/huggingface/lerobot) -->

</div>

<h2 align="center">
    <p><a href="https://huggingface.co/docs/lerobot/hope_jr">
        Build Your Own HopeJR Robot!</a></p>
</h2>

<div align="center">
  <img
    src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/hope_jr/hopejr.png"
    alt="HopeJR robot"
    title="HopeJR robot"
    width="60%"
  />

  <p><strong>Meet HopeJR – A humanoid robot arm and hand for dexterous manipulation!</strong></p>
  <p>Control it with exoskeletons and gloves for precise hand movements.</p>
  <p>Perfect for advanced manipulation tasks! 🤖</p>

  <p><a href="https://huggingface.co/docs/lerobot/hope_jr">
      See the full HopeJR tutorial here.</a></p>
</div>

<br/>

<h2 align="center">
    <p><a href="https://huggingface.co/docs/lerobot/so101">
        Build Your Own SO-101 Robot!</a></p>
</h2>

<div align="center">
  <table>
    <tr>
      <td align="center"><img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/so101/so101.webp" alt="SO-101 follower arm" title="SO-101 follower arm" width="90%"/></td>
      <td align="center"><img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/so101/so101-leader.webp" alt="SO-101 leader arm" title="SO-101 leader arm" width="90%"/></td>
    </tr>
  </table>

  <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>Then sit back and watch your creation act autonomously! 🤯</p>

  <p><a href="https://huggingface.co/docs/lerobot/so101">
      See the full SO-101 tutorial here.</a></p>

  <p>Want to take it to the next level? Make your SO-101 mobile by building LeKiwi!</p>
  <p>Check out the <a href="https://huggingface.co/docs/lerobot/lekiwi">LeKiwi tutorial</a> and bring your robot to life on wheels.</p>

  <img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/lekiwi/kiwi.webp" alt="LeKiwi mobile robot" title="LeKiwi mobile robot" width="50%">
</div>

<br/>

<h3 align="center">
    <p>LeRobot: State-of-the-art AI for real-world robotics</p>
</h3>

---

🤗 LeRobot aims to provide models, datasets, and tools for real-world robotics in PyTorch. The goal is to lower the barrier to entry to robotics so that everyone can contribute and benefit from sharing datasets and pretrained models.

🤗 LeRobot contains state-of-the-art approaches that have been shown to transfer to the real-world with a focus on imitation learning and reinforcement learning.

🤗 LeRobot already provides a set of pretrained models, datasets with human collected demonstrations, and simulation environments to get started without assembling a robot. In the coming weeks, the plan is to add more and more support for real-world robotics on the most affordable and capable robots out there.

🤗 LeRobot hosts pretrained models and datasets on this Hugging Face community page: [huggingface.co/lerobot](https://huggingface.co/lerobot)

#### Examples of pretrained models on simulation environments

<table>
  <tr>
    <td><img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/gym/aloha_act.gif" width="100%" alt="ACT policy on ALOHA env"/></td>
    <td><img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/gym/simxarm_tdmpc.gif" width="100%" alt="TDMPC policy on SimXArm env"/></td>
    <td><img src="https://raw.githubusercontent.com/huggingface/lerobot/main/media/gym/pusht_diffusion.gif" width="100%" alt="Diffusion policy on PushT env"/></td>
  </tr>
  <tr>
    <td align="center">ACT policy on ALOHA env</td>
    <td align="center">TDMPC policy on SimXArm env</td>
    <td align="center">Diffusion policy on PushT env</td>
  </tr>
</table>

## Installation

LeRobot works with Python 3.10+ and PyTorch 2.2+.

### Environment Setup

Create a virtual environment with Python 3.10 and activate it, e.g. with [`miniforge`](https://conda-forge.org/download/):

```bash
conda create -y -n lerobot python=3.10
conda activate lerobot
```

When using `conda`, install `ffmpeg` in your environment:

```bash
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:
>
> - _[On any platform]_ Explicitly install `ffmpeg 7.X` using:
>
> ```bash
> 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 🤗

#### From Source

First, clone the repository and navigate into the directory:

```bash
git clone https://github.com/huggingface/lerobot.git
cd lerobot
```

Then, install the library in editable mode. This is useful if you plan to contribute to the code.

```bash
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:
> `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:

- [aloha](https://github.com/huggingface/gym-aloha)
- [xarm](https://github.com/huggingface/gym-xarm)
- [pusht](https://github.com/huggingface/gym-pusht)

For instance, to install 🤗 LeRobot with aloha and pusht, use:

```bash
pip install -e ".[aloha, pusht]"
```

### Installation from PyPI

**Core Library:**
Install the base package with:

```bash
pip install lerobot
```

_This installs only the default dependencies._

**Extra Features:**
To install additional functionality, use one of the following:

```bash
pip install 'lerobot[all]'          # All available features
pip install 'lerobot[aloha,pusht]'  # Specific features (Aloha & Pusht)
pip install 'lerobot[feetech]'      # Feetech motor support
```

_Replace `[...]` with your desired features._

**Available Tags:**
For a full list of optional dependencies, see:
https://pypi.org/project/lerobot/

### Weights & Biases

To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with

```bash
wandb login
```

(note: you will also need to enable WandB in the configuration. See below.)

### Visualize datasets

Check out [exa

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 405 recognized source files, 3765 KB.
- Hugging Face (technology) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- AI coding agent: GitHub Copilot — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 500)

```
.dockerignore
.gitattributes
.github/ISSUE_TEMPLATE/bug-report.yml
.github/PULL_REQUEST_TEMPLATE.md
.github/workflows/documentation-upload-pr.yml
.github/workflows/documentation.yml
.github/workflows/fast_tests.yml
.github/workflows/full_tests.yml
.github/workflows/nightly.yml
.github/workflows/quality.yml
.github/workflows/release.yml
.github/workflows/security.yml
.github/workflows/stale.yml
.github/workflows/unbound_deps_tests.yml
.gitignore
.pre-commit-config.yaml
benchmarks/video/benchmark.py
benchmarks/video/capture_camera_feed.py
benchmarks/video/README.md
benchmarks/video/run_video_benchmark.py
CODE_OF_CONDUCT.md
CONTRIBUTING.md
docker/Dockerfile.internal
docker/Dockerfile.user
docs-requirements.txt
docs/README.md
docs/source/_toctree.yml
docs/source/act.mdx
docs/source/async.mdx
docs/source/backwardcomp.mdx
docs/source/cameras.mdx
docs/source/contributing.md
docs/source/debug_processor_pipeline.mdx
docs/source/feetech.mdx
docs/source/hilserl_sim.mdx
docs/source/hilserl.mdx
docs/source/hope_jr.mdx
docs/source/il_robots.mdx
docs/source/il_sim.mdx
docs/source/implement_your_own_processor.mdx
docs/source/index.mdx
docs/source/installation.mdx
docs/source/integrate_hardware.mdx
docs/source/introduction_processors.mdx
docs/source/koch.mdx
docs/source/lekiwi.mdx
docs/source/lerobot-dataset-v3.mdx
docs/source/libero.mdx
docs/source/metaworld.mdx
docs/source/multi_gpu_training.mdx
docs/source/notebooks.mdx
docs/source/phone_teleop.mdx
docs/source/pi0.mdx
docs/source/pi05.mdx
docs/source/policy_act_README.md
docs/source/policy_diffusion_README.md
docs/source/policy_smolvla_README.md
docs/source/policy_tdmpc_README.md
docs/source/policy_vqbet_README.md
docs/source/porting_datasets_v3.mdx
docs/source/processors_robots_teleop.mdx
docs/source/reachy2.mdx
docs/source/smolvla.mdx
docs/source/so100.mdx
docs/source/so101.mdx
docs/source/using_dataset_tools.mdx
examples/backward_compatibility/replay.py
examples/dataset/load_lerobot_dataset.py
examples/dataset/use_dataset_image_transforms.py
examples/dataset/use_dataset_tools.py
examples/lekiwi/archived/krish_so101_controller.py
examples/lekiwi/archived/paintbot_examples.py
examples/lekiwi/archived/simple_svg_draw.py
examples/lekiwi/archived/slower_so101_yash_controller_3.py
examples/lekiwi/archived/svg_path_loading.py
examples/lekiwi/archived/svg_to_robot.py
examples/lekiwi/evaluate.py
examples/lekiwi/paint_bot_examples_improved.py
examples/lekiwi/record.py
examples/lekiwi/replay.py
examples/lekiwi/teleoperate.py
examples/marc/calib/__init__.py
examples/marc/calib/detect_tags.py
examples/marc/calib/estimate_homography.py
examples/marc/calib/homography.py
examples/marc/calib/page_to_robot.py
examples/marc/calib/warp.py
examples/marc/constants.py
examples/marc/executor/__init__.py
examples/marc/executor/driver_api.py
examples/marc/executor/so100_driver.py
examples/marc/fetch_so101_urdf.py
examples/marc/planner/__init__.py
examples/marc/planner/color_quant.py
examples/marc/planner/make_plan.py
examples/marc/planner/order_paths.py
examples/marc/planner/sample_curves.py
examples/marc/planner/vector_planning.py
examples/marc/README.md
examples/marc/requirements.txt
examples/marc/run_draw_lerobot_ik.py
examples/marc/run_draw_test_square.py
examples/marc/run_prompt_to_robot.py
examples/marc/run_svg.py
examples/marc/vectorize/__init__.py
examples/marc/vectorize/generate.py
examples/marc/vectorize/potrace_wrap.py
examples/marc/vectorize/simplify_svg.py
examples/phone_to_so100/evaluate.py
examples/phone_to_so100/record.py
examples/phone_to_so100/replay.py
examples/phone_to_so100/teleoperate.py
examples/port_datasets/display_error_files.py
examples/port_datasets/port_droid.py
examples/port_datasets/slurm_aggregate_shards.py
examples/port_datasets/slurm_port_shards.py
examples/port_datasets/slurm_upload.py
examples/so100_to_so100_EE/evaluate.py
examples/so100_to_so100_EE/record.py
examples/so100_to_so100_EE/replay.py
[380 more files omitted for size]
```

### Dependencies

- examples/marc/requirements.txt: accelerate, cairosvg, diffusers, numpy, opencv-contrib-python, pillow, pupil-apriltags, svgpathtools, transformers, vpype
- pyproject.toml: accelerate@>=1.7.0,<2.0.0, accelerate@>=1.10.0,<2.0.0, av@>=15.0.0,<16.0.0, cmake@>=3.29.0.1,<4.2.0, datasets@>=4.0.0,<4.2.0, debugpy@>=1.8.1,<1.9.0, deepdiff@>=7.0.1,<9.0.0, diffusers@>=0.27.2,<0.36.0, draccus@==0.10.0, dynamixel-sdk@>=3.7.31,<3.9.0, einops@>=0.8.0,<0.9.0, feetech-servo-sdk@>=1.0.0,<2.0.0, grpcio@==1.73.1, grpcio-tools@==1.73.1, gym-aloha@>=0.1.2,<0.2.0, gym-hil@>=0.1.13,<0.2.0, gym-pusht@>=0.1.5,<0.2.0, gymnasium@>=1.1.1,<2.0.0, hebi-py@>=2.8.0,<2.12.0, hidapi@>=0.14.0,<0.15.0, huggingface-hub[hf-transfer,cli]@>=0.34.2,<0.36.0, imageio[ffmpeg]@>=2.34.0,<3.0.0, jsonlines@>=4.0.0,<5.0.0, lerobot[aloha], lerobot[async], lerobot[dev], lerobot[dynamixel], lerobot[feetech], lerobot[feetech], lerobot[gamepad], lerobot[grpcio-dep], lerobot[grpcio-dep], lerobot[grpcio-dep], lerobot[hilserl], lerobot[hopejr], lerobot[intelrealsense], lerobot[kinematics], lerobot[lekiwi], lerobot[libero], lerobot[metaworld], lerobot[phone], lerobot[pi], lerobot[placo-dep], lerobot[placo-dep], lerobot[pusht], lerobot[pygame-dep], lerobot[pygame-dep], lerobot[reachy2], lerobot[smolvla], lerobot[test], lerobot[transformers-dep], lerobot[transformers-dep], lerobot[transformers-dep], lerobot[video_benchmark], libero@@ git+https://github.com/huggingface/lerobot-libero.git@main#egg=libero, matplotlib@>=3.10.3,<4.0.0, metaworld@==3.0.0, mock-serial@>=0.0.1,<0.1.0, num2words@>=0.5.14,<0.6.0, opencv-python-headless@>=4.9.0,<4.13.0, packaging@>=24.2,<26.0, pandas@>=2.2.2,<2.4.0, placo@>=0.9.6,<0.10.0, pre-commit@>=3.7.0,<5.0.0, protobuf@==6.31.0, pygame@>=2.5.1,<2.7.0, pymunk@>=6.6.0,<7.0.0, pynput@>=1.7.7,<1.9.0, pyrealsense2@>=2.55.1.6486,<2.57.0, pyrealsense2-macosx@>=2.54,<2.55.0, pyserial@>=3.5,<4.0, pytest@>=8.1.0,<9.0.0, pytest-cov@>=5.0.0,<8.0.0, pytest-timeout@>=2.4.0,<3.0.0, pyzmq@>=26.2.1,<28.0.0, reachy2_sdk@>=1.0.14,<1.1.0, rerun-sdk@>=0.24.0,<0.27.0, safetensors@>=0.4.3,<1.0.0, scikit-image@>=0.23.2,<0.26.0, setuptools@>=71.0.0,<81.0.0, teleop@>=0.1.0,<0.2.0, termcolor@>=2.4.0,<4.0.0, torch@>=2.2.1,<2.8.0, torchcodec@>=0.2.1,<0.6.0, torchvision@>=0.21.0,<0.23.0, transformers@>=4.53.0,<5.0.0, transformers@@ git+https://github.com/huggingface/transformers.git@fix/lerobot_openpi, wandb@>=0.20.0,<0.22.0

### Recent commits (newest first)

- Merge pull request #27 from yyardi/codex/fix-inverse-kinematics-for-paint_bot_examples_improved-ubur49
- Merge branch 'main' into codex/fix-inverse-kinematics-for-paint_bot_examples_improved-ubur49
- Allow paint bot star command to auto-download URDF
- Merge pull request #26 from yyardi/codex/fix-inverse-kinematics-for-paint_bot_examples_improved
- Improve paint bot IK robustness and add drawing CLI
- Merge pull request #25 from yyardi/codex/fix-svg-loading-and-drawing-placement
- Merge branch 'main' into codex/fix-svg-loading-and-drawing-placement
- Restore automatic follower calibration
- Revert "Krish Konda Added Rudimentary Drawing"
- Merge pull request #24 from yyardi/codex/fix-svg-loading-and-drawing-placement
- Merge branch 'main' into codex/fix-svg-loading-and-drawing-placement
- Auto-calibrate SO100 follower when offsets missing
- Merge pull request #23 from yyardi/codex/fix-svg-loading-and-drawing-placement-lh10ve
- Merge branch 'main' into codex/fix-svg-loading-and-drawing-placement-lh10ve
- Ensure SO101 downloader grabs mesh assets
- Merge pull request #22 from yyardi/codex/fix-svg-loading-and-drawing-placement
- Add helper to download the SO101 URDF
- Merge pull request #21 from yyardi/codex/fix-svg-loading-and-drawing-placement-n3vsmj
- Merge branch 'main' into codex/fix-svg-loading-and-drawing-placement-n3vsmj
- Allow reusing prompt-to-robot intermediates

## Key source files (fetched from GitHub, selected and truncated for size)

### CODE_OF_CONDUCT.md

```markdown
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or advances of
  any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
  without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[feedback@huggingface.co](mailto:feedback@huggingface.co).
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in vi
[truncated — 2521 more characters]
```

### CONTRIBUTING.md

```markdown
# How to contribute to 🤗 LeRobot?

Everyone is welcome to contribute, and we value everybody's contribution. Code
is thus not the only way to help the community. Answering questions, helping
others, reaching out and improving the documentations are immensely valuable to
the community.

It also helps us if you spread the word: reference the library from blog posts
on the awesome projects it made possible, shout out on Twitter when it has
helped you, or simply ⭐️ the repo to say "thank you".

Whichever way you choose to contribute, please be mindful to respect our
[code of conduct](https://github.com/huggingface/lerobot/blob/main/CODE_OF_CONDUCT.md).

## You can contribute in so many ways!

Some of the ways you can contribute to 🤗 LeRobot:

- Fixing outstanding issues with the existing code.
- Implementing new models, datasets or simulation environments.
- 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).

If you are not sure how to contribute or want to know the next features we working on, look on this project page: [LeRobot TODO](https://github.com/orgs/huggingface/projects/46)

## Submitting a new issue or feature request

Do your best to follow these guidelines when submitting an issue or a feature
request. It will make it easier for us to come back to you quickly and with good
feedback.

### Did you find a bug?

The 🤗 LeRobot library is robust and reliable thanks to the users who notify us of
the problems they encounter. So thank you for reporting an issue.

First, we would really appreciate it if you could **make sure the bug was not
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:

- 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
  less than 30s.
- The full traceback if an exception is raised.
- Attach any other additional information, like screenshots, you think may help.

### Do you want a new feature?

A good feature request addresses the following points:

1. Motivation first:

- 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.
- Is it related to something you would need for a project? We'd love to hear
  about it!
- Is it something you worked on and think could benefit the community?
  Awesome! Tell us what problem it solved for you.

2. Write a _paragraph_ describing the feature.
3. Provide a **code snippet** that demonstrates its future use.
4. In case this is related to a paper, please attach a link.
5. Attach any
[truncated — 8314 more characters]
```

### pyproject.toml

```
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project.urls]
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"
discord = "https://discord.gg/s3KuuzsPFb"

[project]
name = "lerobot"
version = "0.3.4"
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 = [
    { name = "Rémi Cadène", email = "re.cadene@gmail.com" },
    { name = "Simon Alibert", email = "alibert.sim@gmail.com" },
    { name = "Alexander Soare", email = "alexander.soare159@gmail.com" },
    { name = "Quentin Gallouédec", email = "quentin.gallouedec@ec-lyon.fr" },
    { 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" },
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: Apache Software License",
    "Programming Language :: Python :: 3.10",
    "Topic :: Software Development :: Build Tools",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
]
keywords = ["lerobot", "huggingface", "robotics",  "machine learning", "artificial intelligence"]

dependencies = [

    # Hugging Face dependencies
    "datasets>=4.0.0,<4.2.0",
    "diffusers>=0.27.2,<0.36.0",
    "huggingface-hub[hf-transfer,cli]>=0.34.2,<0.36.0",
    "accelerate>=1.10.0,<2.0.0",

    # Core dependencies
    "setuptools>=71.0.0,<81.0.0",
    "cmake>=3.29.0.1,<4.2.0",
    "einops>=0.8.0,<0.9.0",
    "opencv-python-headless>=4.9.0,<4.13.0",
    "av>=15.0.0,<16.0.0",
    "jsonlines>=4.0.0,<5.0.0",
    "packaging>=24.2,<26.0",
    "pynput>=1.7.7,<1.9.0",
    "pyserial>=3.5,<4.0",
    "wandb>=0.20.0,<0.22.0", # TODO: Bumb dependency (compatible with protobuf)

    "torch>=2.2.1,<2.8.0", # TODO: Bumb dependency
    "torchcodec>=0.2.1,<0.6.0; 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')", # TODO: Bumb dependency
    "torchvision>=0.21.0,<0.23.0", # TODO: Bumb dependency

    "draccus==0.10.0", # TODO: Remove ==
    "gymnasium>=1.1.1,<2.0.0",
    "rerun-sdk>=0.24.0,<0.27.0",

    # Support dependencies
    "deepdiff>=7.0.1,<9.0.0",
    "imageio[ffmpeg]>=2.34.0,<3.0.0",
    "termcolor>=2.4.0,<4.0.0",
]

# Optional dependencies
[project.optional-dependencies]

# Common
pygame-dep = ["pygame>=2.5.1,<2.7.0"]
placo-dep = ["placo>=0.9.6,<0.10.0"]
transformers-dep = ["transformers>=4.53.0,<5.0.0"]
grpcio-dep = ["grpcio==1.73.1", "protobuf==6.31.0"] # TODO: Bumb dependency (compatible with wandb)

# Motors
feetech = ["feetech-servo-sdk>=1.0.0,<2.0.0"]
dynamixel = ["dynamixel-sdk>=3.7.31,<3.9.0"]

# Robots
gamepad = ["lerobot[pygame-dep]", "hidapi>=0.14.0,<0.15.0"]
hopejr = ["lerobot[feetech]", "lerobot[pygame-dep]"]
lekiwi = ["lerobot[feetech]", "pyzmq>=26.2.1,<28.0.0"]
reachy2 = ["reachy2_sdk>=1.0.14,<1.1.0"]
kinematics = ["lerobot[placo-dep]"]
intelrealsense = [
    "pyrealsense2>=2.55.1.6486,<2.57.0 ; sys_platform != 'darwin'",
    "pyrealsense2-macosx>=2.54,<2.55.0 ; sys_platform == 'darwin'",
]
phone = ["hebi-py>=2.8.0,<2.12.0", "teleop>=0.1.0,<0.2.0"]

# Policies
pi = ["transformers @ git+https://github.com/huggingface/transformers.git@fix/lerobot_openpi"]
smolvla = ["lerobot[transformers-dep]", "num2words>=0.5.14,<0.6.0", "accelerate>=1.7.0,<2.0.0", "safetensors>=0.4.3,<1.0.0"]
hilserl = ["lerobot[transformers-dep]", "gym-hil>=0.1.13,<0.2.0", "lerobot[grpcio-dep]", "lerobot[placo-dep]"]

# Features
async = ["lerobot[grpcio-dep]", "matplotlib>=3.10.3,<4.0.0"]

# Development
dev = ["pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools==1.73.1"]
test = ["pytest>=8.1.0,<9.0.0", "pytest-timeout>=2.4.0,<3.0.0", "pytest-cov>=5.0.0,<8.0.0", "mock-serial>=0.0.1,<0.1.0 ; sys_platform != 'win32'"]
video_benchmark = ["scikit-image>=0.23.2,<0.26.0", "pandas>=2.2.2,<2.4.0"]

# Simulation
aloha = ["gym-aloha>=0.1.2,<0.2.0"]
pusht = ["gym-pusht>=0.1.5,<0.2.0", "pymunk>=6.6.0,<7.0.0"] # TODO: Fix pymunk version in gym-pusht instead
libero = ["lerobot[transformers-dep]", "libero @ git+https://github.com/huggingface/lerobot-libero.git@main#egg=libero"]
metaworld = ["metaworld==3.0.0"]

# All
all = [
    "lerobot[dynamixel]",
    "lerobot[gamepad]",
    "lerobot[hopejr]",
    "lerobot[lekiwi]",
    "lerobot[reachy2]",
    "lerobot[kinematics]",
    "lerobot[intelrealsense]",
    "lerobot[pi]",
    "lerobot[smolvla]",
    "lerobot[hilserl]",
    "lerobot[async]",
    "lerobot[dev]",
    "lerobot[test]",
    "le
[truncated — 5264 more characters]
```

### examples/marc/requirements.txt

```
diffusers
transformers
accelerate
opencv-contrib-python
numpy
pillow
pupil-apriltags
vpype
cairosvg
svgpathtools

```

### .pre-commit-config.yaml

```yaml
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

default_language_version:
    python: python3.10

exclude: "tests/artifacts/.*\\.safetensors$"

repos:
  ##### Meta #####
  - repo: meta
    hooks:
      - id: check-useless-excludes
      - id: check-hooks-apply

   ##### General Code Quality & Formatting #####
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: check-added-large-files
        args: ['--maxkb=1024']
      - id: debug-statements
      - id: check-merge-conflict
      - id: check-case-conflict
      - id: check-yaml
      - id: check-toml
      - id: end-of-file-fixer
      - id: trailing-whitespace

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.14.1
    hooks:
      - id: ruff-format
      - id: ruff
        args: [--fix, --exit-non-zero-on-fix]

  - repo: https://github.com/adhtruong/mirrors-typos
    rev: v1.38.1
    hooks:
      - id: typos
        args: [--force-exclude]

  - repo: https://github.com/asottile/pyupgrade
    rev: v3.21.0
    hooks:
    -   id: pyupgrade
        args: [--py310-plus]

  ##### Markdown Quality #####
  - repo: https://github.com/rbubley/mirrors-prettier
    rev: v3.6.2
    hooks:
      - id: prettier
        name: Format Markdown with Prettier
        types_or: [markdown, mdx]
        args: [--prose-wrap=preserve]

  ##### Security #####
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.28.0
    hooks:
      - id: gitleaks

  - repo: https://github.com/woodruffw/zizmor-pre-commit
    rev: v1.15.2
    hooks:
      - id: zizmor

  - repo: https://github.com/PyCQA/bandit
    rev: 1.8.6
    hooks:
    - id: bandit
      args: ["-c", "pyproject.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.18.2
    hooks:
      - id: mypy
        args: [--config-file=pyproject.toml]
        exclude: ^(examples|benchmarks|tests)/

  ##### 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"]

```

### tests/__init__.py

```python
# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

```

### tests/test_available.py

```python
#!/usr/bin/env python

# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import importlib

import gymnasium as gym
import pytest

import lerobot
from lerobot.policies.act.modeling_act import ACTPolicy
from lerobot.policies.diffusion.modeling_diffusion import DiffusionPolicy
from lerobot.policies.tdmpc.modeling_tdmpc import TDMPCPolicy
from lerobot.policies.vqbet.modeling_vqbet import VQBeTPolicy
from tests.utils import require_env


@pytest.mark.parametrize("env_name, task_name", lerobot.env_task_pairs)
@require_env
def test_available_env_task(env_name: str, task_name: list):
    """
    This test verifies that all environments listed in `lerobot/__init__.py` can
    be successfully imported — if they're installed — and that their
    `available_tasks_per_env` are valid.
    """
    package_name = f"gym_{env_name}"
    importlib.import_module(package_name)
    gym_handle = f"{package_name}/{task_name}"
    assert gym_handle in gym.envs.registry, gym_handle


def test_available_policies():
    """
    This test verifies that the class attribute `name` for all policies is
    consistent with those listed in `lerobot/__init__.py`.
    """
    policy_classes = [ACTPolicy, DiffusionPolicy, TDMPCPolicy, VQBeTPolicy]
    policies = [pol_cls.name for pol_cls in policy_classes]
    assert set(policies) == set(lerobot.available_policies), policies


def test_print():
    print(lerobot.available_envs)
    print(lerobot.available_tasks_per_env)
    print(lerobot.available_datasets)
    print(lerobot.available_datasets_per_env)
    print(lerobot.available_real_world_datasets)
    print(lerobot.available_policies)
    print(lerobot.available_policies_per_env)

```

### tests/conftest.py

```python
#!/usr/bin/env python

# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import traceback

import pytest
from serial import SerialException

from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
from tests.utils import DEVICE

# Import fixture modules as plugins
pytest_plugins = [
    "tests.fixtures.dataset_factories",
    "tests.fixtures.files",
    "tests.fixtures.hub",
    "tests.fixtures.optimizers",
    "tests.plugins.reachy2_sdk",
]


def pytest_collection_finish():
    print(f"\nTesting with {DEVICE=}")


def _check_component_availability(component_type, available_components, make_component):
    """Generic helper to check if a hardware component is available"""
    if component_type not in available_components:
        raise ValueError(
            f"The {component_type} type is not valid. Expected one of these '{available_components}'"
        )

    try:
        component = make_component(component_type)
        component.connect()
        del component
        return True

    except Exception as e:
        print(f"\nA {component_type} is not available.")

        if isinstance(e, ModuleNotFoundError):
            print(f"\nInstall module '{e.name}'")
        elif isinstance(e, SerialException):
            print("\nNo physical device detected.")
        elif isinstance(e, ValueError) and "camera_index" in str(e):
            print("\nNo physical camera detected.")
        else:
            traceback.print_exc()

        return False


@pytest.fixture
def patch_builtins_input(monkeypatch):
    def print_text(text=None):
        if text is not None:
            print(text)

    monkeypatch.setattr("builtins.input", print_text)


@pytest.fixture
def policy_feature_factory():
    """PolicyFeature factory"""

    def _pf(ft: FeatureType, shape: tuple[int, ...]) -> PolicyFeature:
        return PolicyFeature(type=ft, shape=shape)

    return _pf


def assert_contract_is_typed(features: dict[PipelineFeatureType, dict[str, PolicyFeature]]) -> None:
    assert isinstance(features, dict)
    assert all(isinstance(k, PipelineFeatureType) for k in features)
    assert all(isinstance(v, dict) for v in features.values())
    assert all(all(isinstance(nk, str) for nk in v) for v in features.values())
    assert all(all(isinstance(nv, PolicyFeature) for nv in v.values()) for v in features.values())

```

### tests/test_control_robot.py

```python
#!/usr/bin/env python

# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from unittest.mock import patch

from lerobot.scripts.lerobot_calibrate import CalibrateConfig, calibrate
from lerobot.scripts.lerobot_record import DatasetRecordConfig, RecordConfig, record
from lerobot.scripts.lerobot_replay import DatasetReplayConfig, ReplayConfig, replay
from lerobot.scripts.lerobot_teleoperate import TeleoperateConfig, teleoperate
from tests.fixtures.constants import DUMMY_REPO_ID
from tests.mocks.mock_robot import MockRobotConfig
from tests.mocks.mock_teleop import MockTeleopConfig


def test_calibrate():
    robot_cfg = MockRobotConfig()
    cfg = CalibrateConfig(robot=robot_cfg)
    calibrate(cfg)


def test_teleoperate():
    robot_cfg = MockRobotConfig()
    teleop_cfg = MockTeleopConfig()
    cfg = TeleoperateConfig(
        robot=robot_cfg,
        teleop=teleop_cfg,
        teleop_time_s=0.1,
    )
    teleoperate(cfg)


def test_record_and_resume(tmp_path):
    robot_cfg = MockRobotConfig()
    teleop_cfg = MockTeleopConfig()
    dataset_cfg = DatasetRecordConfig(
        repo_id=DUMMY_REPO_ID,
        single_task="Dummy task",
        root=tmp_path / "record",
        num_episodes=1,
        episode_time_s=0.1,
        reset_time_s=0,
        push_to_hub=False,
    )
    cfg = RecordConfig(
        robot=robot_cfg,
        dataset=dataset_cfg,
        teleop=teleop_cfg,
        play_sounds=False,
    )

    dataset = record(cfg)

    assert dataset.fps == 30
    assert dataset.meta.total_episodes == dataset.num_episodes == 1
    assert dataset.meta.total_frames == dataset.num_frames == 3
    assert dataset.meta.total_tasks == 1

    cfg.resume = True
    # Mock the revision to prevent Hub calls during resume
    with (
        patch("lerobot.datasets.lerobot_dataset.get_safe_version") as mock_get_safe_version,
        patch("lerobot.datasets.lerobot_dataset.snapshot_download") as mock_snapshot_download,
    ):
        mock_get_safe_version.return_value = "v3.0"
        mock_snapshot_download.return_value = str(tmp_path / "record")
        dataset = record(cfg)

    assert dataset.meta.total_episodes == dataset.num_episodes == 2
    assert dataset.meta.total_frames == dataset.num_frames == 6
    assert dataset.meta.total_tasks == 1


def test_record_and_replay(tmp_path):
    robot_cfg = MockRobotConfig()
    teleop_cfg = MockTeleopConfig()
    record_dataset_cfg = DatasetRecordConfig(
        repo_id=DUMMY_REPO_ID,
        single_task="Dummy task",
        root=tmp_path / "record_and_replay",
        num_episodes=1,
        episode_time_s=0.1,
        push_to_hub=False,
    )
    record_cfg = RecordConfig(
        robot=robot_cfg,
        dataset=record_dataset_cfg,
        teleop=teleop_cfg,
        play_sounds=False,
    )
    replay_dataset_cfg = DatasetReplayConfig(
        repo_id=DUMMY_REPO_ID,
        episode=0,
        root=tmp_path / "record_and_replay",
    )
    replay_cfg = ReplayConfig(
        robot=robot_cfg,
        dataset=replay_dataset_cfg,
        play_sounds=False,
    )

    record(record_cfg)

    # Mock the revision to prevent Hub calls during replay
    with (
        patch("lerobot.datasets.lerobot_dataset.get_safe_version") as mock_get_safe_version,
        patch("lerobot.datasets.lerobot_dataset.snapshot_download") as mock_snapshot_download,
    ):
        mock_get_safe_version.return_value = "v3.0"
        mock_snapshot_download.return_value = str(tmp_path / "record_and_replay")
        replay(replay_cfg)

```

### tests/utils.py

```python
#!/usr/bin/env python

# Copyright 2024 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import platform
from functools import wraps

import pytest
import torch

from lerobot import available_cameras, available_motors, available_robots
from lerobot.utils.import_utils import is_package_available

DEVICE = os.environ.get("LEROBOT_TEST_DEVICE", "cuda") if torch.cuda.is_available() else "cpu"

TEST_ROBOT_TYPES = []
for robot_type in available_robots:
    TEST_ROBOT_TYPES += [(robot_type, True), (robot_type, False)]

TEST_CAMERA_TYPES = []
for camera_type in available_cameras:
    TEST_CAMERA_TYPES += [(camera_type, True), (camera_type, False)]

TEST_MOTOR_TYPES = []
for motor_type in available_motors:
    TEST_MOTOR_TYPES += [(motor_type, True), (motor_type, False)]

# Camera indices used for connecting physical cameras
OPENCV_CAMERA_INDEX = int(os.environ.get("LEROBOT_TEST_OPENCV_CAMERA_INDEX", 0))
INTELREALSENSE_SERIAL_NUMBER = int(os.environ.get("LEROBOT_TEST_INTELREALSENSE_SERIAL_NUMBER", 128422271614))

DYNAMIXEL_PORT = os.environ.get("LEROBOT_TEST_DYNAMIXEL_PORT", "/dev/tty.usbmodem575E0032081")
DYNAMIXEL_MOTORS = {
    "shoulder_pan": [1, "xl430-w250"],
    "shoulder_lift": [2, "xl430-w250"],
    "elbow_flex": [3, "xl330-m288"],
    "wrist_flex": [4, "xl330-m288"],
    "wrist_roll": [5, "xl330-m288"],
    "gripper": [6, "xl330-m288"],
}

FEETECH_PORT = os.environ.get("LEROBOT_TEST_FEETECH_PORT", "/dev/tty.usbmodem585A0080971")
FEETECH_MOTORS = {
    "shoulder_pan": [1, "sts3215"],
    "shoulder_lift": [2, "sts3215"],
    "elbow_flex": [3, "sts3215"],
    "wrist_flex": [4, "sts3215"],
    "wrist_roll": [5, "sts3215"],
    "gripper": [6, "sts3215"],
}


def require_x86_64_kernel(func):
    """
    Decorator that skips the test if plateform device is not an x86_64 cpu.
    """
    from functools import wraps

    @wraps(func)
    def wrapper(*args, **kwargs):
        if platform.machine() != "x86_64":
            pytest.skip("requires x86_64 plateform")
        return func(*args, **kwargs)

    return wrapper


def require_cpu(func):
    """
    Decorator that skips the test if device is not cpu.
    """
    from functools import wraps

    @wraps(func)
    def wrapper(*args, **kwargs):
        if DEVICE != "cpu":
            pytest.skip("requires cpu")
        return func(*args, **kwargs)

    return wrapper


def require_cuda(func):
    """
    Decorator that skips the test if cuda is not available.
    """
    from functools import wraps

    @wraps(func)
    def wrapper(*args, **kwargs):
        if not torch.cuda.is_available():
            pytest.skip("requires cuda")
        return func(*args, **kwargs)

    return wrapper


def require_env(func):
    """
    Decorator that skips the test if the required environment package is not installed.
    As it need 'env_name' in args, it also checks whether it is provided as an argument.
    If 'env_name' is None, this check is skipped.
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        # Determine if 'env_name' is provided and extract its value
        arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
        if "env_name" in arg_names:
            # Get the index of 'env_name' and retrieve the value from args
            index = arg_names.index("env_name")
            env_name = args[index] if len(args) > index else kwargs.get("env_name")
        else:
            raise ValueError("Function does not have 'env_name' as an argument.")

        # Perform the package check
        package_name = f"gym_{env_name}"
        if env_name is not None and not is_package_available(package_name):
            pytest.skip(f"gym-{env_name} not installed")

        return func(*args, **kwargs)

    return wrapper


def require_package_arg(func):
    """
    Decorator that skips the test if the required package is not installed.
    This is similar to `require_env` but more general in that it can check any package (not just environments).
    As it need 'required_packages' in args, it also checks whether it is provided as an argument.
    If 'required_packages' is None, this check is skipped.
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        # Determine if 'required_packages' is provided and extract its value
        arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
        if "required_packages" in arg_names:
            # Get the index of 'required_packages' and retrieve the value from args
            index = arg_names.index("required_packages")
            required_packages = args[index] if len(args) > index else kwargs.get("required_packages")
        else:
            raise ValueError("Function does not have 'required_packages' as an argument.")

        if required_packages is None:
            return func(*args, **kwargs)

        # Perform the package check
        for package in required_packages:
            if not is_package_available(package):
                pytest.skip(f"{package} not installed")

        return func(*args, **kwargs)

    return wrapper


def require_package(package_name):
    """
    Decorator that skips the test if the specified package is not installed.
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if not is_package_available(package_name):
                pytest.skip(f"{package_name} not installed")
            return func(*args, **kwargs)

        re
[truncated — 35 more characters]
```

[415 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]