From 87528186c03124c641b8452fe8215046cafdbd3e Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Sun, 12 Apr 2026 11:27:59 +0200 Subject: [PATCH] address minor review comments --- docs/source/installation.mdx | 32 ++++++++++----------- pyproject.toml | 2 +- src/lerobot/__init__.py | 10 +++---- src/lerobot/envs/__init__.py | 9 +++--- src/lerobot/motors/dynamixel/dynamixel.py | 28 ++++++++---------- src/lerobot/motors/feetech/feetech.py | 28 ++++++++---------- src/lerobot/scripts/lerobot_calibrate.py | 2 ++ src/lerobot/scripts/lerobot_dataset_viz.py | 2 ++ src/lerobot/scripts/lerobot_edit_dataset.py | 2 ++ src/lerobot/scripts/lerobot_eval.py | 3 ++ src/lerobot/scripts/lerobot_record.py | 2 ++ src/lerobot/scripts/lerobot_replay.py | 2 ++ src/lerobot/scripts/lerobot_teleoperate.py | 2 ++ src/lerobot/scripts/lerobot_train.py | 5 ++++ tests/mocks/mock_dynamixel.py | 19 ++++++++++-- tests/mocks/mock_feetech.py | 18 +++++++++++- uv.lock | 32 ++++++++++----------- 17 files changed, 120 insertions(+), 78 deletions(-) diff --git a/docs/source/installation.mdx b/docs/source/installation.mdx index 246e6415b..d3a45634d 100644 --- a/docs/source/installation.mdx +++ b/docs/source/installation.mdx @@ -133,16 +133,16 @@ Then, install the library in editable mode. This is useful if you plan to contri ```bash -pip install -e ".[robot]" # For real robot workflows (recording, replaying) -pip install -e ".[training]" # For training policies -pip install -e ".[all]" # Everything (all policies, envs, hardware, dev tools) +pip install -e ".[core_scripts]" # For robot workflows (recording, replaying, calibrate) +pip install -e ".[training]" # For training policies +pip install -e ".[all]" # Everything (all policies, envs, hardware, dev tools) ``` ```bash -uv pip install -e ".[robot]" # For real robot workflows (recording, replaying) -uv pip install -e ".[training]" # For training policies -uv pip install -e ".[all]" # Everything (all policies, envs, hardware, dev tools) +uv pip install -e ".[core_scripts]" # For robot workflows (recording, replaying, calibrate) +uv pip install -e ".[training]" # For training policies +uv pip install -e ".[all]" # Everything (all policies, envs, hardware, dev tools) ``` @@ -183,17 +183,17 @@ LeRobot provides **feature-scoped extras** that map to common workflows. If you **Composite Extras** combine feature extras for common CLI scripts: -| Extra | Includes | Typical use case | -| ------------- | ------------------------------ | ------------------------------------------------------- | -| `robot` | `dataset` + `hardware` + `viz` | `lerobot-record`, `lerobot-replay`, `lerobot-calibrate` | -| `evaluation` | `av` | `lerobot-eval` (add policy + env extras as needed) | -| `dataset_viz` | `dataset` + `viz` | `lerobot-dataset-viz`, `lerobot-imgtransform-viz` | +| Extra | Includes | Typical use case | +| -------------- | ------------------------------ | ------------------------------------------------------- | +| `core_scripts` | `dataset` + `hardware` + `viz` | `lerobot-record`, `lerobot-replay`, `lerobot-calibrate` | +| `evaluation` | `av` | `lerobot-eval` (add policy + env extras as needed) | +| `dataset_viz` | `dataset` + `viz` | `lerobot-dataset-viz`, `lerobot-imgtransform-viz` | ```bash -pip install 'lerobot[robot]' # Record, replay, calibrate -pip install 'lerobot[training]' # Train policies -pip install 'lerobot[robot,training]' # Record + train -pip install 'lerobot[all]' # Everything +pip install 'lerobot[core_scripts]' # Record, replay, calibrate +pip install 'lerobot[training]' # Train policies +pip install 'lerobot[core_scripts,training]' # Record + train +pip install 'lerobot[all]' # Everything ``` **Policy, environment, and hardware extras** are still available for specific dependencies: @@ -206,7 +206,7 @@ pip install 'lerobot[aloha,pusht]' # Simulation environments pip install 'lerobot[feetech]' # Feetech motor support ``` -_Multiple extras can be combined (e.g., `.[robot,pi,pusht]`). For a full list of available extras, refer to `pyproject.toml`._ +_Multiple extras can be combined (e.g., `.[core_scripts,pi,pusht]`). For a full list of available extras, refer to `pyproject.toml`._ ### Troubleshooting diff --git a/pyproject.toml b/pyproject.toml index b1268d368..3e6834d74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,7 +118,7 @@ build = [ # ── User-facing composite extras (map to CLI scripts) ───── # lerobot-record, lerobot-replay, lerobot-calibrate, lerobot-teleoperate, etc. -robot = ["lerobot[dataset]", "lerobot[hardware]", "lerobot[viz]"] +core_scripts = ["lerobot[dataset]", "lerobot[hardware]", "lerobot[viz]"] # lerobot-eval -- base evaluation framework. You also need the policy's extra (e.g., lerobot[pi]) # and the environment's extra (e.g., lerobot[pusht]) if evaluating in simulation. evaluation = ["lerobot[av-dep]"] diff --git a/src/lerobot/__init__.py b/src/lerobot/__init__.py index 4adac5d56..070d6d45e 100644 --- a/src/lerobot/__init__.py +++ b/src/lerobot/__init__.py @@ -24,11 +24,11 @@ model and dataset sharing. The base install is intentionally lightweight. Feature-specific dependencies are gated behind optional extras:: - pip install 'lerobot[dataset]' # dataset loading & creation - pip install 'lerobot[training]' # training loop + wandb - pip install 'lerobot[hardware]' # real robot control - pip install 'lerobot[robot]' # dataset + hardware + viz (recording) - pip install 'lerobot[all]' # everything + pip install 'lerobot[dataset]' # dataset loading & creation + pip install 'lerobot[training]' # training loop + wandb + pip install 'lerobot[hardware]' # real robot control + pip install 'lerobot[core_scripts]' # dataset + hardware + viz (record, replay, calibrate, etc.) + pip install 'lerobot[all]' # everything """ from lerobot.__version__ import __version__ diff --git a/src/lerobot/envs/__init__.py b/src/lerobot/envs/__init__.py index 730207d6c..277fd04f4 100644 --- a/src/lerobot/envs/__init__.py +++ b/src/lerobot/envs/__init__.py @@ -13,11 +13,10 @@ # limitations under the License. # NOTE: gymnasium is currently a core dependency but is a candidate for moving to an -# optional extra in the future. This guard is here to ensure a clear error message -# if/when that transition happens. -from lerobot.utils.import_utils import require_package - -require_package("gymnasium", extra="evaluation", import_name="gymnasium") +# optional extra in the future. When that transition happens, uncomment the guard below +# and update the extra name to the one that will contain gymnasium. +# from lerobot.utils.import_utils import require_package +# require_package("gymnasium", extra="", import_name="gymnasium") from .configs import AlohaEnv, EnvConfig, HILSerlRobotEnvConfig, HubEnvConfig, PushtEnv from .factory import make_env, make_env_config, make_env_pre_post_processors diff --git a/src/lerobot/motors/dynamixel/dynamixel.py b/src/lerobot/motors/dynamixel/dynamixel.py index 3e5a08135..4502bd668 100644 --- a/src/lerobot/motors/dynamixel/dynamixel.py +++ b/src/lerobot/motors/dynamixel/dynamixel.py @@ -90,21 +90,6 @@ class TorqueMode(Enum): DISABLED = 0 -def _split_into_byte_chunks(value: int, length: int) -> list[int]: - if length == 1: - data = [value] - elif length == 2: - data = [dxl.DXL_LOBYTE(value), dxl.DXL_HIBYTE(value)] - elif length == 4: - data = [ - dxl.DXL_LOBYTE(dxl.DXL_LOWORD(value)), - dxl.DXL_HIBYTE(dxl.DXL_LOWORD(value)), - dxl.DXL_LOBYTE(dxl.DXL_HIWORD(value)), - dxl.DXL_HIBYTE(dxl.DXL_HIWORD(value)), - ] - return data - - class DynamixelMotorsBus(SerialMotorsBus): """ The Dynamixel implementation for a MotorsBus. It relies on the python dynamixel sdk to communicate with @@ -249,7 +234,18 @@ class DynamixelMotorsBus(SerialMotorsBus): return half_turn_homings def _split_into_byte_chunks(self, value: int, length: int) -> list[int]: - return _split_into_byte_chunks(value, length) + if length == 1: + data = [value] + elif length == 2: + data = [dxl.DXL_LOBYTE(value), dxl.DXL_HIBYTE(value)] + elif length == 4: + data = [ + dxl.DXL_LOBYTE(dxl.DXL_LOWORD(value)), + dxl.DXL_HIBYTE(dxl.DXL_LOWORD(value)), + dxl.DXL_LOBYTE(dxl.DXL_HIWORD(value)), + dxl.DXL_HIBYTE(dxl.DXL_HIWORD(value)), + ] + return data def broadcast_ping(self, num_retry: int = 0, raise_on_error: bool = False) -> dict[int, int] | None: for n_try in range(1 + num_retry): diff --git a/src/lerobot/motors/feetech/feetech.py b/src/lerobot/motors/feetech/feetech.py index 837505830..9b1e0fb7e 100644 --- a/src/lerobot/motors/feetech/feetech.py +++ b/src/lerobot/motors/feetech/feetech.py @@ -73,21 +73,6 @@ class TorqueMode(Enum): DISABLED = 0 -def _split_into_byte_chunks(value: int, length: int) -> list[int]: - if length == 1: - data = [value] - elif length == 2: - data = [scs.SCS_LOBYTE(value), scs.SCS_HIBYTE(value)] - elif length == 4: - data = [ - scs.SCS_LOBYTE(scs.SCS_LOWORD(value)), - scs.SCS_HIBYTE(scs.SCS_LOWORD(value)), - scs.SCS_LOBYTE(scs.SCS_HIWORD(value)), - scs.SCS_HIBYTE(scs.SCS_HIWORD(value)), - ] - return data - - def patch_setPacketTimeout(self, packet_length): # noqa: N802 """ HACK: This patches the PortHandler behavior to set the correct packet timeouts. @@ -332,7 +317,18 @@ class FeetechMotorsBus(SerialMotorsBus): return ids_values def _split_into_byte_chunks(self, value: int, length: int) -> list[int]: - return _split_into_byte_chunks(value, length) + if length == 1: + data = [value] + elif length == 2: + data = [scs.SCS_LOBYTE(value), scs.SCS_HIBYTE(value)] + elif length == 4: + data = [ + scs.SCS_LOBYTE(scs.SCS_LOWORD(value)), + scs.SCS_HIBYTE(scs.SCS_LOWORD(value)), + scs.SCS_LOBYTE(scs.SCS_HIWORD(value)), + scs.SCS_HIBYTE(scs.SCS_HIWORD(value)), + ] + return data def _broadcast_ping(self) -> tuple[dict[int, int], int]: data_list: dict[int, int] = {} diff --git a/src/lerobot/scripts/lerobot_calibrate.py b/src/lerobot/scripts/lerobot_calibrate.py index 192daa69f..e68d7438b 100644 --- a/src/lerobot/scripts/lerobot_calibrate.py +++ b/src/lerobot/scripts/lerobot_calibrate.py @@ -15,6 +15,8 @@ """ Helper to recalibrate your device (robot or teleoperator). +Requires: pip install 'lerobot[hardware]' + Example: ```shell diff --git a/src/lerobot/scripts/lerobot_dataset_viz.py b/src/lerobot/scripts/lerobot_dataset_viz.py index c8d3fab99..d07a2767d 100644 --- a/src/lerobot/scripts/lerobot_dataset_viz.py +++ b/src/lerobot/scripts/lerobot_dataset_viz.py @@ -15,6 +15,8 @@ # limitations under the License. """ Visualize data of **all** frames of any episode of a dataset of type LeRobotDataset. +Requires: pip install 'lerobot[dataset_viz]' (includes dataset + viz extras) + Note: The last frame of the episode doesn't always correspond to a final state. That's because our datasets are composed of transition from state to state up to the antepenultimate state associated to the ultimate action to arrive in the final state. diff --git a/src/lerobot/scripts/lerobot_edit_dataset.py b/src/lerobot/scripts/lerobot_edit_dataset.py index 162bf7e41..0cfb34325 100644 --- a/src/lerobot/scripts/lerobot_edit_dataset.py +++ b/src/lerobot/scripts/lerobot_edit_dataset.py @@ -17,6 +17,8 @@ """ Edit LeRobot datasets using various transformation tools. +Requires: pip install 'lerobot[dataset]' + This script allows you to delete episodes, split datasets, merge datasets, remove features, modify tasks, recompute stats, and convert image datasets to video format. When new_repo_id is specified, creates a new dataset. diff --git a/src/lerobot/scripts/lerobot_eval.py b/src/lerobot/scripts/lerobot_eval.py index 9f13bcc1e..d45483d21 100644 --- a/src/lerobot/scripts/lerobot_eval.py +++ b/src/lerobot/scripts/lerobot_eval.py @@ -15,6 +15,9 @@ # limitations under the License. """Evaluate a policy on an environment by running rollouts and computing metrics. +Requires: pip install 'lerobot[evaluation]' plus the policy extra (e.g. lerobot[pi]) + and the environment extra (e.g. lerobot[pusht]) if evaluating in simulation. + Usage examples: You want to evaluate a model from the hub (eg: https://huggingface.co/lerobot/diffusion_pusht) diff --git a/src/lerobot/scripts/lerobot_record.py b/src/lerobot/scripts/lerobot_record.py index 8f1d27602..fa92a296d 100644 --- a/src/lerobot/scripts/lerobot_record.py +++ b/src/lerobot/scripts/lerobot_record.py @@ -15,6 +15,8 @@ """ Records a dataset. Actions for the robot can be either generated by teleoperation or by a policy. +Requires: pip install 'lerobot[core_scripts]' (includes dataset + hardware + viz extras) + Example: ```shell diff --git a/src/lerobot/scripts/lerobot_replay.py b/src/lerobot/scripts/lerobot_replay.py index a00e62b04..41d2926cc 100644 --- a/src/lerobot/scripts/lerobot_replay.py +++ b/src/lerobot/scripts/lerobot_replay.py @@ -15,6 +15,8 @@ """ Replays the actions of an episode from a dataset on a robot. +Requires: pip install 'lerobot[core_scripts]' (includes dataset + hardware + viz extras) + Examples: ```shell diff --git a/src/lerobot/scripts/lerobot_teleoperate.py b/src/lerobot/scripts/lerobot_teleoperate.py index 36d842af4..76157595e 100644 --- a/src/lerobot/scripts/lerobot_teleoperate.py +++ b/src/lerobot/scripts/lerobot_teleoperate.py @@ -15,6 +15,8 @@ """ Simple script to control a robot from teleoperation. +Requires: pip install 'lerobot[hardware]' + Example: ```shell diff --git a/src/lerobot/scripts/lerobot_train.py b/src/lerobot/scripts/lerobot_train.py index c943767e3..a862c640d 100644 --- a/src/lerobot/scripts/lerobot_train.py +++ b/src/lerobot/scripts/lerobot_train.py @@ -13,6 +13,11 @@ # 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. +"""Train a policy. + +Requires: pip install 'lerobot[training]' (includes dataset + accelerate + wandb extras) +""" + import dataclasses import logging import time diff --git a/tests/mocks/mock_dynamixel.py b/tests/mocks/mock_dynamixel.py index 84026fc34..4a424a97c 100644 --- a/tests/mocks/mock_dynamixel.py +++ b/tests/mocks/mock_dynamixel.py @@ -21,10 +21,25 @@ import dynamixel_sdk as dxl import serial from mock_serial.mock_serial import MockSerial -from lerobot.motors.dynamixel.dynamixel import _split_into_byte_chunks - from .mock_serial_patch import WaitableStub + +def _split_into_byte_chunks(value: int, length: int) -> list[int]: + """Split an integer into a list of byte-sized integers (little-endian).""" + if length == 1: + data = [value] + elif length == 2: + data = [dxl.DXL_LOBYTE(value), dxl.DXL_HIBYTE(value)] + elif length == 4: + data = [ + dxl.DXL_LOBYTE(dxl.DXL_LOWORD(value)), + dxl.DXL_HIBYTE(dxl.DXL_LOWORD(value)), + dxl.DXL_LOBYTE(dxl.DXL_HIWORD(value)), + dxl.DXL_HIBYTE(dxl.DXL_HIWORD(value)), + ] + return data + + # https://emanual.robotis.com/docs/en/dxl/crc/ DXL_CRC_TABLE = [ 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, diff --git a/tests/mocks/mock_feetech.py b/tests/mocks/mock_feetech.py index 33cbc41d6..6e303b56b 100644 --- a/tests/mocks/mock_feetech.py +++ b/tests/mocks/mock_feetech.py @@ -21,11 +21,27 @@ import scservo_sdk as scs import serial from mock_serial import MockSerial -from lerobot.motors.feetech.feetech import _split_into_byte_chunks, patch_setPacketTimeout +from lerobot.motors.feetech.feetech import patch_setPacketTimeout from .mock_serial_patch import WaitableStub +def _split_into_byte_chunks(value: int, length: int) -> list[int]: + """Split an integer into a list of byte-sized integers (little-endian).""" + if length == 1: + data = [value] + elif length == 2: + data = [scs.SCS_LOBYTE(value), scs.SCS_HIBYTE(value)] + elif length == 4: + data = [ + scs.SCS_LOBYTE(scs.SCS_LOWORD(value)), + scs.SCS_HIBYTE(scs.SCS_LOWORD(value)), + scs.SCS_LOBYTE(scs.SCS_HIWORD(value)), + scs.SCS_HIBYTE(scs.SCS_HIWORD(value)), + ] + return data + + class MockFeetechPacket(abc.ABC): @classmethod def build(cls, scs_id: int, params: list[int], length: int, *args, **kwargs) -> bytes: diff --git a/uv.lock b/uv.lock index a0e6d7409..f7854e874 100644 --- a/uv.lock +++ b/uv.lock @@ -2304,6 +2304,18 @@ build = [ can-dep = [ { name = "python-can" }, ] +core-scripts = [ + { name = "av" }, + { name = "datasets" }, + { name = "deepdiff" }, + { name = "jsonlines" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pynput" }, + { name = "pyserial" }, + { name = "rerun-sdk" }, + { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] damiao = [ { name = "python-can" }, ] @@ -2477,18 +2489,6 @@ qwen-vl-utils-dep = [ reachy2 = [ { name = "reachy2-sdk" }, ] -robot = [ - { name = "av" }, - { name = "datasets" }, - { name = "deepdiff" }, - { name = "jsonlines" }, - { name = "pandas" }, - { name = "pyarrow" }, - { name = "pynput" }, - { name = "pyserial" }, - { name = "rerun-sdk" }, - { name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux') or (platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, -] robstride = [ { name = "python-can" }, ] @@ -2607,12 +2607,12 @@ requires-dist = [ { name = "lerobot", extras = ["damiao"], marker = "extra == 'openarms'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'aloha'" }, + { name = "lerobot", extras = ["dataset"], marker = "extra == 'core-scripts'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'dataset-viz'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'dev'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'libero'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'metaworld'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'pusht'" }, - { name = "lerobot", extras = ["dataset"], marker = "extra == 'robot'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["dataset"], marker = "extra == 'training'" }, { name = "lerobot", extras = ["dev"], marker = "extra == 'all'" }, @@ -2628,8 +2628,8 @@ requires-dist = [ { name = "lerobot", extras = ["grpcio-dep"], marker = "extra == 'dev'" }, { name = "lerobot", extras = ["grpcio-dep"], marker = "extra == 'hilserl'" }, { name = "lerobot", extras = ["hardware"], marker = "extra == 'all'" }, + { name = "lerobot", extras = ["hardware"], marker = "extra == 'core-scripts'" }, { name = "lerobot", extras = ["hardware"], marker = "extra == 'dev'" }, - { name = "lerobot", extras = ["hardware"], marker = "extra == 'robot'" }, { name = "lerobot", extras = ["hardware"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["hilserl"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["hopejr"], marker = "extra == 'all'" }, @@ -2680,9 +2680,9 @@ requires-dist = [ { name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'xvla'" }, { name = "lerobot", extras = ["video-benchmark"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'all'" }, + { name = "lerobot", extras = ["viz"], marker = "extra == 'core-scripts'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'dataset-viz'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'dev'" }, - { name = "lerobot", extras = ["viz"], marker = "extra == 'robot'" }, { name = "lerobot", extras = ["viz"], marker = "extra == 'test'" }, { name = "lerobot", extras = ["wallx"], marker = "extra == 'all'" }, { name = "lerobot", extras = ["xvla"], marker = "extra == 'all'" }, @@ -2740,7 +2740,7 @@ requires-dist = [ { name = "transformers", marker = "extra == 'transformers-dep'", specifier = "==5.3.0" }, { name = "wandb", marker = "extra == 'training'", specifier = ">=0.24.0,<0.25.0" }, ] -provides-extras = ["dataset", "training", "hardware", "viz", "build", "robot", "evaluation", "dataset-viz", "av-dep", "pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "diffusers-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "diffusion", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"] +provides-extras = ["dataset", "training", "hardware", "viz", "build", "core-scripts", "evaluation", "dataset-viz", "av-dep", "pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "diffusers-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "diffusion", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"] [[package]] name = "librt"