mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-31 02:41:24 +00:00
feat(dependecies): minimal default tag install
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
228
uv.lock
generated
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user