feat(dependecies): minimal default tag install

This commit is contained in:
Steven Palma
2026-04-10 14:22:13 +02:00
parent 6799da35eb
commit e2381633cd
44 changed files with 575 additions and 363 deletions

View File

@@ -168,7 +168,7 @@ Use the `image_transforms` parameter when loading a dataset for training:
```python
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.transforms import ImageTransforms, ImageTransformsConfig, ImageTransformConfig
from lerobot.transforms import ImageTransforms, ImageTransformsConfig, ImageTransformConfig
# Option 1: Use default transform configuration (disabled by default)
transforms_config = ImageTransformsConfig(

View File

@@ -27,7 +27,7 @@ from torchvision.transforms import v2
from torchvision.transforms.functional import to_pil_image
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.transforms import ImageTransformConfig, ImageTransforms, ImageTransformsConfig
from lerobot.transforms import ImageTransformConfig, ImageTransforms, ImageTransformsConfig
def save_image(tensor, filename):

View File

@@ -58,44 +58,64 @@ classifiers = [
keywords = ["lerobot", "huggingface", "robotics", "machine learning", "artificial intelligence"]
dependencies = [
# Hugging Face dependencies
"datasets>=4.0.0,<5.0.0",
"diffusers>=0.27.2,<0.36.0",
"huggingface-hub>=1.0.0,<2.0.0",
"accelerate>=1.10.0,<2.0.0",
# Core dependencies
"numpy>=2.0.0,<2.3.0", # NOTE: Explicitly listing numpy helps the resolver converge faster. Upper bound imposed by opencv-python-headless.
"setuptools>=71.0.0,<81.0.0",
"cmake>=3.29.0.1,<4.2.0",
"packaging>=24.2,<26.0",
# Core ML
"torch>=2.7,<2.11.0",
"torchcodec>=0.3.0,<0.11.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')", # NOTE: Windows support starts at version 0.7 (needs torch==2.8), ffmpeg>=8 support starts at version 0.8.1 (needs torch==2.9), system-wide ffmpeg support starts at version 0.10 (needs torch==2.10).
"torchvision>=0.22.0,<0.26.0",
"einops>=0.8.0,<0.9.0",
"numpy>=2.0.0,<2.3.0", # NOTE: Explicitly listing numpy helps the resolver converge faster. Upper bound imposed by opencv-python-headless.
"opencv-python-headless>=4.9.0,<4.14.0",
"av>=15.0.0,<16.0.0",
"jsonlines>=4.0.0,<5.0.0",
"pynput>=1.7.8,<1.9.0",
"pyserial>=3.5,<4.0",
"einops>=0.8.0,<0.9.0",
"wandb>=0.24.0,<0.25.0",
# Config & Hub
"draccus==0.10.0", # TODO: Relax version constraint
"gymnasium>=1.1.1,<2.0.0",
"rerun-sdk>=0.24.0,<0.27.0",
"huggingface-hub>=1.0.0,<2.0.0",
# Support dependencies
"deepdiff>=7.0.1,<9.0.0",
"imageio[ffmpeg]>=2.34.0,<3.0.0",
# Environments
"gymnasium>=1.1.1,<2.0.0",
# Lightweight utilities
"packaging>=24.2,<26.0",
"termcolor>=2.4.0,<4.0.0",
]
# Optional dependencies
[project.optional-dependencies]
# ── Feature-scoped extras ──────────────────────────────────
dataset = [
"datasets>=4.0.0,<5.0.0",
"av>=15.0.0,<16.0.0",
"torchcodec>=0.3.0,<0.11.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')", # NOTE: Windows support starts at version 0.7 (needs torch==2.8), ffmpeg>=8 support starts at version 0.8.1 (needs torch==2.9), system-wide ffmpeg support starts at version 0.10 (needs torch==2.10).
"jsonlines>=4.0.0,<5.0.0",
]
train = [
"lerobot[dataset]",
"accelerate>=1.10.0,<2.0.0",
"wandb>=0.24.0,<0.25.0",
"diffusers>=0.27.2,<0.36.0",
]
hardware = [
"pynput>=1.7.8,<1.9.0",
"pyserial>=3.5,<4.0",
"deepdiff>=7.0.1,<9.0.0",
]
viz = [
"rerun-sdk>=0.24.0,<0.27.0",
]
build = [
"cmake>=3.29.0.1,<4.2.0",
"setuptools>=71.0.0,<81.0.0",
]
# ── User-facing composite extras (map to CLI scripts) ─────
# lerobot-record, lerobot-replay, lerobot-calibrate, lerobot-teleoperate, etc.
robot = ["lerobot[dataset]", "lerobot[hardware]", "lerobot[viz]"]
# lerobot-eval
evaluation = ["av>=15.0.0,<16.0.0"]
# lerobot-train
training = ["lerobot[train]"]
# lerobot-dataset-viz, lerobot-imgtransform-viz
dataset_viz = ["lerobot[dataset]", "lerobot[viz]"]
# Common
pygame-dep = ["pygame>=2.5.1,<2.7.0"]
placo-dep = ["placo>=0.9.6,<0.9.17"]
@@ -166,19 +186,25 @@ async = ["lerobot[grpcio-dep]", "lerobot[matplotlib-dep]"]
peft = ["lerobot[transformers-dep]", "lerobot[peft-dep]"]
# 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", "mypy>=1.19.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'"]
dev = ["lerobot[dataset]", "lerobot[train]", "lerobot[hardware]", "lerobot[viz]", "pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools==1.73.1", "mypy>=1.19.1"]
test = ["lerobot[dataset]", "lerobot[train]", "lerobot[hardware]", "lerobot[viz]", "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
# NOTE: Explicitly listing scipy helps flatten the dependecy tree.
aloha = ["gym-aloha>=0.1.2,<0.2.0", "lerobot[scipy-dep]"]
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]", "hf-libero>=0.1.3,<0.2.0; sys_platform == 'linux'", "lerobot[scipy-dep]"]
metaworld = ["metaworld==3.0.0", "lerobot[scipy-dep]"]
aloha = ["lerobot[dataset]", "gym-aloha>=0.1.2,<0.2.0", "lerobot[scipy-dep]"]
pusht = ["lerobot[dataset]", "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[dataset]", "lerobot[transformers-dep]", "hf-libero>=0.1.3,<0.2.0; sys_platform == 'linux'", "lerobot[scipy-dep]"]
metaworld = ["lerobot[dataset]", "metaworld==3.0.0", "lerobot[scipy-dep]"]
# All
all = [
# Feature-scoped extras
"lerobot[dataset]",
"lerobot[train]",
"lerobot[hardware]",
"lerobot[viz]",
"lerobot[build]",
# NOTE(resolver hint): scipy is pulled in transitively via lerobot[scipy-dep] through
# multiple extras (aloha, metaworld, pi, wallx, phone). Listing it explicitly
# helps pip's resolver converge by constraining scipy early, before it encounters
@@ -267,7 +293,8 @@ ignore = [
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "F403"]
"__init__.py" = ["F401", "F403", "E402"]
"src/lerobot/scripts/*" = ["E402"] # require_package gates before imports
"src/lerobot/policies/wall_x/**" = ["N801", "N812", "SIM102", "SIM108", "SIM210", "SIM211", "B006", "B007", "SIM118"] # Supprese these as they are coming from original Qwen2_5_vl code TODO(pepijn): refactor original
[tool.ruff.lint.isort]

View File

@@ -16,8 +16,8 @@
from dataclasses import dataclass, field
from lerobot.datasets.transforms import ImageTransformsConfig
from lerobot.datasets.video_utils import get_safe_default_codec
from lerobot.transforms import ImageTransformsConfig
from lerobot.utils.import_utils import get_safe_default_codec
@dataclass

View File

@@ -15,17 +15,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from lerobot.utils.import_utils import require_package
require_package("datasets", extra="dataset")
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.multi_dataset import MultiLeRobotDataset
from lerobot.datasets.sampler import EpisodeAwareSampler
from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset
from lerobot.datasets.transforms import ImageTransforms, ImageTransformsConfig
__all__ = [
"EpisodeAwareSampler",
"ImageTransforms",
"ImageTransformsConfig",
"LeRobotDataset",
"LeRobotDatasetMetadata",
"MultiLeRobotDataset",

View File

@@ -829,7 +829,7 @@ def _copy_and_reindex_episodes_metadata(
data_metadata: Dict mapping new episode index to its data file metadata
video_metadata: Optional dict mapping new episode index to its video metadata
"""
from lerobot.datasets.utils import flatten_dict
from lerobot.utils.utils import flatten_dict
if src_dataset.meta.episodes is None:
src_dataset.meta.episodes = load_episodes(src_dataset.meta.root)

View File

@@ -24,7 +24,7 @@ from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.multi_dataset import MultiLeRobotDataset
from lerobot.datasets.streaming_dataset import StreamingLeRobotDataset
from lerobot.datasets.transforms import ImageTransforms
from lerobot.transforms import ImageTransforms
from lerobot.utils.constants import ACTION, OBS_PREFIX, REWARD
IMAGENET_STATS = {

View File

@@ -13,7 +13,6 @@
# 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 json
from pathlib import Path
from typing import Any
@@ -41,6 +40,7 @@ from lerobot.datasets.utils import (
serialize_dict,
unflatten_dict,
)
from lerobot.utils.io_utils import load_json, write_json
from lerobot.utils.utils import SuppressProgressBars
@@ -116,33 +116,6 @@ def embed_images(dataset: datasets.Dataset) -> datasets.Dataset:
return dataset
def load_json(fpath: Path) -> Any:
"""Load data from a JSON file.
Args:
fpath (Path): Path to the JSON file.
Returns:
Any: The data loaded from the JSON file.
"""
with open(fpath) as f:
return json.load(f)
def write_json(data: dict, fpath: Path) -> None:
"""Write data to a JSON file.
Creates parent directories if they don't exist.
Args:
data (dict): The dictionary to write.
fpath (Path): The path to the output JSON file.
"""
fpath.parent.mkdir(exist_ok=True, parents=True)
with open(fpath, "w") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def write_info(info: dict, local_dir: Path) -> None:
write_json(info, local_dir / INFO_PATH)

View File

@@ -28,6 +28,8 @@ import torch
from huggingface_hub import DatasetCard, DatasetCardData, HfApi
from huggingface_hub.errors import RevisionNotFoundError
from lerobot.utils.utils import flatten_dict, unflatten_dict
V30_MESSAGE = """
The dataset you requested ({repo_id}) is in {version} format.
@@ -123,59 +125,6 @@ def update_chunk_file_indices(chunk_idx: int, file_idx: int, chunks_size: int) -
return chunk_idx, file_idx
def flatten_dict(d: dict, parent_key: str = "", sep: str = "/") -> dict:
"""Flatten a nested dictionary by joining keys with a separator.
Example:
>>> dct = {"a": {"b": 1, "c": {"d": 2}}, "e": 3}
>>> print(flatten_dict(dct))
{'a/b': 1, 'a/c/d': 2, 'e': 3}
Args:
d (dict): The dictionary to flatten.
parent_key (str): The base key to prepend to the keys in this level.
sep (str): The separator to use between keys.
Returns:
dict: A flattened dictionary.
"""
items = []
for k, v in d.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_dict(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
def unflatten_dict(d: dict, sep: str = "/") -> dict:
"""Unflatten a dictionary with delimited keys into a nested dictionary.
Example:
>>> flat_dct = {"a/b": 1, "a/c/d": 2, "e": 3}
>>> print(unflatten_dict(flat_dct))
{'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
Args:
d (dict): A dictionary with flattened keys.
sep (str): The separator used in the keys.
Returns:
dict: A nested dictionary.
"""
outdict = {}
for key, value in d.items():
parts = key.split(sep)
d = outdict
for part in parts[:-1]:
if part not in d:
d[part] = {}
d = d[part]
d[parts[-1]] = value
return outdict
def serialize_dict(stats: dict[str, torch.Tensor | np.ndarray | dict]) -> dict:
"""Serialize a dictionary containing tensors or numpy arrays to be JSON-compatible.

View File

@@ -37,6 +37,8 @@ import torchvision
from datasets.features.features import register_feature
from PIL import Image
from lerobot.utils.import_utils import get_safe_default_codec
logger = logging.getLogger(__name__)
# List of hardware encoders to probe for auto-selection. Availability depends on the platform and FFmpeg build.
@@ -116,16 +118,6 @@ def resolve_vcodec(vcodec: str) -> str:
return "libsvtav1"
def get_safe_default_codec():
if importlib.util.find_spec("torchcodec"):
return "torchcodec"
else:
logger.warning(
"'torchcodec' is not available in your platform, falling back to 'pyav' as a default decoder"
)
return "pyav"
def decode_video_frames(
video_path: Path | str,
timestamps: list[float],

View File

@@ -23,13 +23,12 @@ import draccus
import torch
from safetensors.torch import load_file, save_file
from lerobot.datasets.io_utils import write_json
from lerobot.datasets.utils import flatten_dict, unflatten_dict
from lerobot.utils.constants import (
OPTIMIZER_PARAM_GROUPS,
OPTIMIZER_STATE,
)
from lerobot.utils.io_utils import deserialize_json_into_object
from lerobot.utils.io_utils import deserialize_json_into_object, write_json
from lerobot.utils.utils import flatten_dict, unflatten_dict
# Type alias for parameters accepted by optimizer build() methods.
# This matches PyTorch's optimizer signature while also supporting:

View File

@@ -23,9 +23,8 @@ import draccus
from torch.optim import Optimizer
from torch.optim.lr_scheduler import LambdaLR, LRScheduler
from lerobot.datasets.io_utils import write_json
from lerobot.utils.constants import SCHEDULER_STATE
from lerobot.utils.io_utils import deserialize_json_into_object
from lerobot.utils.io_utils import deserialize_json_into_object, write_json
@dataclass

View File

@@ -18,14 +18,15 @@ from __future__ import annotations
import importlib
import logging
from typing import Any, TypedDict, Unpack
from typing import TYPE_CHECKING, Any, TypedDict, Unpack
import torch
if TYPE_CHECKING:
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
from lerobot.configs.policies import PreTrainedConfig
from lerobot.configs.types import FeatureType
from lerobot.datasets.dataset_metadata import LeRobotDatasetMetadata
from lerobot.datasets.feature_utils import dataset_to_policy_features
from lerobot.envs.configs import EnvConfig
from lerobot.envs.utils import env_to_policy_features
from lerobot.policies.act.configuration_act import ACTConfig
@@ -494,6 +495,8 @@ def make_policy(
kwargs = {}
if ds_meta is not None:
from lerobot.datasets.feature_utils import dataset_to_policy_features
features = dataset_to_policy_features(ds_meta.features)
else:
if not cfg.pretrained_path:

View File

@@ -23,7 +23,6 @@ from torch import nn
from lerobot.configs.policies import PreTrainedConfig
from lerobot.configs.types import FeatureType, PolicyFeature
from lerobot.datasets.feature_utils import build_dataset_frame
from lerobot.types import PolicyAction, RobotAction, RobotObservation
from lerobot.utils.constants import ACTION, OBS_STR
@@ -163,6 +162,8 @@ def build_inference_frame(
Returns:
A dictionary of preprocessed tensors ready for model inference.
"""
from lerobot.datasets.feature_utils import build_dataset_frame
# Extracts the correct keys from the incoming raw observation
observation = build_dataset_frame(ds_features, observation, prefix=OBS_STR)

View File

@@ -19,14 +19,17 @@ from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass, field
from typing import Any
from typing import TYPE_CHECKING, Any
import torch
from torch import Tensor
from lerobot.configs.types import FeatureType, NormalizationMode, PipelineFeatureType, PolicyFeature
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.types import EnvTransition, PolicyAction, TransitionKey
if TYPE_CHECKING:
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.utils.constants import ACTION
from .converters import from_tensor_to_numpy, to_tensor

View File

@@ -66,7 +66,6 @@ import time
from pathlib import Path
import numpy as np
import rerun as rr
import torch
import torch.utils.data
import tqdm
@@ -117,6 +116,11 @@ def visualize_dataset(
if mode not in ["local", "distant"]:
raise ValueError(mode)
from lerobot.utils.import_utils import require_package
require_package("rerun-sdk", extra="viz", import_name="rerun")
import rerun as rr
spawn_local_viewer = mode == "local" and not save
rr.init(f"{repo_id}/episode_{episode_index}", spawn=spawn_local_viewer)

View File

@@ -28,7 +28,10 @@ from pathlib import Path
def find_available_ports():
from serial.tools import list_ports # Part of pyserial library
from lerobot.utils.import_utils import require_package
require_package("pyserial", extra="hardware", import_name="serial")
from serial.tools import list_ports
if platform.system() == "Windows":
# List COM ports using pyserial

View File

@@ -37,7 +37,7 @@ from torchvision.transforms import ToPILImage
from lerobot.configs.default import DatasetConfig
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.transforms import (
from lerobot.transforms import (
ImageTransforms,
ImageTransformsConfig,
make_transform_from_config,

View File

@@ -56,8 +56,6 @@ import time
from dataclasses import asdict, dataclass
from pprint import pformat
import rerun as rr
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig # noqa: F401
from lerobot.cameras.realsense.configuration_realsense import RealSenseCameraConfig # noqa: F401
from lerobot.cameras.zmq.configuration_zmq import ZMQCameraConfig # noqa: F401
@@ -103,7 +101,7 @@ from lerobot.teleoperators import ( # noqa: F401
from lerobot.utils.import_utils import register_third_party_plugins
from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import init_logging, move_cursor_up
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data, shutdown_rerun
@dataclass
@@ -240,7 +238,7 @@ def teleoperate(cfg: TeleoperateConfig):
pass
finally:
if cfg.display_data:
rr.rerun_shutdown()
shutdown_rerun()
teleop.disconnect()
robot.disconnect()

View File

@@ -20,6 +20,10 @@ from contextlib import nullcontext
from pprint import pformat
from typing import Any
from lerobot.utils.import_utils import require_package
require_package("accelerate", extra="training")
import torch
from accelerate import Accelerator
from termcolor import colored

View File

@@ -12,25 +12,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
########################################################################################
# Utilities
########################################################################################
import logging
import traceback
from contextlib import nullcontext
from copy import copy
from functools import cache
from typing import Any
from typing import TYPE_CHECKING, Any
import numpy as np
import torch
from deepdiff import DeepDiff
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.utils import DEFAULT_FEATURES
from lerobot.policies.pretrained import PreTrainedPolicy
if TYPE_CHECKING:
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.policies.utils import prepare_observation_for_inference
from lerobot.processor import PolicyProcessorPipeline
from lerobot.robots import Robot
@@ -218,6 +218,13 @@ def sanity_check_dataset_robot_compatibility(
Raises:
ValueError: If any of the checked metadata fields do not match.
"""
from lerobot.utils.import_utils import require_package
require_package("deepdiff", extra="hardware")
from deepdiff import DeepDiff
from lerobot.datasets.utils import DEFAULT_FEATURES
fields = [
("robot_type", dataset.meta.robot_type, robot.robot_type),
("fps", dataset.fps, fps),

View File

@@ -69,6 +69,24 @@ def is_package_available(
return package_exists
def get_safe_default_codec():
if importlib.util.find_spec("torchcodec"):
return "torchcodec"
else:
logging.warning(
"'torchcodec' is not available in your platform, falling back to 'pyav' as a default decoder"
)
return "pyav"
def require_package(pkg_name: str, extra: str, import_name: str | None = None) -> None:
"""Raise an informative ImportError if a package required by an optional feature is missing."""
if not is_package_available(pkg_name, import_name):
raise ImportError(
f"'{pkg_name}' is required but not installed. Install it with: pip install 'lerobot[{extra}]'"
)
_transformers_available = is_package_available("transformers")
_peft_available = is_package_available("peft")
_scipy_available = is_package_available("scipy")

View File

@@ -14,21 +14,64 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import warnings
from pathlib import Path
import imageio
from typing import Any
JsonLike = str | int | float | bool | None | list["JsonLike"] | dict[str, "JsonLike"] | tuple["JsonLike", ...]
def write_video(video_path, stacked_frames, fps):
# Filter out DeprecationWarnings raised from pkg_resources
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", "pkg_resources is deprecated as an API", category=DeprecationWarning
)
imageio.mimsave(video_path, stacked_frames, fps=fps)
def load_json(fpath: Path) -> Any:
"""Load data from a JSON file.
Args:
fpath (Path): Path to the JSON file.
Returns:
Any: The data loaded from the JSON file.
"""
with open(fpath) as f:
return json.load(f)
def write_json(data: dict, fpath: Path) -> None:
"""Write data to a JSON file.
Creates parent directories if they don't exist.
Args:
data (dict): The dictionary to write.
fpath (Path): The path to the output JSON file.
"""
fpath.parent.mkdir(exist_ok=True, parents=True)
with open(fpath, "w") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def write_video(video_path: str | Path, stacked_frames: list, fps: int) -> None:
"""Write a sequence of RGB frames to an MP4 video file using libx264.
Args:
video_path: Output file path.
stacked_frames: List of HWC uint8 numpy arrays (RGB).
fps: Frames per second for the output video.
"""
import av
with av.open(str(video_path), mode="w") as container:
height, width = stacked_frames[0].shape[:2]
# Ensure dimensions are even for yuv420p compatibility
height = height if height % 2 == 0 else height - 1
width = width if width % 2 == 0 else width - 1
stream = container.add_stream("libx264", rate=fps)
stream.width = width
stream.height = height
stream.pix_fmt = "yuv420p"
for frame_array in stacked_frames:
frame = av.VideoFrame.from_ndarray(frame_array, format="rgb24")
for packet in stream.encode(frame):
container.mux(packet)
for packet in stream.encode():
container.mux(packet)
def deserialize_json_into_object[T: JsonLike](fpath: Path, obj: T) -> T:

View File

@@ -23,8 +23,8 @@ import numpy as np
import torch
from safetensors.torch import load_file, save_file
from lerobot.datasets.utils import flatten_dict, unflatten_dict
from lerobot.utils.constants import RNG_STATE
from lerobot.utils.utils import flatten_dict, unflatten_dict
def serialize_python_rng_state() -> dict[str, torch.Tensor]:

View File

@@ -19,7 +19,6 @@ from torch.optim import Optimizer
from torch.optim.lr_scheduler import LRScheduler
from lerobot.configs.train import TrainPipelineConfig
from lerobot.datasets.io_utils import load_json, write_json
from lerobot.optim.optimizers import load_optimizer_state, save_optimizer_state
from lerobot.optim.schedulers import load_scheduler_state, save_scheduler_state
from lerobot.policies.pretrained import PreTrainedPolicy
@@ -31,6 +30,7 @@ from lerobot.utils.constants import (
TRAINING_STATE_DIR,
TRAINING_STEP,
)
from lerobot.utils.io_utils import load_json, write_json
from lerobot.utils.random_utils import load_rng_state, save_rng_state

View File

@@ -199,6 +199,59 @@ def get_elapsed_time_in_days_hours_minutes_seconds(elapsed_time_s: float):
return days, hours, minutes, seconds
def flatten_dict(d: dict, parent_key: str = "", sep: str = "/") -> dict:
"""Flatten a nested dictionary by joining keys with a separator.
Example:
>>> dct = {"a": {"b": 1, "c": {"d": 2}}, "e": 3}
>>> print(flatten_dict(dct))
{'a/b': 1, 'a/c/d': 2, 'e': 3}
Args:
d (dict): The dictionary to flatten.
parent_key (str): The base key to prepend to the keys in this level.
sep (str): The separator to use between keys.
Returns:
dict: A flattened dictionary.
"""
items = []
for k, v in d.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_dict(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
def unflatten_dict(d: dict, sep: str = "/") -> dict:
"""Unflatten a dictionary with delimited keys into a nested dictionary.
Example:
>>> flat_dct = {"a/b": 1, "a/c/d": 2, "e": 3}
>>> print(unflatten_dict(flat_dct))
{'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
Args:
d (dict): A dictionary with flattened keys.
sep (str): The separator used in the keys.
Returns:
dict: A nested dictionary.
"""
outdict = {}
for key, value in d.items():
parts = key.split(sep)
d_inner = outdict
for part in parts[:-1]:
if part not in d_inner:
d_inner[part] = {}
d_inner = d_inner[part]
d_inner[parts[-1]] = value
return outdict
class SuppressProgressBars:
"""
Context manager to suppress progress bars.

View File

@@ -16,11 +16,16 @@ import numbers
import os
import numpy as np
import rerun as rr
from lerobot.types import RobotAction, RobotObservation
from lerobot.utils.import_utils import require_package
from .constants import ACTION, ACTION_PREFIX, OBS_PREFIX, OBS_STR
require_package("rerun-sdk", extra="viz", import_name="rerun")
import rerun as rr # noqa: E402
from lerobot.types import RobotAction, RobotObservation # noqa: E402
from .constants import ACTION, ACTION_PREFIX, OBS_PREFIX, OBS_STR # noqa: E402
def init_rerun(
@@ -44,6 +49,11 @@ def init_rerun(
rr.spawn(memory_limit=memory_limit)
def shutdown_rerun() -> None:
"""Shuts down the Rerun SDK gracefully."""
rr.rerun_shutdown()
def _is_scalar(x):
return isinstance(x, (float | numbers.Real | np.integer | np.floating)) or (
isinstance(x, np.ndarray) and x.ndim == 0

View File

@@ -19,7 +19,7 @@ import torch
from safetensors.torch import save_file
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.transforms import (
from lerobot.transforms import (
ImageTransformConfig,
ImageTransforms,
ImageTransformsConfig,

View File

@@ -24,7 +24,7 @@ import torch
from lerobot.configs.types import PolicyFeature
from lerobot.utils.constants import OBS_STATE
from tests.utils import require_package
from tests.utils import skip_if_package_missing
# -----------------------------------------------------------------------------
# Test fixtures
@@ -62,7 +62,7 @@ class MockPolicy:
@pytest.fixture
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def policy_server():
"""Fresh `PolicyServer` instance with a stubbed-out policy model."""
# Import only when the test actually runs (after decorator check)

View File

@@ -16,7 +16,7 @@
"""Contract tests for DatasetReader."""
from lerobot.datasets.dataset_reader import DatasetReader
from lerobot.datasets.video_utils import get_safe_default_codec
from lerobot.utils.import_utils import get_safe_default_codec
# ── Loading ──────────────────────────────────────────────────────────

View File

@@ -35,7 +35,6 @@ from lerobot.datasets.image_writer import image_array_to_pil_image
from lerobot.datasets.io_utils import hf_transform_to_torch
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.multi_dataset import MultiLeRobotDataset
from lerobot.datasets.transforms import ImageTransforms, ImageTransformsConfig
from lerobot.datasets.utils import (
DEFAULT_CHUNK_SIZE,
DEFAULT_DATA_FILE_SIZE_IN_MB,
@@ -46,6 +45,7 @@ from lerobot.datasets.video_utils import VALID_VIDEO_CODECS
from lerobot.envs.factory import make_env_config
from lerobot.policies.factory import make_policy_config
from lerobot.robots import make_robot_from_config
from lerobot.transforms import ImageTransforms, ImageTransformsConfig
from lerobot.utils.constants import ACTION, DONE, OBS_IMAGES, OBS_STATE, OBS_STR, REWARD
from tests.fixtures.constants import DUMMY_CHW, DUMMY_HWC, DUMMY_REPO_ID
from tests.mocks.mock_robot import MockRobotConfig

View File

@@ -21,7 +21,11 @@ from safetensors.torch import load_file
from torchvision.transforms import v2
from torchvision.transforms.v2 import functional as F # noqa: N812
from lerobot.datasets.transforms import (
from lerobot.scripts.lerobot_imgtransform_viz import (
save_all_transforms,
save_each_transform,
)
from lerobot.transforms import (
ImageTransformConfig,
ImageTransforms,
ImageTransformsConfig,
@@ -29,10 +33,6 @@ from lerobot.datasets.transforms import (
SharpnessJitter,
make_transform_from_config,
)
from lerobot.scripts.lerobot_imgtransform_viz import (
save_all_transforms,
save_each_transform,
)
from lerobot.utils.random_utils import seeded_context
from tests.artifacts.image_transforms.save_image_transforms_to_safetensors import ARTIFACT_DIR
from tests.utils import require_x86_64_kernel

View File

@@ -21,7 +21,7 @@ from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature
from lerobot.policies.sac.reward_model.configuration_classifier import RewardClassifierConfig
from lerobot.policies.sac.reward_model.modeling_classifier import ClassifierOutput
from lerobot.utils.constants import OBS_IMAGE, REWARD
from tests.utils import require_package
from tests.utils import skip_if_package_missing
def test_classifier_output():
@@ -37,7 +37,7 @@ def test_classifier_output():
)
@require_package("transformers")
@skip_if_package_missing("transformers")
@pytest.mark.skip(
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
)
@@ -81,7 +81,7 @@ def test_binary_classifier_with_default_params():
assert not torch.isnan(output.hidden_states).any(), "Tensor contains NaN values"
@require_package("transformers")
@skip_if_package_missing("transformers")
@pytest.mark.skip(
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
)
@@ -123,7 +123,7 @@ def test_multiclass_classifier():
assert not torch.isnan(output.hidden_states).any(), "Tensor contains NaN values"
@require_package("transformers")
@skip_if_package_missing("transformers")
@pytest.mark.skip(
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
)
@@ -138,7 +138,7 @@ def test_default_device():
assert p.device == torch.device("cpu")
@require_package("transformers")
@skip_if_package_missing("transformers")
@pytest.mark.skip(
reason="helper2424/resnet10 needs to be updated to work with the latest version of transformers"
)

View File

@@ -24,10 +24,10 @@ from lerobot.policies.factory import make_pre_post_processors # noqa: E402
from lerobot.policies.rtc.configuration_rtc import RTCConfig # noqa: E402
from lerobot.policies.smolvla.configuration_smolvla import SmolVLAConfig # noqa: F401
from lerobot.utils.random_utils import set_seed # noqa: E402
from tests.utils import require_cuda, require_package # noqa: E402
from tests.utils import require_cuda, skip_if_package_missing # noqa: E402
@require_package("transformers")
@skip_if_package_missing("transformers")
@require_cuda
def test_smolvla_rtc_initialization():
from lerobot.policies.smolvla.modeling_smolvla import SmolVLAPolicy # noqa: F401
@@ -65,7 +65,7 @@ def test_smolvla_rtc_initialization():
print("✓ SmolVLA RTC initialization: Test passed")
@require_package("transformers")
@skip_if_package_missing("transformers")
@require_cuda
def test_smolvla_rtc_initialization_without_rtc_config():
from lerobot.policies.smolvla.modeling_smolvla import SmolVLAPolicy # noqa: F401
@@ -87,7 +87,7 @@ def test_smolvla_rtc_initialization_without_rtc_config():
print("✓ SmolVLA RTC initialization without RTC config: Test passed")
@require_package("transformers")
@skip_if_package_missing("transformers")
@require_cuda
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
def test_smolvla_rtc_inference_with_prev_chunk():
@@ -170,7 +170,7 @@ def test_smolvla_rtc_inference_with_prev_chunk():
print("✓ SmolVLA RTC inference with prev_chunk: Test passed")
@require_package("transformers")
@skip_if_package_missing("transformers")
@require_cuda
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
def test_smolvla_rtc_inference_without_prev_chunk():
@@ -244,7 +244,7 @@ def test_smolvla_rtc_inference_without_prev_chunk():
print("✓ SmolVLA RTC inference without prev_chunk: Test passed")
@require_package("transformers")
@skip_if_package_missing("transformers")
@require_cuda
@pytest.mark.skipif(True, reason="Requires pretrained SmolVLA model weights")
def test_smolvla_rtc_validation_rules():

View File

@@ -36,7 +36,7 @@ from lerobot.utils.constants import (
OBS_LANGUAGE_SUBTASK_TOKENS,
OBS_STATE,
)
from tests.utils import require_package
from tests.utils import skip_if_package_missing
class MockTokenizer:
@@ -94,7 +94,7 @@ def mock_tokenizer():
return MockTokenizer(vocab_size=100)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_basic_tokenization(mock_auto_tokenizer):
"""Test basic string tokenization functionality."""
@@ -129,7 +129,7 @@ def test_basic_tokenization(mock_auto_tokenizer):
assert attention_mask.shape == (10,)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_basic_tokenization_with_tokenizer_object():
"""Test basic string tokenization functionality using tokenizer object directly."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -161,7 +161,7 @@ def test_basic_tokenization_with_tokenizer_object():
assert attention_mask.shape == (10,)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_list_of_strings_tokenization(mock_auto_tokenizer):
"""Test tokenization of a list of strings."""
@@ -189,7 +189,7 @@ def test_list_of_strings_tokenization(mock_auto_tokenizer):
assert attention_mask.shape == (2, 8)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_tuple_of_strings_tokenization(mock_auto_tokenizer):
"""Test tokenization of a tuple of strings (returned by VectorEnv.call())."""
@@ -213,7 +213,7 @@ def test_tuple_of_strings_tokenization(mock_auto_tokenizer):
assert attention_mask.shape == (2, 8)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_custom_keys(mock_auto_tokenizer):
"""Test using custom task_key."""
@@ -239,7 +239,7 @@ def test_custom_keys(mock_auto_tokenizer):
assert tokens.shape == (5,)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_none_complementary_data(mock_auto_tokenizer):
"""Test handling of None complementary_data."""
@@ -255,7 +255,7 @@ def test_none_complementary_data(mock_auto_tokenizer):
processor(transition)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_missing_task_key(mock_auto_tokenizer):
"""Test handling when task key is missing."""
@@ -270,7 +270,7 @@ def test_missing_task_key(mock_auto_tokenizer):
processor(transition)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_none_task_value(mock_auto_tokenizer):
"""Test handling when task value is None."""
@@ -285,7 +285,7 @@ def test_none_task_value(mock_auto_tokenizer):
processor(transition)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_unsupported_task_type(mock_auto_tokenizer):
"""Test handling of unsupported task types."""
@@ -307,14 +307,14 @@ def test_unsupported_task_type(mock_auto_tokenizer):
processor(transition)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_no_tokenizer_error():
"""Test that ValueError is raised when neither tokenizer nor tokenizer_name is provided."""
with pytest.raises(ValueError, match="Either 'tokenizer' or 'tokenizer_name' must be provided"):
TokenizerProcessorStep()
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_invalid_tokenizer_name_error():
"""Test that error is raised when invalid tokenizer_name is provided."""
with patch("lerobot.processor.tokenizer_processor.AutoTokenizer") as mock_auto_tokenizer:
@@ -325,7 +325,7 @@ def test_invalid_tokenizer_name_error():
TokenizerProcessorStep(tokenizer_name="invalid-tokenizer")
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_get_config_with_tokenizer_name(mock_auto_tokenizer):
"""Test configuration serialization when using tokenizer_name."""
@@ -354,7 +354,7 @@ def test_get_config_with_tokenizer_name(mock_auto_tokenizer):
assert config == expected
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_config_with_tokenizer_object():
"""Test configuration serialization when using tokenizer object."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -382,7 +382,7 @@ def test_get_config_with_tokenizer_object():
assert "tokenizer_name" not in config
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_state_dict_methods(mock_auto_tokenizer):
"""Test state_dict and load_state_dict methods."""
@@ -399,7 +399,7 @@ def test_state_dict_methods(mock_auto_tokenizer):
processor.load_state_dict({})
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_reset_method(mock_auto_tokenizer):
"""Test reset method."""
@@ -412,7 +412,7 @@ def test_reset_method(mock_auto_tokenizer):
processor.reset()
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_integration_with_robot_processor(mock_auto_tokenizer):
"""Test integration with RobotProcessor."""
@@ -449,7 +449,7 @@ def test_integration_with_robot_processor(mock_auto_tokenizer):
assert torch.equal(result[TransitionKey.ACTION], transition[TransitionKey.ACTION])
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_save_and_load_pretrained_with_tokenizer_name(mock_auto_tokenizer):
"""Test saving and loading processor with tokenizer_name."""
@@ -489,7 +489,7 @@ def test_save_and_load_pretrained_with_tokenizer_name(mock_auto_tokenizer):
assert f"{OBS_LANGUAGE}.attention_mask" in result[TransitionKey.OBSERVATION]
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_save_and_load_pretrained_with_tokenizer_object():
"""Test saving and loading processor with tokenizer object using overrides."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -528,7 +528,7 @@ def test_save_and_load_pretrained_with_tokenizer_object():
assert f"{OBS_LANGUAGE}.attention_mask" in result[TransitionKey.OBSERVATION]
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_registry_functionality():
"""Test that the processor is properly registered."""
from lerobot.processor import ProcessorStepRegistry
@@ -541,7 +541,7 @@ def test_registry_functionality():
assert retrieved_class is TokenizerProcessorStep
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_features_basic():
"""Test basic feature contract functionality."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -574,7 +574,7 @@ def test_features_basic():
assert attention_mask_feature.shape == (128,)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_features_with_custom_max_length():
"""Test feature contract with custom max_length."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -596,7 +596,7 @@ def test_features_with_custom_max_length():
assert attention_mask_feature.shape == (64,)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_features_existing_features():
"""Test feature contract when tokenized features already exist."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -618,7 +618,7 @@ def test_features_existing_features():
assert output_features[PipelineFeatureType.OBSERVATION][f"{OBS_LANGUAGE}.attention_mask"].shape == (100,)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_tokenization_parameters(mock_auto_tokenizer):
"""Test that tokenization parameters are correctly passed to tokenizer."""
@@ -666,7 +666,7 @@ def test_tokenization_parameters(mock_auto_tokenizer):
assert tracking_tokenizer.last_call_kwargs["return_tensors"] == "pt"
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_preserves_other_complementary_data(mock_auto_tokenizer):
"""Test that other complementary data fields are preserved."""
@@ -701,7 +701,7 @@ def test_preserves_other_complementary_data(mock_auto_tokenizer):
assert f"{OBS_LANGUAGE}.attention_mask" in observation
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_deterministic_tokenization(mock_auto_tokenizer):
"""Test that tokenization is deterministic for the same input."""
@@ -729,7 +729,7 @@ def test_deterministic_tokenization(mock_auto_tokenizer):
assert torch.equal(attention_mask1, attention_mask2)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_empty_string_task(mock_auto_tokenizer):
"""Test handling of empty string task."""
@@ -753,7 +753,7 @@ def test_empty_string_task(mock_auto_tokenizer):
assert tokens.shape == (8,)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_very_long_task(mock_auto_tokenizer):
"""Test handling of very long task strings."""
@@ -779,7 +779,7 @@ def test_very_long_task(mock_auto_tokenizer):
assert attention_mask.shape == (5,)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_custom_padding_side(mock_auto_tokenizer):
"""Test using custom padding_side parameter."""
@@ -833,7 +833,7 @@ def test_custom_padding_side(mock_auto_tokenizer):
assert tracking_tokenizer.padding_side_calls[-1] == "right"
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_cpu():
"""Test that tokenized tensors stay on CPU when other tensors are on CPU."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -857,7 +857,7 @@ def test_device_detection_cpu():
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_cuda():
"""Test that tokenized tensors are moved to CUDA when other tensors are on CUDA."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -882,7 +882,7 @@ def test_device_detection_cuda():
@pytest.mark.skipif(torch.cuda.device_count() < 2, reason="Requires at least 2 GPUs")
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_multi_gpu():
"""Test that tokenized tensors match device in multi-GPU setup."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -906,7 +906,7 @@ def test_device_detection_multi_gpu():
assert attention_mask.device == device
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_no_tensors():
"""Test that tokenized tensors stay on CPU when no other tensors exist."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -928,7 +928,7 @@ def test_device_detection_no_tensors():
assert attention_mask.device.type == "cpu"
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_mixed_devices():
"""Test device detection when tensors are on different devices (uses first found)."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -956,7 +956,7 @@ def test_device_detection_mixed_devices():
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_from_action():
"""Test that device is detected from action tensor when no observation tensors exist."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -979,7 +979,7 @@ def test_device_detection_from_action():
assert attention_mask.device.type == "cuda"
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_device_detection_preserves_dtype():
"""Test that device detection doesn't affect dtype of tokenized tensors."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1000,7 +1000,7 @@ def test_device_detection_preserves_dtype():
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_integration_with_device_processor(mock_auto_tokenizer):
"""Test that TokenizerProcessorStep works correctly with DeviceProcessorStep in pipeline."""
@@ -1039,7 +1039,7 @@ def test_integration_with_device_processor(mock_auto_tokenizer):
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_simulated_accelerate_scenario():
"""Test scenario simulating Accelerate with data already on GPU."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1077,7 +1077,7 @@ def test_simulated_accelerate_scenario():
# =============================================================================
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_missing_key():
"""Test get_subtask returns None when subtask key is missing from complementary_data."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1093,7 +1093,7 @@ def test_get_subtask_missing_key():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_none_value():
"""Test get_subtask returns None when subtask value is None."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1109,7 +1109,7 @@ def test_get_subtask_none_value():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_none_complementary_data():
"""Test get_subtask returns None when complementary_data is None."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1125,7 +1125,7 @@ def test_get_subtask_none_complementary_data():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_string():
"""Test get_subtask returns list with single string when subtask is a string."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1143,7 +1143,7 @@ def test_get_subtask_string():
assert len(result) == 1
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_list_of_strings():
"""Test get_subtask returns the list when subtask is already a list of strings."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1162,7 +1162,7 @@ def test_get_subtask_list_of_strings():
assert len(result) == 3
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_unsupported_type_integer():
"""Test get_subtask returns None when subtask is an unsupported type (integer)."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1178,7 +1178,7 @@ def test_get_subtask_unsupported_type_integer():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_unsupported_type_mixed_list():
"""Test get_subtask returns None when subtask is a list with mixed types."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1194,7 +1194,7 @@ def test_get_subtask_unsupported_type_mixed_list():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_unsupported_type_dict():
"""Test get_subtask returns None when subtask is a dictionary."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1210,7 +1210,7 @@ def test_get_subtask_unsupported_type_dict():
assert result is None
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_empty_string():
"""Test get_subtask with empty string returns list with empty string."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1226,7 +1226,7 @@ def test_get_subtask_empty_string():
assert result == [""]
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_get_subtask_empty_list():
"""Test get_subtask with empty list returns empty list."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1247,7 +1247,7 @@ def test_get_subtask_empty_list():
# =============================================================================
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_when_present():
"""Test that subtask is tokenized and added to observation when present."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1276,7 +1276,7 @@ def test_subtask_tokenization_when_present():
assert subtask_attention_mask.dtype == torch.bool
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_not_added_when_none():
"""Test that subtask tokens are NOT added to observation when subtask is None."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1300,7 +1300,7 @@ def test_subtask_tokenization_not_added_when_none():
assert f"{OBS_LANGUAGE}.attention_mask" in observation
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_not_added_when_subtask_value_is_none():
"""Test that subtask tokens are NOT added when subtask value is explicitly None."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1320,7 +1320,7 @@ def test_subtask_tokenization_not_added_when_subtask_value_is_none():
assert OBS_LANGUAGE_SUBTASK_ATTENTION_MASK not in observation
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_list_of_strings():
"""Test subtask tokenization with list of strings."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1346,7 +1346,7 @@ def test_subtask_tokenization_list_of_strings():
assert subtask_attention_mask.shape == (2, 8)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_device_cpu():
"""Test that subtask tokens are on CPU when other tensors are on CPU."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1372,7 +1372,7 @@ def test_subtask_tokenization_device_cpu():
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_device_cuda():
"""Test that subtask tokens are moved to CUDA when other tensors are on CUDA."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1397,7 +1397,7 @@ def test_subtask_tokenization_device_cuda():
assert subtask_attention_mask.device.type == "cuda"
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_preserves_other_observation_data():
"""Test that subtask tokenization preserves other observation data."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1423,7 +1423,7 @@ def test_subtask_tokenization_preserves_other_observation_data():
assert OBS_LANGUAGE_SUBTASK_ATTENTION_MASK in observation
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_attention_mask_dtype():
"""Test that subtask attention mask has correct dtype (bool)."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1442,7 +1442,7 @@ def test_subtask_attention_mask_dtype():
assert subtask_attention_mask.dtype == torch.bool
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_tokenization_deterministic():
"""Test that subtask tokenization is deterministic for the same input."""
mock_tokenizer = MockTokenizer(vocab_size=100)
@@ -1467,7 +1467,7 @@ def test_subtask_tokenization_deterministic():
assert torch.equal(subtask_mask1, subtask_mask2)
@require_package("transformers")
@skip_if_package_missing("transformers")
@patch("lerobot.processor.tokenizer_processor.AutoTokenizer")
def test_subtask_tokenization_integration_with_pipeline(mock_auto_tokenizer):
"""Test subtask tokenization works correctly with DataProcessorPipeline."""
@@ -1504,7 +1504,7 @@ def test_subtask_tokenization_integration_with_pipeline(mock_auto_tokenizer):
assert observation[OBS_LANGUAGE_SUBTASK_TOKENS].shape == (6,)
@require_package("transformers")
@skip_if_package_missing("transformers")
def test_subtask_not_added_for_unsupported_types():
"""Test that subtask tokens are not added when subtask has unsupported type."""
mock_tokenizer = MockTokenizer(vocab_size=100)

View File

@@ -23,7 +23,7 @@ from torch.multiprocessing import Event, Queue
from lerobot.utils.constants import OBS_STR
from lerobot.utils.transition import Transition
from tests.utils import require_package
from tests.utils import skip_if_package_missing
def create_learner_service_stub():
@@ -64,7 +64,7 @@ def close_service_stub(channel, server):
server.stop(None)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_establish_learner_connection_success():
from lerobot.rl.actor import establish_learner_connection
@@ -81,7 +81,7 @@ def test_establish_learner_connection_success():
close_service_stub(channel, server)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_establish_learner_connection_failure():
from lerobot.rl.actor import establish_learner_connection
@@ -100,7 +100,7 @@ def test_establish_learner_connection_failure():
close_service_stub(channel, server)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_push_transitions_to_transport_queue():
from lerobot.rl.actor import push_transitions_to_transport_queue
from lerobot.transport.utils import bytes_to_transitions
@@ -135,7 +135,7 @@ def test_push_transitions_to_transport_queue():
assert_transitions_equal(deserialized_transition, transitions[i])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_transitions_stream():
from lerobot.rl.actor import transitions_stream
@@ -167,7 +167,7 @@ def test_transitions_stream():
assert streamed_data[2].data == b"transition_data_3"
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_interactions_stream():
from lerobot.rl.actor import interactions_stream

View File

@@ -26,7 +26,7 @@ from lerobot.configs.train import TrainRLServerPipelineConfig
from lerobot.policies.sac.configuration_sac import SACConfig
from lerobot.utils.constants import OBS_STR
from lerobot.utils.transition import Transition
from tests.utils import require_package
from tests.utils import skip_if_package_missing
def create_test_transitions(count: int = 3) -> list[Transition]:
@@ -88,7 +88,7 @@ def cfg():
return cfg
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(10) # force cross-platform watchdog
def test_end_to_end_transitions_flow(cfg):
from lerobot.rl.actor import (
@@ -150,7 +150,7 @@ def test_end_to_end_transitions_flow(cfg):
assert_transitions_equal(transition, input_transitions[i])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(10)
def test_end_to_end_interactions_flow(cfg):
from lerobot.rl.actor import (
@@ -223,7 +223,7 @@ def test_end_to_end_interactions_flow(cfg):
assert received == expected
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.parametrize("data_size", ["small", "large"])
@pytest.mark.timeout(10)
def test_end_to_end_parameters_flow(cfg, data_size):

View File

@@ -20,7 +20,7 @@ from multiprocessing import Event, Queue
import pytest
from tests.utils import require_package # our gRPC servicer class
from tests.utils import skip_if_package_missing # our gRPC servicer class
@pytest.fixture(scope="function")
@@ -39,7 +39,7 @@ def learner_service_stub():
close_learner_service_stub(channel, server)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def create_learner_service_stub(
shutdown_event: Event,
parameters_queue: Queue,
@@ -75,7 +75,7 @@ def create_learner_service_stub(
return services_pb2_grpc.LearnerServiceStub(channel), channel, server
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def close_learner_service_stub(channel, server):
channel.close()
server.stop(None)
@@ -91,7 +91,7 @@ def test_ready_method(learner_service_stub):
assert response == services_pb2.Empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_send_interactions():
from lerobot.transport import services_pb2
@@ -135,7 +135,7 @@ def test_send_interactions():
assert interactions == [b"123", b"4", b"5", b"678"]
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_send_transitions():
from lerobot.transport import services_pb2
@@ -181,7 +181,7 @@ def test_send_transitions():
assert transitions == [b"transition_1transition_2transition_3", b"batch_1batch_2"]
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_send_transitions_empty_stream():
from lerobot.transport import services_pb2
@@ -209,7 +209,7 @@ def test_send_transitions_empty_stream():
assert transitions_queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(10) # force cross-platform watchdog
def test_stream_parameters():
import time
@@ -267,7 +267,7 @@ def test_stream_parameters():
assert time_diff == pytest.approx(seconds_between_pushes, abs=0.1)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_stream_parameters_with_shutdown():
from lerobot.transport import services_pb2
@@ -319,7 +319,7 @@ def test_stream_parameters_with_shutdown():
assert received_params == [b"param_batch_1", b"stop"]
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
@pytest.mark.timeout(3) # force cross-platform watchdog
def test_stream_parameters_waits_and_retries_on_empty_queue():
import threading

View File

@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
from safetensors.torch import load_file
from .utils import require_package
from .utils import skip_if_package_missing
# Skip this entire module in CI
pytestmark = pytest.mark.skipif(
@@ -37,7 +37,7 @@ def resolve_model_id_for_peft_training(policy_type):
@pytest.mark.parametrize("policy_type", ["smolvla"])
@require_package("peft")
@skip_if_package_missing("peft")
def test_peft_training_push_to_hub_works(policy_type, tmp_path):
"""Ensure that push to hub stores PEFT only the adapter, not the full model weights."""
output_dir = tmp_path / f"output_{policy_type}"
@@ -76,7 +76,7 @@ def test_peft_training_push_to_hub_works(policy_type, tmp_path):
@pytest.mark.parametrize("policy_type", ["smolvla"])
@require_package("peft")
@skip_if_package_missing("peft")
def test_peft_training_works(policy_type, tmp_path):
"""Check whether the standard case of fine-tuning a (partially) pre-trained policy with PEFT works."""
output_dir = tmp_path / f"output_{policy_type}"
@@ -125,7 +125,7 @@ def test_peft_training_works(policy_type, tmp_path):
@pytest.mark.parametrize("policy_type", ["smolvla"])
@require_package("peft")
@skip_if_package_missing("peft")
def test_peft_training_params_are_fewer(policy_type, tmp_path):
"""Check whether the standard case of fine-tuning a (partially) pre-trained policy with PEFT works."""
output_dir = tmp_path / f"output_{policy_type}"
@@ -176,7 +176,7 @@ def dummy_make_robot_from_config(*args, **kwargs):
@pytest.mark.parametrize("policy_type", ["smolvla"])
@require_package("peft")
@skip_if_package_missing("peft")
def test_peft_record_loads_policy(policy_type, tmp_path):
"""Train a policy with PEFT and attempt to load it with `lerobot-record`."""
from peft import PeftModel

View File

@@ -23,10 +23,10 @@ import torch
from lerobot.utils.constants import ACTION
from lerobot.utils.transition import Transition
from tests.utils import require_cuda, require_package
from tests.utils import require_cuda, skip_if_package_missing
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_bytes_buffer_size_empty_buffer():
from lerobot.transport.utils import bytes_buffer_size
@@ -37,7 +37,7 @@ def test_bytes_buffer_size_empty_buffer():
assert buffer.tell() == 0
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_bytes_buffer_size_small_buffer():
from lerobot.transport.utils import bytes_buffer_size
@@ -47,7 +47,7 @@ def test_bytes_buffer_size_small_buffer():
assert buffer.tell() == 0
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_bytes_buffer_size_large_buffer():
from lerobot.transport.utils import CHUNK_SIZE, bytes_buffer_size
@@ -58,7 +58,7 @@ def test_bytes_buffer_size_large_buffer():
assert buffer.tell() == 0
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_send_bytes_in_chunks_empty_data():
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
@@ -68,7 +68,7 @@ def test_send_bytes_in_chunks_empty_data():
assert len(chunks) == 0
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_single_chunk_small_data():
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
@@ -82,7 +82,7 @@ def test_single_chunk_small_data():
assert chunks[0].transfer_state == services_pb2.TransferState.TRANSFER_END
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_not_silent_mode():
from lerobot.transport.utils import send_bytes_in_chunks, services_pb2
@@ -94,7 +94,7 @@ def test_not_silent_mode():
assert chunks[0].data == b"Some data"
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_send_bytes_in_chunks_large_data():
from lerobot.transport.utils import CHUNK_SIZE, send_bytes_in_chunks, services_pb2
@@ -111,7 +111,7 @@ def test_send_bytes_in_chunks_large_data():
assert chunks[2].transfer_state == services_pb2.TransferState.TRANSFER_END
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_send_bytes_in_chunks_large_data_with_exact_chunk_size():
from lerobot.transport.utils import CHUNK_SIZE, send_bytes_in_chunks, services_pb2
@@ -124,7 +124,7 @@ def test_send_bytes_in_chunks_large_data_with_exact_chunk_size():
assert chunks[0].transfer_state == services_pb2.TransferState.TRANSFER_END
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_empty_data():
from lerobot.transport.utils import receive_bytes_in_chunks
@@ -138,7 +138,7 @@ def test_receive_bytes_in_chunks_empty_data():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_single_chunk():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -157,7 +157,7 @@ def test_receive_bytes_in_chunks_single_chunk():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_single_not_end_chunk():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -175,7 +175,7 @@ def test_receive_bytes_in_chunks_single_not_end_chunk():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_multiple_chunks():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -199,7 +199,7 @@ def test_receive_bytes_in_chunks_multiple_chunks():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_multiple_messages():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -235,7 +235,7 @@ def test_receive_bytes_in_chunks_multiple_messages():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_shutdown_during_receive():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -259,7 +259,7 @@ def test_receive_bytes_in_chunks_shutdown_during_receive():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_only_begin_chunk():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -279,7 +279,7 @@ def test_receive_bytes_in_chunks_only_begin_chunk():
assert queue.empty()
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_missing_begin():
from lerobot.transport.utils import receive_bytes_in_chunks, services_pb2
@@ -303,7 +303,7 @@ def test_receive_bytes_in_chunks_missing_begin():
# Tests for state_to_bytes and bytes_to_state_dict
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_state_to_bytes_empty_dict():
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
@@ -314,7 +314,7 @@ def test_state_to_bytes_empty_dict():
assert reconstructed == state_dict
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_bytes_to_state_dict_empty_data():
from lerobot.transport.utils import bytes_to_state_dict
@@ -323,7 +323,7 @@ def test_bytes_to_state_dict_empty_data():
bytes_to_state_dict(b"")
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_state_to_bytes_simple_dict():
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
@@ -347,7 +347,7 @@ def test_state_to_bytes_simple_dict():
assert torch.allclose(state_dict[key], reconstructed[key])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_state_to_bytes_various_dtypes():
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
@@ -372,7 +372,7 @@ def test_state_to_bytes_various_dtypes():
assert torch.allclose(state_dict[key], reconstructed[key])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_bytes_to_state_dict_invalid_data():
from lerobot.transport.utils import bytes_to_state_dict
@@ -382,7 +382,7 @@ def test_bytes_to_state_dict_invalid_data():
@require_cuda
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_state_to_bytes_various_dtypes_cuda():
from lerobot.transport.utils import bytes_to_state_dict, state_to_bytes
@@ -407,7 +407,7 @@ def test_state_to_bytes_various_dtypes_cuda():
assert torch.allclose(state_dict[key], reconstructed[key])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_python_object_to_bytes_none():
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
@@ -439,7 +439,7 @@ def test_python_object_to_bytes_none():
(1, 2, 3),
],
)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_python_object_to_bytes_simple_types(obj):
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
@@ -450,7 +450,7 @@ def test_python_object_to_bytes_simple_types(obj):
assert type(reconstructed) is type(obj)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_python_object_to_bytes_with_tensors():
from lerobot.transport.utils import bytes_to_python_object, python_object_to_bytes
@@ -475,7 +475,7 @@ def test_python_object_to_bytes_with_tensors():
assert torch.equal(obj["nested"]["tensor2"], reconstructed["nested"]["tensor2"])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_transitions_to_bytes_empty_list():
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
@@ -487,7 +487,7 @@ def test_transitions_to_bytes_empty_list():
assert isinstance(reconstructed, list)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_transitions_to_bytes_single_transition():
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
@@ -509,7 +509,7 @@ def test_transitions_to_bytes_single_transition():
assert_transitions_equal(transitions[0], reconstructed[0])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def assert_transitions_equal(t1: Transition, t2: Transition):
"""Helper to assert two transitions are equal."""
assert_observation_equal(t1["state"], t2["state"])
@@ -519,7 +519,7 @@ def assert_transitions_equal(t1: Transition, t2: Transition):
assert_observation_equal(t1["next_state"], t2["next_state"])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def assert_observation_equal(o1: dict, o2: dict):
"""Helper to assert two observations are equal."""
assert set(o1.keys()) == set(o2.keys())
@@ -527,7 +527,7 @@ def assert_observation_equal(o1: dict, o2: dict):
assert torch.allclose(o1[key], o2[key])
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_transitions_to_bytes_multiple_transitions():
from lerobot.transport.utils import bytes_to_transitions, transitions_to_bytes
@@ -551,7 +551,7 @@ def test_transitions_to_bytes_multiple_transitions():
assert_transitions_equal(original, reconstructed_item)
@require_package("grpcio", "grpc")
@skip_if_package_missing("grpcio", "grpc")
def test_receive_bytes_in_chunks_unknown_state():
from lerobot.transport.utils import receive_bytes_in_chunks

View File

@@ -152,7 +152,7 @@ def require_env(func):
return wrapper
def require_package_arg(func):
def skip_if_package_arg_missing(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).
@@ -184,7 +184,7 @@ def require_package_arg(func):
return wrapper
def require_package(package_name, import_name=None):
def skip_if_package_missing(package_name, import_name=None):
"""
Decorator that skips the test if the specified package is not installed.
"""

View File

@@ -48,6 +48,9 @@ def mock_rerun(monkeypatch):
calls.append((key, obj, kwargs))
dummy_rr = SimpleNamespace(
__name__="rerun",
__package__="rerun",
__spec__=SimpleNamespace(name="rerun", submodule_search_locations=None),
Scalars=DummyScalar,
Image=DummyImage,
log=dummy_log,

228
uv.lock generated
View File

@@ -2,11 +2,14 @@ version = 1
revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.14' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version >= '3.14' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version >= '3.14' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version == '3.13.*' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version == '3.13.*' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version == '3.13.*' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version < '3.13' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version < '3.13' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version < '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version < '3.13' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version >= '3.14' and platform_machine != 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
"python_full_version >= '3.14' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
@@ -820,7 +823,7 @@ name = "cuda-bindings"
version = "12.9.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder", marker = "sys_platform == 'linux'" },
{ name = "cuda-pathfinder", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" },
@@ -907,7 +910,7 @@ name = "decord"
version = "0.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x') or (platform_machine != 's390x' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/79/936af42edf90a7bd4e41a6cac89c913d4b47fa48a26b042d5129a9242ee3/decord-0.6.0-py3-none-manylinux2010_x86_64.whl", hash = "sha256:51997f20be8958e23b7c4061ba45d0efcd86bffd5fe81c695d0befee0d442976", size = 13602299, upload-time = "2021-06-14T21:30:55.486Z" },
@@ -1010,7 +1013,8 @@ name = "dm-tree"
version = "0.1.9"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version >= '3.14' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version >= '3.14' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version >= '3.14' and platform_machine != 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
"python_full_version >= '3.14' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
@@ -1043,9 +1047,11 @@ name = "dm-tree"
version = "0.1.10"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.13.*' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version == '3.13.*' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version == '3.13.*' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version < '3.13' and platform_machine != 's390x' and sys_platform == 'linux'",
"python_full_version < '3.13' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'",
"(python_full_version < '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine == 'armv7l' and sys_platform == 'linux')",
"python_full_version < '3.13' and platform_machine == 's390x' and sys_platform == 'linux'",
"python_full_version == '3.13.*' and platform_machine != 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
"python_full_version == '3.13.*' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'linux'",
@@ -2187,37 +2193,28 @@ name = "lerobot"
version = "0.5.2"
source = { editable = "." }
dependencies = [
{ name = "accelerate" },
{ name = "av" },
{ name = "cmake" },
{ name = "datasets" },
{ name = "deepdiff" },
{ name = "diffusers" },
{ name = "draccus" },
{ name = "einops" },
{ name = "gymnasium" },
{ name = "huggingface-hub" },
{ name = "imageio", extra = ["ffmpeg"] },
{ name = "jsonlines" },
{ name = "numpy" },
{ name = "opencv-python-headless" },
{ name = "packaging" },
{ name = "pynput" },
{ name = "pyserial" },
{ name = "rerun-sdk" },
{ name = "setuptools" },
{ name = "termcolor" },
{ name = "torch" },
{ 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')" },
{ name = "torchvision" },
{ name = "wandb" },
]
[package.optional-dependencies]
all = [
{ name = "accelerate" },
{ name = "av" },
{ name = "cmake" },
{ name = "contourpy" },
{ name = "datasets" },
{ name = "debugpy" },
{ name = "deepdiff" },
{ name = "diffusers" },
{ name = "dynamixel-sdk" },
{ name = "faker" },
{ name = "fastapi" },
@@ -2230,6 +2227,7 @@ all = [
{ name = "hebi-py" },
{ name = "hf-libero", marker = "sys_platform == 'linux'" },
{ name = "hidapi" },
{ name = "jsonlines" },
{ name = "matplotlib" },
{ name = "metaworld" },
{ name = "mock-serial", marker = "sys_platform != 'win32'" },
@@ -2242,24 +2240,34 @@ all = [
{ name = "protobuf" },
{ name = "pygame" },
{ name = "pymunk" },
{ name = "pynput" },
{ name = "pyrealsense2", marker = "sys_platform != 'darwin'" },
{ name = "pyrealsense2-macosx", marker = "sys_platform == 'darwin'" },
{ name = "pyserial" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "pytest-timeout" },
{ name = "pyzmq" },
{ name = "qwen-vl-utils" },
{ name = "reachy2-sdk" },
{ name = "rerun-sdk" },
{ name = "safetensors" },
{ name = "scikit-image" },
{ name = "scipy" },
{ name = "setuptools" },
{ name = "teleop" },
{ 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')" },
{ name = "torchdiffeq" },
{ name = "transformers" },
{ name = "wandb" },
]
aloha = [
{ name = "av" },
{ name = "datasets" },
{ name = "gym-aloha" },
{ name = "jsonlines" },
{ name = "scipy" },
{ 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')" },
]
async = [
{ name = "contourpy" },
@@ -2267,23 +2275,54 @@ async = [
{ name = "matplotlib" },
{ name = "protobuf" },
]
build = [
{ name = "cmake" },
{ name = "setuptools" },
]
can-dep = [
{ name = "python-can" },
]
damiao = [
{ name = "python-can" },
]
dataset = [
{ name = "av" },
{ name = "datasets" },
{ name = "jsonlines" },
{ 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')" },
]
dataset-viz = [
{ name = "av" },
{ name = "datasets" },
{ name = "jsonlines" },
{ 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')" },
]
dev = [
{ name = "accelerate" },
{ name = "av" },
{ name = "datasets" },
{ name = "debugpy" },
{ name = "deepdiff" },
{ name = "diffusers" },
{ name = "grpcio" },
{ name = "grpcio-tools" },
{ name = "jsonlines" },
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "protobuf" },
{ 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')" },
{ name = "wandb" },
]
dynamixel = [
{ name = "dynamixel-sdk" },
]
evaluation = [
{ name = "av" },
]
feetech = [
{ name = "feetech-servo-sdk" },
]
@@ -2307,6 +2346,11 @@ grpcio-dep = [
{ name = "grpcio" },
{ name = "protobuf" },
]
hardware = [
{ name = "deepdiff" },
{ name = "pynput" },
{ name = "pyserial" },
]
hilserl = [
{ name = "grpcio" },
{ name = "gym-hil" },
@@ -2330,8 +2374,12 @@ lekiwi = [
{ name = "pyzmq" },
]
libero = [
{ name = "av" },
{ name = "datasets" },
{ name = "hf-libero", marker = "sys_platform == 'linux'" },
{ name = "jsonlines" },
{ name = "scipy" },
{ 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')" },
{ name = "transformers" },
]
matplotlib-dep = [
@@ -2339,8 +2387,12 @@ matplotlib-dep = [
{ name = "matplotlib" },
]
metaworld = [
{ name = "av" },
{ name = "datasets" },
{ name = "jsonlines" },
{ name = "metaworld" },
{ name = "scipy" },
{ 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')" },
]
multi-task-dit = [
{ name = "transformers" },
@@ -2369,8 +2421,12 @@ placo-dep = [
{ name = "placo" },
]
pusht = [
{ name = "av" },
{ name = "datasets" },
{ name = "gym-pusht" },
{ name = "jsonlines" },
{ name = "pymunk" },
{ 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')" },
]
pygame-dep = [
{ name = "pygame" },
@@ -2381,6 +2437,16 @@ qwen-vl-utils-dep = [
reachy2 = [
{ name = "reachy2-sdk" },
]
robot = [
{ name = "av" },
{ name = "datasets" },
{ name = "deepdiff" },
{ name = "jsonlines" },
{ 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" },
]
@@ -2401,10 +2467,39 @@ smolvla = [
{ name = "transformers" },
]
test = [
{ name = "accelerate" },
{ name = "av" },
{ name = "datasets" },
{ name = "deepdiff" },
{ name = "diffusers" },
{ name = "jsonlines" },
{ name = "mock-serial", marker = "sys_platform != 'win32'" },
{ name = "pynput" },
{ name = "pyserial" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "pytest-timeout" },
{ 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')" },
{ name = "wandb" },
]
train = [
{ name = "accelerate" },
{ name = "av" },
{ name = "datasets" },
{ name = "diffusers" },
{ name = "jsonlines" },
{ 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')" },
{ name = "wandb" },
]
training = [
{ name = "accelerate" },
{ name = "av" },
{ name = "datasets" },
{ name = "diffusers" },
{ name = "jsonlines" },
{ 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')" },
{ name = "wandb" },
]
transformers-dep = [
{ name = "transformers" },
@@ -2422,6 +2517,9 @@ video-benchmark = [
{ name = "pandas" },
{ name = "scikit-image" },
]
viz = [
{ name = "rerun-sdk" },
]
wallx = [
{ name = "peft" },
{ name = "qwen-vl-utils" },
@@ -2435,16 +2533,17 @@ xvla = [
[package.metadata]
requires-dist = [
{ name = "accelerate", specifier = ">=1.10.0,<2.0.0" },
{ name = "accelerate", marker = "extra == 'smolvla'", specifier = ">=1.7.0,<2.0.0" },
{ name = "av", specifier = ">=15.0.0,<16.0.0" },
{ name = "cmake", specifier = ">=3.29.0.1,<4.2.0" },
{ name = "accelerate", marker = "extra == 'train'", specifier = ">=1.10.0,<2.0.0" },
{ name = "av", marker = "extra == 'dataset'", specifier = ">=15.0.0,<16.0.0" },
{ name = "av", marker = "extra == 'evaluation'", specifier = ">=15.0.0,<16.0.0" },
{ name = "cmake", marker = "extra == 'build'", specifier = ">=3.29.0.1,<4.2.0" },
{ name = "contourpy", marker = "extra == 'matplotlib-dep'", specifier = ">=1.3.0,<2.0.0" },
{ name = "datasets", specifier = ">=4.0.0,<5.0.0" },
{ name = "datasets", marker = "extra == 'dataset'", specifier = ">=4.0.0,<5.0.0" },
{ name = "debugpy", marker = "extra == 'dev'", specifier = ">=1.8.1,<1.9.0" },
{ name = "decord", marker = "(platform_machine == 'AMD64' and extra == 'groot') or (platform_machine == 'x86_64' and extra == 'groot')", specifier = ">=0.6.0,<1.0.0" },
{ name = "deepdiff", specifier = ">=7.0.1,<9.0.0" },
{ name = "diffusers", specifier = ">=0.27.2,<0.36.0" },
{ name = "deepdiff", marker = "extra == 'hardware'", specifier = ">=7.0.1,<9.0.0" },
{ name = "diffusers", marker = "extra == 'train'", specifier = ">=0.27.2,<0.36.0" },
{ name = "dm-tree", marker = "extra == 'groot'", specifier = ">=0.1.8,<1.0.0" },
{ name = "draccus", specifier = "==0.10.0" },
{ name = "dynamixel-sdk", marker = "extra == 'dynamixel'", specifier = ">=3.7.31,<3.9.0" },
@@ -2463,13 +2562,23 @@ requires-dist = [
{ name = "hf-libero", marker = "sys_platform == 'linux' and extra == 'libero'", specifier = ">=0.1.3,<0.2.0" },
{ name = "hidapi", marker = "extra == 'gamepad'", specifier = ">=0.14.0,<0.15.0" },
{ name = "huggingface-hub", specifier = ">=1.0.0,<2.0.0" },
{ name = "imageio", extras = ["ffmpeg"], specifier = ">=2.34.0,<3.0.0" },
{ name = "jsonlines", specifier = ">=4.0.0,<5.0.0" },
{ name = "jsonlines", marker = "extra == 'dataset'", specifier = ">=4.0.0,<5.0.0" },
{ name = "lerobot", extras = ["aloha"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["async"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["build"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["can-dep"], marker = "extra == 'damiao'" },
{ name = "lerobot", extras = ["can-dep"], marker = "extra == 'robstride'" },
{ 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 == '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 == 'train'" },
{ name = "lerobot", extras = ["dev"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["dynamixel"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["feetech"], marker = "extra == 'hopejr'" },
@@ -2478,6 +2587,10 @@ requires-dist = [
{ name = "lerobot", extras = ["grpcio-dep"], marker = "extra == 'async'" },
{ 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 == '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'" },
{ name = "lerobot", extras = ["intelrealsense"], marker = "extra == 'all'" },
@@ -2512,6 +2625,10 @@ requires-dist = [
{ name = "lerobot", extras = ["scipy-dep"], marker = "extra == 'wallx'" },
{ name = "lerobot", extras = ["smolvla"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["test"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["train"], marker = "extra == 'all'" },
{ name = "lerobot", extras = ["train"], marker = "extra == 'dev'" },
{ name = "lerobot", extras = ["train"], marker = "extra == 'test'" },
{ name = "lerobot", extras = ["train"], marker = "extra == 'training'" },
{ name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'groot'" },
{ name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'hilserl'" },
{ name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'libero'" },
@@ -2523,6 +2640,11 @@ requires-dist = [
{ name = "lerobot", extras = ["transformers-dep"], marker = "extra == 'wallx'" },
{ 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 == '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'" },
{ name = "matplotlib", marker = "extra == 'matplotlib-dep'", specifier = ">=3.10.3,<4.0.0" },
@@ -2545,10 +2667,10 @@ requires-dist = [
{ name = "protobuf", marker = "extra == 'grpcio-dep'", specifier = ">=6.31.1,<6.32.0" },
{ name = "pygame", marker = "extra == 'pygame-dep'", specifier = ">=2.5.1,<2.7.0" },
{ name = "pymunk", marker = "extra == 'pusht'", specifier = ">=6.6.0,<7.0.0" },
{ name = "pynput", specifier = ">=1.7.8,<1.9.0" },
{ name = "pynput", marker = "extra == 'hardware'", specifier = ">=1.7.8,<1.9.0" },
{ name = "pyrealsense2", marker = "sys_platform != 'darwin' and extra == 'intelrealsense'", specifier = ">=2.55.1.6486,<2.57.0" },
{ name = "pyrealsense2-macosx", marker = "sys_platform == 'darwin' and extra == 'intelrealsense'", specifier = ">=2.54,<2.57.0" },
{ name = "pyserial", specifier = ">=3.5,<4.0" },
{ name = "pyserial", marker = "extra == 'hardware'", specifier = ">=3.5,<4.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.1.0,<9.0.0" },
{ name = "pytest-cov", marker = "extra == 'test'", specifier = ">=5.0.0,<8.0.0" },
{ name = "pytest-timeout", marker = "extra == 'test'", specifier = ">=2.4.0,<3.0.0" },
@@ -2557,24 +2679,24 @@ requires-dist = [
{ name = "pyzmq", marker = "extra == 'unitree-g1'", specifier = ">=26.2.1,<28.0.0" },
{ name = "qwen-vl-utils", marker = "extra == 'qwen-vl-utils-dep'", specifier = ">=0.0.11,<0.1.0" },
{ name = "reachy2-sdk", marker = "extra == 'reachy2'", specifier = ">=1.0.15,<1.1.0" },
{ name = "rerun-sdk", specifier = ">=0.24.0,<0.27.0" },
{ name = "rerun-sdk", marker = "extra == 'viz'", specifier = ">=0.24.0,<0.27.0" },
{ name = "safetensors", marker = "extra == 'groot'", specifier = ">=0.4.3,<1.0.0" },
{ name = "safetensors", marker = "extra == 'smolvla'", specifier = ">=0.4.3,<1.0.0" },
{ name = "scikit-image", marker = "extra == 'video-benchmark'", specifier = ">=0.23.2,<0.26.0" },
{ name = "scipy", marker = "extra == 'all'", specifier = ">=1.14.0,<2.0.0" },
{ name = "scipy", marker = "extra == 'scipy-dep'", specifier = ">=1.14.0,<2.0.0" },
{ name = "setuptools", specifier = ">=71.0.0,<81.0.0" },
{ name = "setuptools", marker = "extra == 'build'", specifier = ">=71.0.0,<81.0.0" },
{ name = "teleop", marker = "extra == 'phone'", specifier = ">=0.1.0,<0.2.0" },
{ name = "termcolor", specifier = ">=2.4.0,<4.0.0" },
{ name = "timm", marker = "extra == 'groot'", specifier = ">=1.0.0,<1.1.0" },
{ name = "torch", specifier = ">=2.7,<2.11.0" },
{ 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')", specifier = ">=0.3.0,<0.11.0" },
{ name = "torchcodec", marker = "(platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and sys_platform == 'linux' and extra == 'dataset') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'dataset') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32' and extra == 'dataset')", specifier = ">=0.3.0,<0.11.0" },
{ name = "torchdiffeq", marker = "extra == 'wallx'", specifier = ">=0.2.4,<0.3.0" },
{ name = "torchvision", specifier = ">=0.22.0,<0.26.0" },
{ name = "transformers", marker = "extra == 'transformers-dep'", specifier = "==5.3.0" },
{ name = "wandb", specifier = ">=0.24.0,<0.25.0" },
{ name = "wandb", marker = "extra == 'train'", specifier = ">=0.24.0,<0.25.0" },
]
provides-extras = ["pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"]
provides-extras = ["dataset", "train", "hardware", "viz", "build", "robot", "evaluation", "training", "dataset-viz", "pygame-dep", "placo-dep", "transformers-dep", "grpcio-dep", "can-dep", "peft-dep", "scipy-dep", "qwen-vl-utils-dep", "matplotlib-dep", "feetech", "dynamixel", "damiao", "robstride", "openarms", "gamepad", "hopejr", "lekiwi", "unitree-g1", "reachy2", "kinematics", "intelrealsense", "phone", "wallx", "pi", "smolvla", "multi-task-dit", "groot", "sarm", "xvla", "hilserl", "async", "peft", "dev", "test", "video-benchmark", "aloha", "pusht", "libero", "metaworld", "all"]
[[package]]
name = "librt"
@@ -3359,7 +3481,7 @@ name = "nvidia-cudnn-cu12"
version = "9.10.2.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
@@ -3370,7 +3492,7 @@ name = "nvidia-cufft-cu12"
version = "11.3.3.83"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
@@ -3397,9 +3519,9 @@ name = "nvidia-cusolver-cu12"
version = "11.7.3.90"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
{ name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
@@ -3410,7 +3532,7 @@ name = "nvidia-cusparse-cu12"
version = "12.5.8.93"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l' and platform_machine != 's390x' and sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
@@ -4231,10 +4353,10 @@ name = "pyobjc-framework-applicationservices"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-coretext", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-coretext", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" }
wheels = [
@@ -4250,7 +4372,7 @@ name = "pyobjc-framework-cocoa"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
wheels = [
@@ -4266,9 +4388,9 @@ name = "pyobjc-framework-coretext"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" }
wheels = [
@@ -4284,8 +4406,8 @@ name = "pyobjc-framework-quartz"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" }
wheels = [