mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-30 21:19:40 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fe58f2d3a | |||
| bd2a796217 | |||
| 4c302572c0 | |||
| 72a1858015 | |||
| 2b578e68f6 | |||
| 7b1419a7fa | |||
| fbe8f5c9da | |||
| d632a103ae | |||
| 7e0fd0d653 | |||
| 0187856202 | |||
| 2939168c33 | |||
| 40a5e70352 | |||
| 0cef9cd197 | |||
| 643ffb4785 | |||
| d59505a735 | |||
| 6ac95363b0 | |||
| ede1fc2978 | |||
| 49d5ea49bc | |||
| d23b65416f |
@@ -164,8 +164,8 @@ includes the range reported by the sensor. Requesting an unsupported control als
|
|||||||
Omitted controls leave the sensor's existing automatic or manual setting unchanged. These options
|
Omitted controls leave the sensor's existing automatic or manual setting unchanged. These options
|
||||||
require `use_rgb=True`.
|
require `use_rgb=True`.
|
||||||
|
|
||||||
On the RealSense D405, the color stream is provided by the Stereo Module, so changing manual
|
Manual color controls require a dedicated RGB module. Cameras without one, such as the RealSense
|
||||||
exposure or gain also affects the depth stream.
|
D405, do not support them and raise an error at connection time.
|
||||||
|
|
||||||
</hfoption>
|
</hfoption>
|
||||||
</hfoptions>
|
</hfoptions>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ The `lerobot-rollout --strategy.type=dagger` mode requires **teleoperators with
|
|||||||
|
|
||||||
- `bi_openarm_mini` - Bimanual OpenArm Mini
|
- `bi_openarm_mini` - Bimanual OpenArm Mini
|
||||||
- `so_leader` - SO100 / SO101 leader arm
|
- `so_leader` - SO100 / SO101 leader arm
|
||||||
|
- `bi_so_leader` - Bimanual SO100 / SO101 leader arms
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The provided commands default to `bi_openarm_follower` + `bi_openarm_mini`.
|
> The provided commands default to `bi_openarm_follower` + `bi_openarm_mini`.
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ By default the env samples objects only from the `lightwheel` registry (what `--
|
|||||||
|
|
||||||
All eval snippets below mirror the CI command (see `.github/workflows/benchmark_tests.yml`). The `--rename_map` argument maps RoboCasa's native camera keys (`robot0_agentview_left` / `robot0_eye_in_hand` / `robot0_agentview_right`) onto the three-camera (`camera1` / `camera2` / `camera3`) input layout the released `smolvla_robocasa` policy was trained on.
|
All eval snippets below mirror the CI command (see `.github/workflows/benchmark_tests.yml`). The `--rename_map` argument maps RoboCasa's native camera keys (`robot0_agentview_left` / `robot0_eye_in_hand` / `robot0_agentview_right`) onto the three-camera (`camera1` / `camera2` / `camera3`) input layout the released `smolvla_robocasa` policy was trained on.
|
||||||
|
|
||||||
|
By default, each task uses the rollout horizon registered by RoboCasa. Set `--env.episode_length=<steps>` to apply the same explicit horizon to every selected task.
|
||||||
|
|
||||||
### Single-task evaluation (recommended for quick iteration)
|
### Single-task evaluation (recommended for quick iteration)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ It is advisable to install one 3-pin cable in the motor after placing them befor
|
|||||||
<hfoption id="Leader">
|
<hfoption id="Leader">
|
||||||
|
|
||||||
- Mount the leader holder onto the wrist and secure it with 4 M3x6mm screws.
|
- Mount the leader holder onto the wrist and secure it with 4 M3x6mm screws.
|
||||||
- Attach the handle to motor 5 using 1 M2x6mm screw.
|
- Attach the handle to the leader holder using 1 M2x6mm screw.
|
||||||
- Insert the gripper motor, secure it with 2 M2x6mm screws on each side, attach a motor horn using a M3x6mm horn screw.
|
- Insert the gripper motor, secure it with 2 M2x6mm screws on each side, attach a motor horn using a M3x6mm horn screw.
|
||||||
- Attach the follower trigger with 4 M3x6mm screws.
|
- Attach the follower trigger with 4 M3x6mm screws.
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from typing import Protocol
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -56,7 +57,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
EEBoundsAndSafety,
|
EEBoundsAndSafety,
|
||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import HF_LEROBOT_CALIBRATION, HF_LEROBOT_HOME, TELEOPERATORS
|
from lerobot.utils.constants import HF_LEROBOT_CALIBRATION, HF_LEROBOT_HOME, TELEOPERATORS
|
||||||
from lerobot.utils.robot_utils import precise_sleep
|
from lerobot.utils.robot_utils import precise_sleep
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from lerobot.types import RobotAction
|
from lerobot.lerobot_types import RobotAction
|
||||||
|
|
||||||
from .base import _GRIPPER_MOTOR_SCALE, IsaacTeleopTeleoperator, _isaacteleop_available
|
from .base import _GRIPPER_MOTOR_SCALE, IsaacTeleopTeleoperator, _isaacteleop_available
|
||||||
from .config_isaac_teleop import SO101LeaderArmConfig
|
from .config_isaac_teleop import SO101LeaderArmConfig
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from lerobot.types import RobotAction
|
from lerobot.lerobot_types import RobotAction
|
||||||
|
|
||||||
from .base import IsaacTeleopTeleoperator, _isaacteleop_available
|
from .base import IsaacTeleopTeleoperator, _isaacteleop_available
|
||||||
from .config_isaac_teleop import XRControllerConfig
|
from .config_isaac_teleop import XRControllerConfig
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import RobotAction
|
||||||
from lerobot.processor import ProcessorStepRegistry, RobotActionProcessorStep
|
from lerobot.processor import ProcessorStepRegistry, RobotActionProcessorStep
|
||||||
from lerobot.types import RobotAction
|
|
||||||
from lerobot.utils.rotation import Rotation
|
from lerobot.utils.rotation import Rotation
|
||||||
|
|
||||||
from .base import _GRIPPER_MOTOR_SCALE
|
from .base import _GRIPPER_MOTOR_SCALE
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from lerobot.cameras.opencv import OpenCVCameraConfig
|
|||||||
from lerobot.common.control_utils import predict_action
|
from lerobot.common.control_utils import predict_action
|
||||||
from lerobot.configs import FeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PolicyFeature
|
||||||
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.policies import make_pre_post_processors
|
from lerobot.policies import make_pre_post_processors
|
||||||
from lerobot.policies.act import ACTPolicy
|
from lerobot.policies.act import ACTPolicy
|
||||||
@@ -38,7 +39,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
ForwardKinematicsJointsToEE,
|
ForwardKinematicsJointsToEE,
|
||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import ACTION, OBS_STR
|
from lerobot.utils.constants import ACTION, OBS_STR
|
||||||
from lerobot.utils.feature_utils import build_dataset_frame, combine_feature_dicts
|
from lerobot.utils.feature_utils import build_dataset_frame, combine_feature_dicts
|
||||||
from lerobot.utils.keyboard_input import init_keyboard_listener
|
from lerobot.utils.keyboard_input import init_keyboard_listener
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from lerobot.cameras.opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv import OpenCVCameraConfig
|
||||||
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -36,7 +37,6 @@ from lerobot.scripts.lerobot_record import record_loop
|
|||||||
from lerobot.teleoperators.phone import Phone, PhoneConfig
|
from lerobot.teleoperators.phone import Phone, PhoneConfig
|
||||||
from lerobot.teleoperators.phone.config_phone import PhoneOS
|
from lerobot.teleoperators.phone.config_phone import PhoneOS
|
||||||
from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction
|
from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.feature_utils import combine_feature_dicts
|
from lerobot.utils.feature_utils import combine_feature_dicts
|
||||||
from lerobot.utils.keyboard_input import init_keyboard_listener
|
from lerobot.utils.keyboard_input import init_keyboard_listener
|
||||||
from lerobot.utils.utils import log_say
|
from lerobot.utils.utils import log_say
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -27,7 +28,6 @@ from lerobot.robots.so_follower import SO100Follower, SO100FollowerConfig
|
|||||||
from lerobot.robots.so_follower.robot_kinematic_processor import (
|
from lerobot.robots.so_follower.robot_kinematic_processor import (
|
||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import ACTION
|
from lerobot.utils.constants import ACTION
|
||||||
from lerobot.utils.robot_utils import precise_sleep
|
from lerobot.utils.robot_utils import precise_sleep
|
||||||
from lerobot.utils.utils import log_say
|
from lerobot.utils.utils import log_say
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ Highlight, or DAgger via ``lerobot-rollout --strategy.type=...``.
|
|||||||
|
|
||||||
from lerobot.cameras.opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv import OpenCVCameraConfig
|
||||||
from lerobot.configs import PreTrainedConfig
|
from lerobot.configs import PreTrainedConfig
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -43,7 +44,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
|
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
|
||||||
from lerobot.rollout.inference import SyncInferenceConfig
|
from lerobot.rollout.inference import SyncInferenceConfig
|
||||||
from lerobot.rollout.strategies import BaseStrategy
|
from lerobot.rollout.strategies import BaseStrategy
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.process import ProcessSignalHandler
|
from lerobot.utils.process import ProcessSignalHandler
|
||||||
from lerobot.utils.utils import init_logging
|
from lerobot.utils.utils import init_logging
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -31,7 +32,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
from lerobot.teleoperators.phone import Phone, PhoneConfig
|
from lerobot.teleoperators.phone import Phone, PhoneConfig
|
||||||
from lerobot.teleoperators.phone.config_phone import PhoneOS
|
from lerobot.teleoperators.phone.config_phone import PhoneOS
|
||||||
from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction
|
from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.robot_utils import precise_sleep
|
from lerobot.utils.robot_utils import precise_sleep
|
||||||
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
|
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from lerobot.cameras.opencv import OpenCVCameraConfig
|
|||||||
from lerobot.common.control_utils import predict_action
|
from lerobot.common.control_utils import predict_action
|
||||||
from lerobot.configs import FeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PolicyFeature
|
||||||
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.policies import make_pre_post_processors
|
from lerobot.policies import make_pre_post_processors
|
||||||
from lerobot.policies.act import ACTPolicy
|
from lerobot.policies.act import ACTPolicy
|
||||||
@@ -38,7 +39,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
ForwardKinematicsJointsToEE,
|
ForwardKinematicsJointsToEE,
|
||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import ACTION, OBS_STR
|
from lerobot.utils.constants import ACTION, OBS_STR
|
||||||
from lerobot.utils.feature_utils import build_dataset_frame, combine_feature_dicts
|
from lerobot.utils.feature_utils import build_dataset_frame, combine_feature_dicts
|
||||||
from lerobot.utils.keyboard_input import init_keyboard_listener
|
from lerobot.utils.keyboard_input import init_keyboard_listener
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
from lerobot.cameras.opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv import OpenCVCameraConfig
|
||||||
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
from lerobot.datasets import LeRobotDataset, aggregate_pipeline_dataset_features, create_initial_features
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -33,7 +34,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
)
|
)
|
||||||
from lerobot.scripts.lerobot_record import record_loop
|
from lerobot.scripts.lerobot_record import record_loop
|
||||||
from lerobot.teleoperators.so_leader import SO100Leader, SO100LeaderConfig
|
from lerobot.teleoperators.so_leader import SO100Leader, SO100LeaderConfig
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.feature_utils import combine_feature_dicts
|
from lerobot.utils.feature_utils import combine_feature_dicts
|
||||||
from lerobot.utils.keyboard_input import init_keyboard_listener
|
from lerobot.utils.keyboard_input import init_keyboard_listener
|
||||||
from lerobot.utils.utils import log_say
|
from lerobot.utils.utils import log_say
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -28,7 +29,6 @@ from lerobot.robots.so_follower import SO100Follower, SO100FollowerConfig
|
|||||||
from lerobot.robots.so_follower.robot_kinematic_processor import (
|
from lerobot.robots.so_follower.robot_kinematic_processor import (
|
||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import ACTION
|
from lerobot.utils.constants import ACTION
|
||||||
from lerobot.utils.robot_utils import precise_sleep
|
from lerobot.utils.robot_utils import precise_sleep
|
||||||
from lerobot.utils.utils import log_say
|
from lerobot.utils.utils import log_say
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ forward/inverse kinematics.
|
|||||||
|
|
||||||
from lerobot.cameras.opencv import OpenCVCameraConfig
|
from lerobot.cameras.opencv import OpenCVCameraConfig
|
||||||
from lerobot.configs import PreTrainedConfig
|
from lerobot.configs import PreTrainedConfig
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -41,7 +42,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
|
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
|
||||||
from lerobot.rollout.inference import SyncInferenceConfig
|
from lerobot.rollout.inference import SyncInferenceConfig
|
||||||
from lerobot.rollout.strategies import BaseStrategy
|
from lerobot.rollout.strategies import BaseStrategy
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.process import ProcessSignalHandler
|
from lerobot.utils.process import ProcessSignalHandler
|
||||||
from lerobot.utils.utils import init_logging
|
from lerobot.utils.utils import init_logging
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.model.kinematics import RobotKinematics
|
from lerobot.model.kinematics import RobotKinematics
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
RobotProcessorPipeline,
|
RobotProcessorPipeline,
|
||||||
@@ -30,7 +31,6 @@ from lerobot.robots.so_follower.robot_kinematic_processor import (
|
|||||||
InverseKinematicsEEToJoints,
|
InverseKinematicsEEToJoints,
|
||||||
)
|
)
|
||||||
from lerobot.teleoperators.so_leader import SO100Leader, SO100LeaderConfig
|
from lerobot.teleoperators.so_leader import SO100Leader, SO100LeaderConfig
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.robot_utils import precise_sleep
|
from lerobot.utils.robot_utils import precise_sleep
|
||||||
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
|
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ dependencies = [
|
|||||||
"einops>=0.8.0,<0.9.0",
|
"einops>=0.8.0,<0.9.0",
|
||||||
|
|
||||||
# Config & Hub
|
# Config & Hub
|
||||||
"draccus==0.10.0", # TODO: Relax version constraint
|
"draccus>=0.11.6,<0.12.0",
|
||||||
"huggingface-hub>=1.0.0,<2.0.0",
|
"huggingface-hub>=1.0.0,<2.0.0",
|
||||||
"requests>=2.32.0,<3.0.0",
|
"requests>=2.32.0,<3.0.0",
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import draccus
|
|||||||
import grpc
|
import grpc
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import PolicyAction
|
||||||
from lerobot.policies import get_policy_class, make_pre_post_processors
|
from lerobot.policies import get_policy_class, make_pre_post_processors
|
||||||
from lerobot.processor import PolicyProcessorPipeline
|
from lerobot.processor import PolicyProcessorPipeline
|
||||||
from lerobot.transport import (
|
from lerobot.transport import (
|
||||||
@@ -45,7 +46,6 @@ from lerobot.transport import (
|
|||||||
services_pb2_grpc, # type: ignore
|
services_pb2_grpc, # type: ignore
|
||||||
)
|
)
|
||||||
from lerobot.transport.utils import receive_bytes_in_chunks
|
from lerobot.transport.utils import receive_bytes_in_chunks
|
||||||
from lerobot.types import PolicyAction
|
|
||||||
|
|
||||||
from .configs import PolicyServerConfig
|
from .configs import PolicyServerConfig
|
||||||
from .constants import SUPPORTED_POLICIES
|
from .constants import SUPPORTED_POLICIES
|
||||||
|
|||||||
@@ -365,11 +365,12 @@ class RealSenseCamera(Camera):
|
|||||||
return self._async_read(timeout_ms=10000, read_depth=read_depth)
|
return self._async_read(timeout_ms=10000, read_depth=read_depth)
|
||||||
|
|
||||||
def _get_color_sensor(self) -> "rs.sensor":
|
def _get_color_sensor(self) -> "rs.sensor":
|
||||||
"""Returns the sensor that controls the color stream.
|
"""Returns the dedicated "RGB Camera" sensor that controls the color stream.
|
||||||
|
|
||||||
Most RealSense cameras expose "RGB Camera" for color. The D405 has no
|
Manual color controls are only applied to a dedicated RGB module. Cameras
|
||||||
separate RGB module — its color stream comes from "Stereo Module".
|
without one (e.g. the D405, whose color stream comes from the shared
|
||||||
We try RGB Camera first, then fall back to Stereo Module.
|
"Stereo Module") are unsupported, so we never fall back to another sensor
|
||||||
|
to avoid altering the depth stream.
|
||||||
"""
|
"""
|
||||||
if self.rs_profile is None:
|
if self.rs_profile is None:
|
||||||
raise RuntimeError(f"{self}: rs_profile must be initialized before use.")
|
raise RuntimeError(f"{self}: rs_profile must be initialized before use.")
|
||||||
@@ -377,12 +378,14 @@ class RealSenseCamera(Camera):
|
|||||||
device = self.rs_profile.get_device()
|
device = self.rs_profile.get_device()
|
||||||
sensors = {s.get_info(rs.camera_info.name): s for s in device.query_sensors()}
|
sensors = {s.get_info(rs.camera_info.name): s for s in device.query_sensors()}
|
||||||
|
|
||||||
for name in ("RGB Camera", "Stereo Module"):
|
if "RGB Camera" in sensors:
|
||||||
if name in sensors:
|
return sensors["RGB Camera"]
|
||||||
return sensors[name]
|
|
||||||
|
|
||||||
available = list(sensors.keys())
|
available = list(sensors.keys())
|
||||||
raise RuntimeError(f"{self}: no color sensor found. Available sensors: {available}")
|
raise RuntimeError(
|
||||||
|
f"{self}: manual color controls require a dedicated 'RGB Camera' module, which this camera does not have. ",
|
||||||
|
f"Available sensors: {available}.",
|
||||||
|
)
|
||||||
|
|
||||||
def _set_sensor_option(self, sensor: "rs.sensor", option: "rs.option", value: float, label: str) -> None:
|
def _set_sensor_option(self, sensor: "rs.sensor", option: "rs.option", value: float, label: str) -> None:
|
||||||
"""Sets a sensor option, re-raising range errors with actionable diagnostics."""
|
"""Sets a sensor option, re-raising range errors with actionable diagnostics."""
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ else:
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
from lerobot.lerobot_types import PolicyAction
|
||||||
from lerobot.processor import PolicyProcessorPipeline
|
from lerobot.processor import PolicyProcessorPipeline
|
||||||
from lerobot.robots import Robot
|
from lerobot.robots import Robot
|
||||||
from lerobot.types import PolicyAction
|
|
||||||
|
|
||||||
|
|
||||||
def predict_action(
|
def predict_action(
|
||||||
|
|||||||
@@ -52,6 +52,17 @@ def get_step_checkpoint_dir(output_dir: Path, total_steps: int, step: int) -> Pa
|
|||||||
return output_dir / CHECKPOINTS_DIR / step_identifier
|
return output_dir / CHECKPOINTS_DIR / step_identifier
|
||||||
|
|
||||||
|
|
||||||
|
def should_save_checkpoint(step: int, save_freq: int, total_steps: int) -> bool:
|
||||||
|
"""Whether a checkpoint should be saved at ``step``.
|
||||||
|
|
||||||
|
A checkpoint is saved every ``save_freq`` steps and always after the final step. A
|
||||||
|
non-positive ``save_freq`` disables periodic saving (only the final checkpoint is
|
||||||
|
written), mirroring how ``log_freq``/``eval_freq`` treat non-positive values and
|
||||||
|
avoiding a ``ZeroDivisionError`` from ``step % 0``.
|
||||||
|
"""
|
||||||
|
return (save_freq > 0 and step % save_freq == 0) or step == total_steps
|
||||||
|
|
||||||
|
|
||||||
def save_training_step(
|
def save_training_step(
|
||||||
step: int, save_dir: Path, num_processes: int | None = None, batch_size: int | None = None
|
step: int, save_dir: Path, num_processes: int | None = None, batch_size: int | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -163,8 +163,10 @@ class PreTrainedConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC): # type: igno
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, PreTrainedConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ class RewardModelConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, RewardModelConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ class TrainPipelineConfig(HubMixin):
|
|||||||
tolerance_s: float = 1e-4
|
tolerance_s: float = 1e-4
|
||||||
save_checkpoint: bool = True
|
save_checkpoint: bool = True
|
||||||
# Checkpoint is saved every `save_freq` training iterations and after the last training step.
|
# Checkpoint is saved every `save_freq` training iterations and after the last training step.
|
||||||
|
# A non-positive value disables periodic saving, keeping only the final checkpoint.
|
||||||
save_freq: int = 20_000
|
save_freq: int = 20_000
|
||||||
use_policy_training_preset: bool = True
|
use_policy_training_preset: bool = True
|
||||||
optimizer: OptimizerConfig | None = None
|
optimizer: OptimizerConfig | None = None
|
||||||
@@ -194,7 +195,11 @@ class TrainPipelineConfig(HubMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if Path(config_path).resolve().exists():
|
if Path(config_path).resolve().exists():
|
||||||
policy_dir = Path(config_path).parent
|
# `config_path` may point at the checkpoint's train_config.json or at its
|
||||||
|
# pretrained_model/ directory (both documented above) — resolve either to
|
||||||
|
# the pretrained_model/ directory.
|
||||||
|
config_path_obj = Path(config_path)
|
||||||
|
policy_dir = config_path_obj.parent if config_path_obj.is_file() else config_path_obj
|
||||||
self.checkpoint_path = policy_dir.parent
|
self.checkpoint_path = policy_dir.parent
|
||||||
elif self.job.is_remote:
|
elif self.job.is_remote:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -386,6 +386,9 @@ class DatasetWriter:
|
|||||||
self._episodes_since_last_encoding = 0
|
self._episodes_since_last_encoding = 0
|
||||||
|
|
||||||
if episode_data is None:
|
if episode_data is None:
|
||||||
|
# Post-save cleanup deliberately does not go through clear_episode_buffer():
|
||||||
|
# staging frames of video cameras must survive here — the (possibly batched)
|
||||||
|
# encoder still needs them and deletes them once each video is written.
|
||||||
if len(self._meta.image_keys) > 0:
|
if len(self._meta.image_keys) > 0:
|
||||||
self._delete_camera_frame_dirs(self._meta.image_keys)
|
self._delete_camera_frame_dirs(self._meta.image_keys)
|
||||||
self.episode_buffer = self._create_episode_buffer()
|
self.episode_buffer = self._create_episode_buffer()
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from collections.abc import Sequence
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType
|
from lerobot.configs import PipelineFeatureType
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.processor import DataProcessorPipeline
|
from lerobot.processor import DataProcessorPipeline
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import ACTION, OBS_IMAGES, OBS_STATE, OBS_STR
|
from lerobot.utils.constants import ACTION, OBS_IMAGES, OBS_STATE, OBS_STR
|
||||||
from lerobot.utils.feature_utils import hw_to_dataset_features
|
from lerobot.utils.feature_utils import hw_to_dataset_features
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ class LookAheadError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _ShardExhaustedError(Exception):
|
||||||
|
"""Raised when a streaming dataset shard has no more items."""
|
||||||
|
|
||||||
|
|
||||||
class Backtrackable[T]:
|
class Backtrackable[T]:
|
||||||
"""
|
"""
|
||||||
Wrap any iterator/iterable so you can step back up to `history` items
|
Wrap any iterator/iterable so you can step back up to `history` items
|
||||||
@@ -178,7 +182,7 @@ class Backtrackable[T]:
|
|||||||
"""
|
"""
|
||||||
Check if we can go back `steps` items without raising an IndexError.
|
Check if we can go back `steps` items without raising an IndexError.
|
||||||
"""
|
"""
|
||||||
return steps <= len(self._back_buf) + self._cursor
|
return steps < len(self._back_buf) + self._cursor
|
||||||
|
|
||||||
def can_peek_ahead(self, steps: int = 1) -> bool:
|
def can_peek_ahead(self, steps: int = 1) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -422,10 +426,7 @@ class StreamingLeRobotDataset(torch.utils.data.IterableDataset):
|
|||||||
else:
|
else:
|
||||||
frames_buffer.append(frame)
|
frames_buffer.append(frame)
|
||||||
break # random shard sampled, switch shard
|
break # random shard sampled, switch shard
|
||||||
except (
|
except _ShardExhaustedError:
|
||||||
RuntimeError,
|
|
||||||
StopIteration,
|
|
||||||
): # NOTE: StopIteration inside a generator throws a RuntimeError since python 3.7
|
|
||||||
del idx_to_backtrack_dataset[shard_key] # Remove exhausted shard, onto another shard
|
del idx_to_backtrack_dataset[shard_key] # Remove exhausted shard, onto another shard
|
||||||
|
|
||||||
# Once shards are all exhausted, shuffle the buffer and yield the remaining frames
|
# Once shards are all exhausted, shuffle the buffer and yield the remaining frames
|
||||||
@@ -503,7 +504,11 @@ class StreamingLeRobotDataset(torch.utils.data.IterableDataset):
|
|||||||
|
|
||||||
def make_frame(self, dataset_iterator: Backtrackable) -> Generator:
|
def make_frame(self, dataset_iterator: Backtrackable) -> Generator:
|
||||||
"""Makes a frame starting from a dataset iterator"""
|
"""Makes a frame starting from a dataset iterator"""
|
||||||
item = next(dataset_iterator)
|
try:
|
||||||
|
item = next(dataset_iterator)
|
||||||
|
except StopIteration as e:
|
||||||
|
# Translate exhaustion here, before PEP 479 turns it into an indistinguishable RuntimeError.
|
||||||
|
raise _ShardExhaustedError from e
|
||||||
item = item_to_torch(item)
|
item = item_to_torch(item)
|
||||||
|
|
||||||
updates = [] # list of "updates" to apply to the item retrieved from hf_dataset (w/o camera features)
|
updates = [] # list of "updates" to apply to the item retrieved from hf_dataset (w/o camera features)
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ class MetaworldEnv(EnvConfig):
|
|||||||
class RoboCasaEnv(EnvConfig):
|
class RoboCasaEnv(EnvConfig):
|
||||||
task: str = "CloseFridge"
|
task: str = "CloseFridge"
|
||||||
fps: int = 20
|
fps: int = 20
|
||||||
episode_length: int = 1000
|
episode_length: int | None = None
|
||||||
obs_type: str = "pixels_agent_pos"
|
obs_type: str = "pixels_agent_pos"
|
||||||
render_mode: str = "rgb_array"
|
render_mode: str = "rgb_array"
|
||||||
camera_name: str = "robot0_agentview_left,robot0_eye_in_hand,robot0_agentview_right"
|
camera_name: str = "robot0_agentview_left,robot0_eye_in_hand,robot0_agentview_right"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from gymnasium import spaces
|
|||||||
from libero.libero import benchmark, get_libero_path
|
from libero.libero import benchmark, get_libero_path
|
||||||
from libero.libero.envs import OffScreenRenderEnv
|
from libero.libero.envs import OffScreenRenderEnv
|
||||||
|
|
||||||
from lerobot.types import RobotObservation
|
from lerobot.lerobot_types import RobotObservation
|
||||||
|
|
||||||
from .utils import _LazyAsyncVectorEnv, parse_camera_names
|
from .utils import _LazyAsyncVectorEnv, parse_camera_names
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import metaworld.policies as policies
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from gymnasium import spaces
|
from gymnasium import spaces
|
||||||
|
|
||||||
from lerobot.types import RobotObservation
|
from lerobot.lerobot_types import RobotObservation
|
||||||
|
|
||||||
from .utils import _LazyAsyncVectorEnv
|
from .utils import _LazyAsyncVectorEnv
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import gymnasium as gym
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from gymnasium import spaces
|
from gymnasium import spaces
|
||||||
|
|
||||||
from lerobot.types import RobotObservation
|
from lerobot.lerobot_types import RobotObservation
|
||||||
|
|
||||||
from .utils import _LazyAsyncVectorEnv, parse_camera_names
|
from .utils import _LazyAsyncVectorEnv, parse_camera_names
|
||||||
|
|
||||||
@@ -98,6 +98,19 @@ def _resolve_tasks(task: str) -> tuple[list[str], str | None]:
|
|||||||
return names, None
|
return names, None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_task_horizon(task: str) -> int:
|
||||||
|
"""Return the rollout horizon registered by RoboCasa for a task."""
|
||||||
|
from robocasa.utils.dataset_registry_utils import get_task_horizon
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(get_task_horizon(task))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
f"No RoboCasa horizon is registered for task '{task}'. "
|
||||||
|
"Set `--env.episode_length=<steps>` explicitly."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def convert_action(flat_action: np.ndarray) -> dict[str, Any]:
|
def convert_action(flat_action: np.ndarray) -> dict[str, Any]:
|
||||||
"""Split a flat (12,) action vector into a RoboCasa action dict.
|
"""Split a flat (12,) action vector into a RoboCasa action dict.
|
||||||
|
|
||||||
@@ -154,7 +167,7 @@ class RoboCasaEnv(gym.Env):
|
|||||||
|
|
||||||
self.camera_name = parse_camera_names(camera_name)
|
self.camera_name = parse_camera_names(camera_name)
|
||||||
|
|
||||||
self._max_episode_steps = episode_length if episode_length is not None else 1000
|
self._max_episode_steps = episode_length if episode_length is not None else _get_task_horizon(task)
|
||||||
|
|
||||||
# Deferred — created on first reset() inside the worker subprocess
|
# Deferred — created on first reset() inside the worker subprocess
|
||||||
# to avoid inheriting stale GPU/EGL contexts across fork().
|
# to avoid inheriting stale GPU/EGL contexts across fork().
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
from gymnasium import spaces
|
from gymnasium import spaces
|
||||||
|
|
||||||
from lerobot.types import RobotObservation
|
from lerobot.lerobot_types import RobotObservation
|
||||||
from lerobot.utils.import_utils import _scipy_available
|
from lerobot.utils.import_utils import _scipy_available
|
||||||
|
|
||||||
from .utils import _LazyAsyncVectorEnv
|
from .utils import _LazyAsyncVectorEnv
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import numpy as np
|
|||||||
from gymnasium import spaces
|
from gymnasium import spaces
|
||||||
from scipy.spatial.transform import Rotation
|
from scipy.spatial.transform import Rotation
|
||||||
|
|
||||||
from lerobot.types import RobotObservation
|
from lerobot.lerobot_types import RobotObservation
|
||||||
|
|
||||||
from .utils import _LazyAsyncVectorEnv
|
from .utils import _LazyAsyncVectorEnv
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs.types import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
ComplementaryDataProcessorStep,
|
ComplementaryDataProcessorStep,
|
||||||
PolicyAction,
|
PolicyAction,
|
||||||
@@ -31,7 +32,6 @@ from lerobot.processor import (
|
|||||||
make_default_policy_processor_steps,
|
make_default_policy_processor_steps,
|
||||||
make_policy_processor_pipelines,
|
make_policy_processor_pipelines,
|
||||||
)
|
)
|
||||||
from lerobot.types import TransitionKey
|
|
||||||
from lerobot.utils.constants import OBS_STATE
|
from lerobot.utils.constants import OBS_STATE
|
||||||
from lerobot.utils.import_utils import _transformers_available, require_package
|
from lerobot.utils.import_utils import _transformers_available, require_package
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ class Evo1Policy(PreTrainedPolicy):
|
|||||||
config_class = Evo1Config
|
config_class = Evo1Config
|
||||||
name = "evo1"
|
name = "evo1"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, config: Evo1Config, *, vlm_hub_kwargs: dict | None = None, **kwargs):
|
def __init__(self, config: Evo1Config, *, vlm_hub_kwargs: dict | None = None, **kwargs):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
config.validate_features()
|
config.validate_features()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from typing import Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
DeviceProcessorStep,
|
DeviceProcessorStep,
|
||||||
@@ -40,7 +41,6 @@ from lerobot.processor.converters import (
|
|||||||
policy_action_to_transition,
|
policy_action_to_transition,
|
||||||
transition_to_policy_action,
|
transition_to_policy_action,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
ACTION,
|
ACTION,
|
||||||
DONE,
|
DONE,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from lerobot.configs import FeatureType, PreTrainedConfig
|
from lerobot.configs import FeatureType, PreTrainedConfig
|
||||||
from lerobot.envs import EnvConfig, env_to_policy_features
|
from lerobot.envs import EnvConfig, env_to_policy_features
|
||||||
|
from lerobot.lerobot_types import PolicyAction
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AbsoluteActionsProcessorStep,
|
AbsoluteActionsProcessorStep,
|
||||||
PolicyProcessorPipeline,
|
PolicyProcessorPipeline,
|
||||||
@@ -37,7 +38,6 @@ from lerobot.processor import (
|
|||||||
transition_to_batch,
|
transition_to_batch,
|
||||||
transition_to_policy_action,
|
transition_to_policy_action,
|
||||||
)
|
)
|
||||||
from lerobot.types import PolicyAction
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
ACTION,
|
ACTION,
|
||||||
POLICY_POSTPROCESSOR_DEFAULT_NAME,
|
POLICY_POSTPROCESSOR_DEFAULT_NAME,
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ class GrootPolicy(PreTrainedPolicy):
|
|||||||
name = "groot"
|
name = "groot"
|
||||||
config_class = GrootConfig
|
config_class = GrootConfig
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, config: GrootConfig, **kwargs):
|
def __init__(self, config: GrootConfig, **kwargs):
|
||||||
"""Initialize Groot policy wrapper."""
|
"""Initialize Groot policy wrapper."""
|
||||||
require_package("transformers", extra="groot")
|
require_package("transformers", extra="groot")
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ if TYPE_CHECKING or _datasets_available:
|
|||||||
else:
|
else:
|
||||||
LeRobotDataset = None
|
LeRobotDataset = None
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AbsoluteActionsProcessorStep,
|
AbsoluteActionsProcessorStep,
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
@@ -66,7 +67,6 @@ from lerobot.processor import (
|
|||||||
transition_to_batch,
|
transition_to_batch,
|
||||||
transition_to_policy_action,
|
transition_to_policy_action,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
ACTION,
|
ACTION,
|
||||||
OBS_IMAGE,
|
OBS_IMAGE,
|
||||||
|
|||||||
@@ -520,6 +520,9 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
|||||||
config_class = MolmoAct2Config
|
config_class = MolmoAct2Config
|
||||||
name = "molmoact2"
|
name = "molmoact2"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return self.config.inference_action_mode == "continuous"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: MolmoAct2Config,
|
config: MolmoAct2Config,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
DeviceProcessorStep,
|
DeviceProcessorStep,
|
||||||
@@ -49,7 +50,6 @@ from lerobot.processor import (
|
|||||||
policy_action_to_transition,
|
policy_action_to_transition,
|
||||||
transition_to_policy_action,
|
transition_to_policy_action,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
ACTION,
|
ACTION,
|
||||||
OBS_IMAGES,
|
OBS_IMAGES,
|
||||||
|
|||||||
@@ -749,6 +749,9 @@ class PI0Policy(PreTrainedPolicy):
|
|||||||
config_class = PI0Config
|
config_class = PI0Config
|
||||||
name = "pi0"
|
name = "pi0"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: PI0Config,
|
config: PI0Config,
|
||||||
|
|||||||
@@ -714,6 +714,9 @@ class PI05Policy(PreTrainedPolicy):
|
|||||||
config_class = PI05Config
|
config_class = PI05Config
|
||||||
name = "pi05"
|
name = "pi05"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: PI05Config,
|
config: PI05Config,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AbsoluteActionsProcessorStep,
|
AbsoluteActionsProcessorStep,
|
||||||
PolicyAction,
|
PolicyAction,
|
||||||
@@ -33,7 +34,6 @@ from lerobot.processor import (
|
|||||||
make_default_policy_processor_steps,
|
make_default_policy_processor_steps,
|
||||||
make_policy_processor_pipelines,
|
make_policy_processor_pipelines,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import OBS_STATE
|
from lerobot.utils.constants import OBS_STATE
|
||||||
|
|
||||||
from .configuration_pi05 import PI05Config
|
from .configuration_pi05 import PI05Config
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AbsoluteActionsProcessorStep,
|
AbsoluteActionsProcessorStep,
|
||||||
ActionTokenizerProcessorStep,
|
ActionTokenizerProcessorStep,
|
||||||
@@ -34,7 +35,6 @@ from lerobot.processor import (
|
|||||||
make_default_policy_processor_steps,
|
make_default_policy_processor_steps,
|
||||||
make_policy_processor_pipelines,
|
make_policy_processor_pipelines,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import OBS_STATE
|
from lerobot.utils.constants import OBS_STATE
|
||||||
|
|
||||||
from .configuration_pi0_fast import PI0FastConfig
|
from .configuration_pi0_fast import PI0FastConfig
|
||||||
|
|||||||
@@ -249,6 +249,10 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
"""Whether this policy implements Real-Time Chunking inference semantics."""
|
||||||
|
return False
|
||||||
|
|
||||||
# TODO(aliberts, rcadene): split into 'forward' and 'compute_loss'?
|
# TODO(aliberts, rcadene): split into 'forward' and 'compute_loss'?
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
|||||||
config_class = SmolVLAConfig
|
config_class = SmolVLAConfig
|
||||||
name = "smolvla"
|
name = "smolvla"
|
||||||
|
|
||||||
|
def supports_rtc(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: SmolVLAConfig,
|
config: SmolVLAConfig,
|
||||||
|
|||||||
@@ -168,14 +168,23 @@ class SmolVLMWithExpertModel(nn.Module):
|
|||||||
last_layers.append(self.num_vlm_layers - 2)
|
last_layers.append(self.num_vlm_layers - 2)
|
||||||
frozen_layers = [
|
frozen_layers = [
|
||||||
"lm_head",
|
"lm_head",
|
||||||
"text_model.model.norm.weight",
|
"text_model.norm.weight",
|
||||||
]
|
]
|
||||||
for layer in last_layers:
|
for layer in last_layers:
|
||||||
frozen_layers.append(f"text_model.model.layers.{layer}.")
|
frozen_layers.append(f"text_model.layers.{layer}.")
|
||||||
|
|
||||||
|
unmatched_patterns = set(frozen_layers)
|
||||||
for name, params in self.vlm.named_parameters():
|
for name, params in self.vlm.named_parameters():
|
||||||
if any(k in name for k in frozen_layers):
|
matched_patterns = [k for k in frozen_layers if k in name]
|
||||||
|
if matched_patterns:
|
||||||
params.requires_grad = False
|
params.requires_grad = False
|
||||||
|
unmatched_patterns.difference_update(matched_patterns)
|
||||||
|
if unmatched_patterns:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Some frozen layer patterns matched no VLM parameters, so the corresponding layers "
|
||||||
|
"would silently remain trainable (parameter naming may have changed in transformers): "
|
||||||
|
f"{sorted(unmatched_patterns)}"
|
||||||
|
)
|
||||||
# To avoid unused params issue with distributed training
|
# To avoid unused params issue with distributed training
|
||||||
for name, params in self.lm_expert.named_parameters():
|
for name, params in self.lm_expert.named_parameters():
|
||||||
if "lm_head" in name:
|
if "lm_head" in name:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import torch
|
|||||||
from torch import nn
|
from torch import nn
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PolicyFeature, PreTrainedConfig
|
from lerobot.configs import FeatureType, PolicyFeature, PreTrainedConfig
|
||||||
from lerobot.types import PolicyAction, RobotAction, RobotObservation
|
from lerobot.lerobot_types import PolicyAction, RobotAction, RobotObservation
|
||||||
from lerobot.utils.constants import ACTION, OBS_STR
|
from lerobot.utils.constants import ACTION, OBS_STR
|
||||||
from lerobot.utils.feature_utils import build_dataset_frame
|
from lerobot.utils.feature_utils import build_dataset_frame
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class XVLAModel(nn.Module):
|
|||||||
# Freeze or unfreeze policy transformer
|
# Freeze or unfreeze policy transformer
|
||||||
if not self.config.train_policy_transformer:
|
if not self.config.train_policy_transformer:
|
||||||
for name, param in self.transformer.named_parameters():
|
for name, param in self.transformer.named_parameters():
|
||||||
if "soft_prompts" not in name:
|
if "soft_prompt" not in name:
|
||||||
param.requires_grad = False
|
param.requires_grad = False
|
||||||
|
|
||||||
# Freeze or unfreeze soft prompts
|
# Freeze or unfreeze soft prompts
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
ObservationProcessorStep,
|
ObservationProcessorStep,
|
||||||
PolicyAction,
|
PolicyAction,
|
||||||
@@ -31,7 +32,6 @@ from lerobot.processor import (
|
|||||||
make_default_policy_processor_steps,
|
make_default_policy_processor_steps,
|
||||||
make_policy_processor_pipelines,
|
make_policy_processor_pipelines,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
IMAGENET_STATS,
|
IMAGENET_STATS,
|
||||||
OBS_IMAGES,
|
OBS_IMAGES,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from lerobot.types import (
|
from lerobot.lerobot_types import (
|
||||||
EnvAction,
|
EnvAction,
|
||||||
EnvTransition,
|
EnvTransition,
|
||||||
PolicyAction,
|
PolicyAction,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvTransition, PolicyAction
|
from lerobot.lerobot_types import EnvTransition, PolicyAction
|
||||||
from lerobot.utils.constants import OBS_ENV_STATE, OBS_IMAGE, OBS_IMAGES, OBS_STATE
|
from lerobot.utils.constants import OBS_ENV_STATE, OBS_IMAGE, OBS_IMAGES, OBS_STATE
|
||||||
|
|
||||||
from .pipeline import (
|
from .pipeline import (
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.types import EnvTransition, PolicyAction, RobotAction, RobotObservation, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, PolicyAction, RobotAction, RobotObservation, TransitionKey
|
||||||
from lerobot.utils.constants import ACTION, DONE, INFO, OBS_PREFIX, REWARD, TRUNCATED
|
from lerobot.utils.constants import ACTION, DONE, INFO, OBS_PREFIX, REWARD, TRUNCATED
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import PolicyAction, RobotAction
|
from lerobot.lerobot_types import PolicyAction, RobotAction
|
||||||
|
|
||||||
from .pipeline import ActionProcessorStep, ProcessorStepRegistry, RobotActionProcessorStep
|
from .pipeline import ActionProcessorStep, ProcessorStepRegistry, RobotActionProcessorStep
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from typing import Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvTransition, PolicyAction, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, PolicyAction, TransitionKey
|
||||||
from lerobot.utils.device_utils import get_safe_torch_device
|
from lerobot.utils.device_utils import get_safe_torch_device
|
||||||
|
|
||||||
from .pipeline import ProcessorStep, ProcessorStepRegistry
|
from .pipeline import ProcessorStep, ProcessorStepRegistry
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from typing import Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs.policies import PreTrainedConfig
|
from lerobot.configs.policies import PreTrainedConfig
|
||||||
from lerobot.types import PolicyAction, RobotAction, RobotObservation
|
from lerobot.lerobot_types import PolicyAction, RobotAction, RobotObservation
|
||||||
from lerobot.utils.constants import POLICY_POSTPROCESSOR_DEFAULT_NAME, POLICY_PREPROCESSOR_DEFAULT_NAME
|
from lerobot.utils.constants import POLICY_POSTPROCESSOR_DEFAULT_NAME, POLICY_PREPROCESSOR_DEFAULT_NAME
|
||||||
|
|
||||||
from .batch_processor import AddBatchDimensionProcessorStep
|
from .batch_processor import AddBatchDimensionProcessorStep
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvAction, EnvTransition, PolicyAction, TransitionKey
|
from lerobot.lerobot_types import EnvAction, EnvTransition, PolicyAction, TransitionKey
|
||||||
|
|
||||||
from .converters import to_tensor
|
from .converters import to_tensor
|
||||||
from .hil_processor import TELEOP_ACTION_KEY
|
from .hil_processor import TELEOP_ACTION_KEY
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from lerobot.teleoperators.utils import TeleopEvents
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lerobot.teleoperators.teleoperator import Teleoperator
|
from lerobot.teleoperators.teleoperator import Teleoperator
|
||||||
|
|
||||||
from lerobot.types import EnvTransition, PolicyAction, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, PolicyAction, TransitionKey
|
||||||
|
|
||||||
from .pipeline import (
|
from .pipeline import (
|
||||||
ComplementaryDataProcessorStep,
|
ComplementaryDataProcessorStep,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, NormalizationMode, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, NormalizationMode, PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvTransition, PolicyAction, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, PolicyAction, TransitionKey
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
|||||||
@@ -45,7 +45,14 @@ from huggingface_hub import hf_hub_download
|
|||||||
from safetensors.torch import load_file, save_file
|
from safetensors.torch import load_file, save_file
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvAction, EnvTransition, PolicyAction, RobotAction, RobotObservation, TransitionKey
|
from lerobot.lerobot_types import (
|
||||||
|
EnvAction,
|
||||||
|
EnvTransition,
|
||||||
|
PolicyAction,
|
||||||
|
RobotAction,
|
||||||
|
RobotObservation,
|
||||||
|
TransitionKey,
|
||||||
|
)
|
||||||
from lerobot.utils.constants import HF_LEROBOT_HOME
|
from lerobot.utils.constants import HF_LEROBOT_HOME
|
||||||
from lerobot.utils.hub import HubMixin
|
from lerobot.utils.hub import HubMixin
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from typing import Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import PolicyAction, RobotAction
|
from lerobot.lerobot_types import PolicyAction, RobotAction
|
||||||
from lerobot.utils.constants import ACTION
|
from lerobot.utils.constants import ACTION
|
||||||
|
|
||||||
from .pipeline import ActionProcessorStep, ProcessorStepRegistry
|
from .pipeline import ActionProcessorStep, ProcessorStepRegistry
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.utils.constants import OBS_STATE
|
from lerobot.utils.constants import OBS_STATE
|
||||||
|
|
||||||
from .delta_action_processor import MapDeltaActionToRobotActionStep, MapTensorToDeltaActionDictStep
|
from .delta_action_processor import MapDeltaActionToRobotActionStep, MapTensorToDeltaActionDictStep
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from lerobot.configs import PipelineFeatureType, PolicyFeature
|
|||||||
from lerobot.configs.recipe import TrainingRecipe
|
from lerobot.configs.recipe import TrainingRecipe
|
||||||
from lerobot.datasets.language import LANGUAGE_EVENTS, LANGUAGE_PERSISTENT
|
from lerobot.datasets.language import LANGUAGE_EVENTS, LANGUAGE_PERSISTENT
|
||||||
from lerobot.datasets.language_render import render_sample
|
from lerobot.datasets.language_render import render_sample
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.utils.utils import unwrap_scalar
|
from lerobot.utils.utils import unwrap_scalar
|
||||||
|
|
||||||
from .pipeline import ProcessorStep, ProcessorStepRegistry
|
from .pipeline import ProcessorStep, ProcessorStepRegistry
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
from lerobot.types import EnvTransition, RobotObservation, TransitionKey
|
from lerobot.lerobot_types import EnvTransition, RobotObservation, TransitionKey
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
ACTION_TOKEN_MASK,
|
ACTION_TOKEN_MASK,
|
||||||
ACTION_TOKENS,
|
ACTION_TOKENS,
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ import torch
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
from lerobot.lerobot_types import TransitionKey
|
||||||
from lerobot.rewards.robometer.configuration_robometer import RobometerConfig
|
from lerobot.rewards.robometer.configuration_robometer import RobometerConfig
|
||||||
from lerobot.rewards.robometer.modeling_robometer import RobometerRewardModel
|
from lerobot.rewards.robometer.modeling_robometer import RobometerRewardModel
|
||||||
from lerobot.rewards.robometer.processor_robometer import RobometerEncoderProcessorStep
|
from lerobot.rewards.robometer.processor_robometer import RobometerEncoderProcessorStep
|
||||||
from lerobot.types import TransitionKey
|
|
||||||
|
|
||||||
DEFAULT_OUTPUT_FILENAME = "robometer_progress.parquet"
|
DEFAULT_OUTPUT_FILENAME = "robometer_progress.parquet"
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from PIL import Image
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
DeviceProcessorStep,
|
DeviceProcessorStep,
|
||||||
@@ -39,7 +40,6 @@ from lerobot.rewards.robometer.configuration_robometer import (
|
|||||||
RobometerConfig,
|
RobometerConfig,
|
||||||
)
|
)
|
||||||
from lerobot.rewards.robometer.modeling_robometer import ROBOMETER_FEATURE_PREFIX
|
from lerobot.rewards.robometer.modeling_robometer import ROBOMETER_FEATURE_PREFIX
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
OBS_IMAGES,
|
OBS_IMAGES,
|
||||||
POLICY_POSTPROCESSOR_DEFAULT_NAME,
|
POLICY_POSTPROCESSOR_DEFAULT_NAME,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ else:
|
|||||||
Faker = None # type: ignore[assignment, misc]
|
Faker = None # type: ignore[assignment, misc]
|
||||||
|
|
||||||
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, PolicyAction, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
DeviceProcessorStep,
|
DeviceProcessorStep,
|
||||||
@@ -58,7 +59,6 @@ from lerobot.processor import (
|
|||||||
policy_action_to_transition,
|
policy_action_to_transition,
|
||||||
transition_to_policy_action,
|
transition_to_policy_action,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, PolicyAction, TransitionKey
|
|
||||||
from lerobot.utils.constants import POLICY_POSTPROCESSOR_DEFAULT_NAME, POLICY_PREPROCESSOR_DEFAULT_NAME
|
from lerobot.utils.constants import POLICY_POSTPROCESSOR_DEFAULT_NAME, POLICY_PREPROCESSOR_DEFAULT_NAME
|
||||||
|
|
||||||
from .configuration_sarm import SARMConfig
|
from .configuration_sarm import SARMConfig
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ import torch
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from lerobot.datasets import LeRobotDataset
|
from lerobot.datasets import LeRobotDataset
|
||||||
|
from lerobot.lerobot_types import TransitionKey
|
||||||
from lerobot.rewards.topreward.configuration_topreward import TOPRewardConfig
|
from lerobot.rewards.topreward.configuration_topreward import TOPRewardConfig
|
||||||
from lerobot.rewards.topreward.modeling_topreward import TOPRewardModel
|
from lerobot.rewards.topreward.modeling_topreward import TOPRewardModel
|
||||||
from lerobot.rewards.topreward.processor_topreward import TOPRewardEncoderProcessorStep
|
from lerobot.rewards.topreward.processor_topreward import TOPRewardEncoderProcessorStep
|
||||||
from lerobot.types import TransitionKey
|
|
||||||
|
|
||||||
DEFAULT_OUTPUT_FILENAME = "topreward_progress.parquet"
|
DEFAULT_OUTPUT_FILENAME = "topreward_progress.parquet"
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
|
|
||||||
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
from lerobot.configs import PipelineFeatureType, PolicyFeature
|
||||||
|
from lerobot.lerobot_types import EnvTransition, TransitionKey
|
||||||
from lerobot.processor import (
|
from lerobot.processor import (
|
||||||
AddBatchDimensionProcessorStep,
|
AddBatchDimensionProcessorStep,
|
||||||
DeviceProcessorStep,
|
DeviceProcessorStep,
|
||||||
@@ -37,7 +38,6 @@ from lerobot.rewards.topreward.configuration_topreward import (
|
|||||||
DEFAULT_PROMPT_SUFFIX_TEMPLATE,
|
DEFAULT_PROMPT_SUFFIX_TEMPLATE,
|
||||||
TOPRewardConfig,
|
TOPRewardConfig,
|
||||||
)
|
)
|
||||||
from lerobot.types import EnvTransition, TransitionKey
|
|
||||||
from lerobot.utils.constants import (
|
from lerobot.utils.constants import (
|
||||||
OBS_IMAGES,
|
OBS_IMAGES,
|
||||||
OBS_PREFIX,
|
OBS_PREFIX,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from huggingface_hub.errors import HfHubHTTPError
|
|||||||
from safetensors.torch import load_file as load_safetensors, save_file as save_safetensors
|
from safetensors.torch import load_file as load_safetensors, save_file as save_safetensors
|
||||||
from torch.optim import Optimizer
|
from torch.optim import Optimizer
|
||||||
|
|
||||||
from lerobot.types import BatchType
|
from lerobot.lerobot_types import BatchType
|
||||||
from lerobot.utils.hub import HubMixin
|
from lerobot.utils.hub import HubMixin
|
||||||
|
|
||||||
from .configs import RLAlgorithmConfig, TrainingStats
|
from .configs import RLAlgorithmConfig, TrainingStats
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import builtins
|
import builtins
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -78,8 +79,10 @@ class RLAlgorithmConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC):
|
|||||||
|
|
||||||
def _save_pretrained(self, save_directory: Path) -> None:
|
def _save_pretrained(self, save_directory: Path) -> None:
|
||||||
"""Serialize this config as ``config.json`` inside ``save_directory``."""
|
"""Serialize this config as ``config.json`` inside ``save_directory``."""
|
||||||
with open(save_directory / CONFIG_NAME, "w") as f, draccus.config_type("json"):
|
# Encode against the base class so draccus includes the choice "type" key,
|
||||||
draccus.dump(self, f, indent=4)
|
# which `from_pretrained` needs to resolve the concrete subclass.
|
||||||
|
with open(save_directory / CONFIG_NAME, "w") as f:
|
||||||
|
json.dump(draccus.encode(self, RLAlgorithmConfig), f, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pretrained(
|
def from_pretrained(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import torch.nn.functional as F # noqa: N812
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
from torch.optim import Optimizer
|
from torch.optim import Optimizer
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import BatchType
|
||||||
from lerobot.policies.gaussian_actor.modeling_gaussian_actor import (
|
from lerobot.policies.gaussian_actor.modeling_gaussian_actor import (
|
||||||
DISCRETE_DIMENSION_INDEX,
|
DISCRETE_DIMENSION_INDEX,
|
||||||
MLP,
|
MLP,
|
||||||
@@ -35,7 +36,6 @@ from lerobot.policies.gaussian_actor.modeling_gaussian_actor import (
|
|||||||
orthogonal_init,
|
orthogonal_init,
|
||||||
)
|
)
|
||||||
from lerobot.policies.utils import get_device_from_parameters
|
from lerobot.policies.utils import get_device_from_parameters
|
||||||
from lerobot.types import BatchType
|
|
||||||
from lerobot.utils.constants import ACTION
|
from lerobot.utils.constants import ACTION
|
||||||
from lerobot.utils.transition import move_state_dict_to_device
|
from lerobot.utils.transition import move_state_dict_to_device
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from lerobot.types import BatchType
|
from lerobot.lerobot_types import BatchType
|
||||||
|
|
||||||
from .data_mixer import DataMixer, OnlineOfflineMixer
|
from .data_mixer import DataMixer, OnlineOfflineMixer
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from lerobot.types import BatchType
|
from lerobot.lerobot_types import BatchType
|
||||||
|
|
||||||
from ..buffer import ReplayBuffer, concatenate_batch_transitions
|
from ..buffer import ReplayBuffer, concatenate_batch_transitions
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lerobot.types import BatchType
|
from lerobot.lerobot_types import BatchType
|
||||||
|
|
||||||
from .algorithms.base import RLAlgorithm
|
from .algorithms.base import RLAlgorithm
|
||||||
from .algorithms.configs import TrainingStats
|
from .algorithms.configs import TrainingStats
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.bimanual import BimanualMixin
|
from lerobot.utils.bimanual import BimanualMixin
|
||||||
from lerobot.utils.decorators import check_if_not_connected
|
from lerobot.utils.decorators import check_if_not_connected
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.bimanual import BimanualMixin
|
from lerobot.utils.bimanual import BimanualMixin
|
||||||
from lerobot.utils.decorators import check_if_not_connected
|
from lerobot.utils.decorators import check_if_not_connected
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.bimanual import BimanualMixin
|
from lerobot.utils.bimanual import BimanualMixin
|
||||||
from lerobot.utils.decorators import check_if_not_connected
|
from lerobot.utils.decorators import check_if_not_connected
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ class BiSOFollower(BimanualMixin, Robot):
|
|||||||
position_i_coefficient=config.left_arm_config.position_i_coefficient,
|
position_i_coefficient=config.left_arm_config.position_i_coefficient,
|
||||||
position_d_coefficient=config.left_arm_config.position_d_coefficient,
|
position_d_coefficient=config.left_arm_config.position_d_coefficient,
|
||||||
use_degrees=config.left_arm_config.use_degrees,
|
use_degrees=config.left_arm_config.use_degrees,
|
||||||
|
num_read_retries=config.left_arm_config.num_read_retries,
|
||||||
cameras=left_arm_cameras,
|
cameras=left_arm_cameras,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ class BiSOFollower(BimanualMixin, Robot):
|
|||||||
position_i_coefficient=config.right_arm_config.position_i_coefficient,
|
position_i_coefficient=config.right_arm_config.position_i_coefficient,
|
||||||
position_d_coefficient=config.right_arm_config.position_d_coefficient,
|
position_d_coefficient=config.right_arm_config.position_d_coefficient,
|
||||||
use_degrees=config.right_arm_config.use_degrees,
|
use_degrees=config.right_arm_config.use_degrees,
|
||||||
|
num_read_retries=config.right_arm_config.num_read_retries,
|
||||||
cameras=config.right_arm_config.cameras,
|
cameras=config.right_arm_config.cameras,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
from lerobot.utils.errors import DeviceNotConnectedError
|
from lerobot.utils.errors import DeviceNotConnectedError
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import time
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorNormMode
|
from lerobot.motors import Motor, MotorNormMode
|
||||||
from lerobot.motors.calibration_gui import RangeFinderGUI
|
from lerobot.motors.calibration_gui import RangeFinderGUI
|
||||||
from lerobot.motors.feetech import (
|
from lerobot.motors.feetech import (
|
||||||
FeetechMotorsBus,
|
FeetechMotorsBus,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import time
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorNormMode
|
from lerobot.motors import Motor, MotorNormMode
|
||||||
from lerobot.motors.calibration_gui import RangeFinderGUI
|
from lerobot.motors.calibration_gui import RangeFinderGUI
|
||||||
from lerobot.motors.feetech import (
|
from lerobot.motors.feetech import (
|
||||||
FeetechMotorsBus,
|
FeetechMotorsBus,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import time
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||||
from lerobot.motors.dynamixel import (
|
from lerobot.motors.dynamixel import (
|
||||||
DynamixelMotorsBus,
|
DynamixelMotorsBus,
|
||||||
OperatingMode,
|
OperatingMode,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -23,10 +23,20 @@ from ..config import RobotConfig
|
|||||||
def lekiwi_cameras_config() -> dict[str, CameraConfig]:
|
def lekiwi_cameras_config() -> dict[str, CameraConfig]:
|
||||||
return {
|
return {
|
||||||
"front": OpenCVCameraConfig(
|
"front": OpenCVCameraConfig(
|
||||||
index_or_path="/dev/video0", fps=30, width=640, height=480, rotation=Cv2Rotation.ROTATE_180
|
index_or_path="/dev/video0",
|
||||||
|
fps=30,
|
||||||
|
width=640,
|
||||||
|
height=480,
|
||||||
|
fourcc="MJPG",
|
||||||
|
rotation=Cv2Rotation.ROTATE_180,
|
||||||
),
|
),
|
||||||
"wrist": OpenCVCameraConfig(
|
"wrist": OpenCVCameraConfig(
|
||||||
index_or_path="/dev/video2", fps=30, width=480, height=640, rotation=Cv2Rotation.ROTATE_90
|
index_or_path="/dev/video2",
|
||||||
|
fps=30,
|
||||||
|
width=480,
|
||||||
|
height=640,
|
||||||
|
fourcc="MJPG",
|
||||||
|
rotation=Cv2Rotation.ROTATE_90,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||||
from lerobot.motors.feetech import (
|
from lerobot.motors.feetech import (
|
||||||
FeetechMotorsBus,
|
FeetechMotorsBus,
|
||||||
OperatingMode,
|
OperatingMode,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
# TODO(aliberts, Steven, Pepijn): use gRPC calls instead of zmq?
|
# TODO(aliberts, Steven, Pepijn): use gRPC calls instead of zmq?
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@@ -22,7 +21,7 @@ from functools import cached_property
|
|||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.constants import ACTION, OBS_STATE
|
from lerobot.utils.constants import ACTION, OBS_STATE
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
from lerobot.utils.errors import DeviceNotConnectedError
|
from lerobot.utils.errors import DeviceNotConnectedError
|
||||||
@@ -134,7 +133,9 @@ class LeKiwiClient(Robot):
|
|||||||
self.zmq_observation_socket = self.zmq_context.socket(zmq.PULL)
|
self.zmq_observation_socket = self.zmq_context.socket(zmq.PULL)
|
||||||
zmq_observations_locator = f"tcp://{self.remote_ip}:{self.port_zmq_observations}"
|
zmq_observations_locator = f"tcp://{self.remote_ip}:{self.port_zmq_observations}"
|
||||||
self.zmq_observation_socket.connect(zmq_observations_locator)
|
self.zmq_observation_socket.connect(zmq_observations_locator)
|
||||||
self.zmq_observation_socket.setsockopt(zmq.CONFLATE, 1)
|
# CONFLATE does not support multipart messages; a small receive queue plus
|
||||||
|
# the existing drain-to-latest loop keeps newest-only semantics.
|
||||||
|
self.zmq_observation_socket.setsockopt(zmq.RCVHWM, 2)
|
||||||
|
|
||||||
poller = zmq.Poller()
|
poller = zmq.Poller()
|
||||||
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
||||||
@@ -147,8 +148,8 @@ class LeKiwiClient(Robot):
|
|||||||
def calibrate(self) -> None:
|
def calibrate(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _poll_and_get_latest_message(self) -> str | None:
|
def _poll_and_get_latest_message(self) -> list[bytes] | None:
|
||||||
"""Polls the ZMQ socket for a limited time and returns the latest message string."""
|
"""Polls the ZMQ socket for a limited time and returns the latest message's frames."""
|
||||||
zmq = self._zmq
|
zmq = self._zmq
|
||||||
poller = zmq.Poller()
|
poller = zmq.Poller()
|
||||||
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
poller.register(self.zmq_observation_socket, zmq.POLLIN)
|
||||||
@@ -166,7 +167,7 @@ class LeKiwiClient(Robot):
|
|||||||
last_msg = None
|
last_msg = None
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
msg = self.zmq_observation_socket.recv_string(zmq.NOBLOCK)
|
msg = self.zmq_observation_socket.recv_multipart(zmq.NOBLOCK)
|
||||||
last_msg = msg
|
last_msg = msg
|
||||||
except zmq.Again:
|
except zmq.Again:
|
||||||
break
|
break
|
||||||
@@ -176,28 +177,27 @@ class LeKiwiClient(Robot):
|
|||||||
|
|
||||||
return last_msg
|
return last_msg
|
||||||
|
|
||||||
def _parse_observation_json(self, obs_string: str) -> RobotObservation | None:
|
def _parse_observation(self, frames: list[bytes]) -> RobotObservation | None:
|
||||||
"""Parses the JSON observation string."""
|
"""Parses a multipart observation: JSON header + one raw JPEG frame per camera."""
|
||||||
try:
|
try:
|
||||||
return json.loads(obs_string)
|
header = json.loads(frames[0])
|
||||||
except json.JSONDecodeError as e:
|
cam_names = header.pop("_cams")
|
||||||
logging.error(f"Error decoding JSON observation: {e}")
|
observation: RobotObservation = header
|
||||||
|
for cam_name, jpeg in zip(cam_names, frames[1:], strict=True):
|
||||||
|
observation[cam_name] = jpeg
|
||||||
|
return observation
|
||||||
|
except (json.JSONDecodeError, KeyError, ValueError) as e:
|
||||||
|
logging.error(f"Error decoding observation: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _decode_image_from_b64(self, image_b64: str) -> np.ndarray | None:
|
def _decode_image(self, jpeg: bytes) -> np.ndarray | None:
|
||||||
"""Decodes a base64 encoded image string to an OpenCV image."""
|
"""Decodes a raw JPEG buffer to an OpenCV image."""
|
||||||
if not image_b64:
|
if not jpeg:
|
||||||
return None
|
|
||||||
try:
|
|
||||||
jpg_data = base64.b64decode(image_b64)
|
|
||||||
np_arr = np.frombuffer(jpg_data, dtype=np.uint8)
|
|
||||||
frame = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
|
|
||||||
if frame is None:
|
|
||||||
logging.warning("cv2.imdecode returned None for an image.")
|
|
||||||
return frame
|
|
||||||
except (TypeError, ValueError) as e:
|
|
||||||
logging.error(f"Error decoding base64 image data: {e}")
|
|
||||||
return None
|
return None
|
||||||
|
frame = cv2.imdecode(np.frombuffer(jpeg, dtype=np.uint8), cv2.IMREAD_COLOR)
|
||||||
|
if frame is None:
|
||||||
|
logging.warning("cv2.imdecode returned None for an image.")
|
||||||
|
return frame
|
||||||
|
|
||||||
def _remote_state_from_obs(
|
def _remote_state_from_obs(
|
||||||
self, observation: RobotObservation
|
self, observation: RobotObservation
|
||||||
@@ -212,10 +212,10 @@ class LeKiwiClient(Robot):
|
|||||||
|
|
||||||
# Decode images
|
# Decode images
|
||||||
current_frames: dict[str, np.ndarray] = {}
|
current_frames: dict[str, np.ndarray] = {}
|
||||||
for cam_name, image_b64 in observation.items():
|
for cam_name, jpeg in observation.items():
|
||||||
if cam_name not in self._cameras_ft:
|
if cam_name not in self._cameras_ft:
|
||||||
continue
|
continue
|
||||||
frame = self._decode_image_from_b64(image_b64)
|
frame = self._decode_image(jpeg)
|
||||||
if frame is not None:
|
if frame is not None:
|
||||||
current_frames[cam_name] = frame
|
current_frames[cam_name] = frame
|
||||||
|
|
||||||
@@ -230,15 +230,15 @@ class LeKiwiClient(Robot):
|
|||||||
If no new data arrives or decoding fails, returns the last known values.
|
If no new data arrives or decoding fails, returns the last known values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 1. Get the latest message string from the socket
|
# 1. Get the latest message's frames from the socket
|
||||||
latest_message_str = self._poll_and_get_latest_message()
|
latest_frames = self._poll_and_get_latest_message()
|
||||||
|
|
||||||
# 2. If no message, return cached data
|
# 2. If no message, return cached data
|
||||||
if latest_message_str is None:
|
if latest_frames is None:
|
||||||
return self.last_frames, self.last_remote_state
|
return self.last_frames, self.last_remote_state
|
||||||
|
|
||||||
# 3. Parse the JSON message
|
# 3. Parse the multipart message
|
||||||
observation = self._parse_observation_json(latest_message_str)
|
observation = self._parse_observation(latest_frames)
|
||||||
|
|
||||||
# 4. If JSON parsing failed, return cached data
|
# 4. If JSON parsing failed, return cached data
|
||||||
if observation is None:
|
if observation is None:
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import base64
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@@ -44,7 +43,9 @@ class LeKiwiHost:
|
|||||||
self.zmq_cmd_socket.bind(f"tcp://*:{config.port_zmq_cmd}")
|
self.zmq_cmd_socket.bind(f"tcp://*:{config.port_zmq_cmd}")
|
||||||
|
|
||||||
self.zmq_observation_socket = self.zmq_context.socket(zmq.PUSH)
|
self.zmq_observation_socket = self.zmq_context.socket(zmq.PUSH)
|
||||||
self.zmq_observation_socket.setsockopt(zmq.CONFLATE, 1)
|
# CONFLATE does not support multipart messages; a 2-deep send queue keeps
|
||||||
|
# near-latest-only semantics and sheds stale observations during stalls.
|
||||||
|
self.zmq_observation_socket.setsockopt(zmq.SNDHWM, 2)
|
||||||
self.zmq_observation_socket.bind(f"tcp://*:{config.port_zmq_observations}")
|
self.zmq_observation_socket.bind(f"tcp://*:{config.port_zmq_observations}")
|
||||||
|
|
||||||
self.connection_time_s = config.connection_time_s
|
self.connection_time_s = config.connection_time_s
|
||||||
@@ -99,19 +100,23 @@ def main(cfg: LeKiwiServerConfig):
|
|||||||
|
|
||||||
last_observation = robot.get_observation()
|
last_observation = robot.get_observation()
|
||||||
|
|
||||||
# Encode ndarrays to base64 strings
|
# Send one multipart message: a JSON header frame (state + camera
|
||||||
for cam_key, _ in robot.cameras.items():
|
# order) followed by one raw JPEG frame per camera. Raw JPEG avoids
|
||||||
ret, buffer = cv2.imencode(
|
# the 33% base64 inflation of embedding binary data in JSON.
|
||||||
".jpg", last_observation[cam_key], [int(cv2.IMWRITE_JPEG_QUALITY), 90]
|
cam_keys = list(robot.cameras.keys())
|
||||||
|
jpeg_frames = []
|
||||||
|
for cam_key in cam_keys:
|
||||||
|
ret, jpeg = cv2.imencode(
|
||||||
|
".jpg", last_observation.pop(cam_key), [int(cv2.IMWRITE_JPEG_QUALITY), 90]
|
||||||
)
|
)
|
||||||
if ret:
|
jpeg_frames.append(jpeg if ret else b"")
|
||||||
last_observation[cam_key] = base64.b64encode(buffer).decode("utf-8")
|
header = {"_cams": cam_keys, **last_observation}
|
||||||
else:
|
|
||||||
last_observation[cam_key] = ""
|
|
||||||
|
|
||||||
# Send the observation to the remote agent
|
# Send the observation to the remote agent
|
||||||
try:
|
try:
|
||||||
host.zmq_observation_socket.send_string(json.dumps(last_observation), flags=zmq.NOBLOCK)
|
host.zmq_observation_socket.send_multipart(
|
||||||
|
[json.dumps(header).encode()] + jpeg_frames, flags=zmq.NOBLOCK
|
||||||
|
)
|
||||||
except zmq.Again:
|
except zmq.Again:
|
||||||
logging.info("Dropping observation, no client connected")
|
logging.info("Dropping observation, no client connected")
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ import time
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||||
from lerobot.motors.dynamixel import (
|
from lerobot.motors.dynamixel import (
|
||||||
DriveMode,
|
DriveMode,
|
||||||
DynamixelMotorsBus,
|
DynamixelMotorsBus,
|
||||||
OperatingMode,
|
OperatingMode,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ from functools import cached_property
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||||
from lerobot.motors.damiao import DamiaoMotorsBus
|
from lerobot.motors.damiao import DamiaoMotorsBus
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import time
|
|||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.utils.import_utils import _reachy2_sdk_available, require_package
|
from lerobot.utils.import_utils import _reachy2_sdk_available, require_package
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ from functools import cached_property
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import MotorCalibration
|
from lerobot.motors import MotorCalibration
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
from lerobot.utils.import_utils import _motorbridge_available, require_package
|
from lerobot.utils.import_utils import _motorbridge_available, require_package
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
import draccus
|
import draccus
|
||||||
|
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import MotorCalibration
|
from lerobot.motors import MotorCalibration
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.constants import HF_LEROBOT_CALIBRATION, ROBOTS
|
from lerobot.utils.constants import HF_LEROBOT_CALIBRATION, ROBOTS
|
||||||
|
|
||||||
from .config import RobotConfig
|
from .config import RobotConfig
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import time
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from lerobot.cameras import make_cameras_from_configs
|
from lerobot.cameras import make_cameras_from_configs
|
||||||
|
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||||
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||||
from lerobot.motors.feetech import (
|
from lerobot.motors.feetech import (
|
||||||
FeetechMotorsBus,
|
FeetechMotorsBus,
|
||||||
OperatingMode,
|
OperatingMode,
|
||||||
)
|
)
|
||||||
from lerobot.types import RobotAction, RobotObservation
|
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..robot import Robot
|
from ..robot import Robot
|
||||||
|
|||||||
@@ -68,10 +68,6 @@ class UnitreeG1Config(RobotConfig):
|
|||||||
# Compensates for gravity on the unitree's arms using the arm ik solver
|
# Compensates for gravity on the unitree's arms using the arm ik solver
|
||||||
gravity_compensation: bool = False
|
gravity_compensation: bool = False
|
||||||
|
|
||||||
# Locomotion controller class name, e.g. "GrootLocomotionController",
|
# Lower-body controller class name, e.g. "GrootLocomotionController" or
|
||||||
# "HolosomaLocomotionController", or "SonicWholeBodyController". None disables it.
|
# "HolosomaLocomotionController". None disables it.
|
||||||
# Selecting "SonicWholeBodyController" implicitly switches the robot to the 64-D
|
|
||||||
# latent-token action/observation interface (``motion_token.{i}.pos`` action and a
|
|
||||||
# ``motion_token_state.{i}.pos`` state echo) so ``lerobot-rollout`` can drive a
|
|
||||||
# policy trained on SONIC motion tokens (e.g. nepyope/sonic_walk).
|
|
||||||
controller: str | None = None
|
controller: str | None = None
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Unitree G1 locomotion controllers (Groot, Holosoma, SONIC)."""
|
|
||||||
|
|
||||||
from .gr00t_locomotion import GrootLocomotionController
|
|
||||||
from .holosoma_locomotion import HolosomaLocomotionController
|
|
||||||
from .sonic_whole_body import SonicWholeBodyController
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"GrootLocomotionController",
|
|
||||||
"HolosomaLocomotionController",
|
|
||||||
"SonicWholeBodyController",
|
|
||||||
]
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""SONIC decoder whole-body controller for the Unitree G1 (token-only).
|
|
||||||
|
|
||||||
Pure-Python/ONNX re-implementation of the *decode* half of NVIDIA's SONIC deploy stack.
|
|
||||||
The encoder is intentionally absent: a token-output VLA (e.g. ``nepyope/sonic_walk``)
|
|
||||||
supplies the 64-D latent ``motion_token`` directly each tick, and the SONIC **decoder**
|
|
||||||
maps ``token + recent proprioception history`` to a residual action that is scaled and
|
|
||||||
added onto the standing pose (``default_angles``) to produce 50 Hz joint-position targets
|
|
||||||
for the robot's PD controller.
|
|
||||||
|
|
||||||
Index spaces: joints exist in two orderings — **IsaacLab** (policy/training order) and
|
|
||||||
**MuJoCo** (deploy order). ``ISAACLAB_TO_MUJOCO`` / ``MUJOCO_TO_ISAACLAB`` (in g1_utils)
|
|
||||||
convert between them. Quaternions are scalar-first ``(w, x, y, z)``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import onnx
|
|
||||||
import onnxruntime as ort
|
|
||||||
from huggingface_hub import hf_hub_download
|
|
||||||
|
|
||||||
from ..g1_utils import (
|
|
||||||
ISAACLAB_TO_MUJOCO,
|
|
||||||
MUJOCO_TO_ISAACLAB,
|
|
||||||
G1_29_JointIndex,
|
|
||||||
get_gravity_orientation,
|
|
||||||
)
|
|
||||||
from ..unitree_g1 import lowstate_to_obs
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# ── Constants (hardware-validated; see the NVIDIA SONIC deploy reference) ──────
|
|
||||||
CONTROL_DT = 0.02 # 50 Hz control period (s)
|
|
||||||
TOKEN_DIM = 64 # decoder latent size
|
|
||||||
|
|
||||||
# SONIC decoder checkpoint: NVIDIA's decoder ONNX re-packaged with its deploy constants
|
|
||||||
# (kp/kd PD gains, the standing pose default_angles, and the residual action_scale) embedded
|
|
||||||
# in the ONNX metadata; see upload_sonic_decoder.py for provisioning. The runtime loads the
|
|
||||||
# model *and* all of these straight from the checkpoint (the Holosoma convention), so no
|
|
||||||
# motor-physics math happens at deploy time.
|
|
||||||
DEFAULT_SONIC_REPO_ID = "lerobot/sonic_decoder"
|
|
||||||
DECODER_FILENAME = "model_decoder.onnx"
|
|
||||||
DECODER_INPUT_DIM = 994 # token(64) + 10-frame proprio history + gravity
|
|
||||||
|
|
||||||
|
|
||||||
def load_sonic_decoder(repo_id: str = DEFAULT_SONIC_REPO_ID):
|
|
||||||
"""Load the SONIC decoder ONNX and its baked-in deploy constants from the checkpoint.
|
|
||||||
|
|
||||||
Returns ``(decoder_session, kp, kd, default_angles, action_scale, neutral_token)``. The
|
|
||||||
gains/pose/scale are (29,) float32 in IsaacLab joint order and ``neutral_token`` is the
|
|
||||||
(64,) float32 idle latent -- all read from the ONNX ``metadata_props`` rather than
|
|
||||||
recomputed/hardcoded at deploy time (mirrors ``holosoma_locomotion.load_policy``).
|
|
||||||
"""
|
|
||||||
decoder_path = hf_hub_download(repo_id=repo_id, filename=DECODER_FILENAME)
|
|
||||||
so = ort.SessionOptions()
|
|
||||||
so.log_severity_level = 3 # quiet ORT logs
|
|
||||||
session = ort.InferenceSession(decoder_path, sess_options=so)
|
|
||||||
dec_dim = int(session.get_inputs()[0].shape[1])
|
|
||||||
if dec_dim != DECODER_INPUT_DIM:
|
|
||||||
raise RuntimeError(f"Unexpected decoder input dim {dec_dim} (expected {DECODER_INPUT_DIM})")
|
|
||||||
|
|
||||||
meta = {p.key: p.value for p in onnx.load(decoder_path, load_external_data=False).metadata_props}
|
|
||||||
required = ("kp", "kd", "default_angles", "action_scale", "neutral_token")
|
|
||||||
missing = [k for k in required if k not in meta]
|
|
||||||
if missing:
|
|
||||||
raise ValueError(
|
|
||||||
f"SONIC decoder ONNX at {repo_id} is missing metadata {missing}; "
|
|
||||||
"re-run upload_sonic_decoder.py to (re)provision the checkpoint."
|
|
||||||
)
|
|
||||||
arr = {k: np.array(json.loads(meta[k]), dtype=np.float32) for k in required}
|
|
||||||
logger.info("Loaded SONIC deploy constants from %s (%d joints)", repo_id, len(arr["kp"]))
|
|
||||||
return session, arr["kp"], arr["kd"], arr["default_angles"], arr["action_scale"], arr["neutral_token"]
|
|
||||||
|
|
||||||
|
|
||||||
def _to_mujoco(a):
|
|
||||||
"""Apply the ``MUJOCO_TO_ISAACLAB`` gather to a 29-vector (deploy-order reorder).
|
|
||||||
|
|
||||||
NOTE: this returns ``a[MUJOCO_TO_ISAACLAB]``. The ``_mj`` suffixes and the exact
|
|
||||||
permutation direction are a fixed convention validated against the deployed SONIC ONNX
|
|
||||||
policy (the decoder consumes vectors in this order). Do not "correct" the table or
|
|
||||||
rename toward the opposite direction without re-validating on hardware.
|
|
||||||
"""
|
|
||||||
return a[MUJOCO_TO_ISAACLAB]
|
|
||||||
|
|
||||||
|
|
||||||
# Action-feature prefix for the latent-token interface (see _extract_token_from_action).
|
|
||||||
TOKEN_ACTION_PREFIX = "motion_token" # nosec B105 - feature-key prefix, not a secret
|
|
||||||
# Proprio-state prefix for the token interface: the robot echoes the last commanded token
|
|
||||||
# here so ``lerobot-rollout`` aggregates it into a 64-D ``observation.state``.
|
|
||||||
TOKEN_STATE_PREFIX = "motion_token_state" # nosec B105 - feature-key prefix, not a secret
|
|
||||||
|
|
||||||
|
|
||||||
def token_action_key(i: int) -> str:
|
|
||||||
"""Action-dict key for the i-th component of the 64-D SONIC latent token.
|
|
||||||
|
|
||||||
The ``.pos`` suffix is required so the value flows through ``lerobot-rollout``, which
|
|
||||||
only routes ``.pos`` scalar features onto the policy action vector.
|
|
||||||
"""
|
|
||||||
return f"{TOKEN_ACTION_PREFIX}.{i}.pos"
|
|
||||||
|
|
||||||
|
|
||||||
def token_state_key(i: int) -> str:
|
|
||||||
"""Observation key for the i-th component of the 64-D SONIC latent token state."""
|
|
||||||
return f"{TOKEN_STATE_PREFIX}.{i}.pos"
|
|
||||||
|
|
||||||
|
|
||||||
# Startup blend duration: over the first control ticks, linearly interpolate every joint
|
|
||||||
# from the robot's initial measured pose into the policy's commanded target, so control
|
|
||||||
# eases in without a snap on the first command.
|
|
||||||
INIT_RAMP_S = 3.0
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_token_from_action(action: dict | None) -> np.ndarray | None:
|
|
||||||
"""Reassemble a dense (64,) latent token from ``motion_token.{i}`` keys, or None.
|
|
||||||
|
|
||||||
The token-only interface: the caller supplies the 64-D encoder latent directly (e.g. a
|
|
||||||
token-output VLA's action), which the decoder consumes with the encoder bypassed.
|
|
||||||
Requires the full dense token; a partial one is ignored (returns None).
|
|
||||||
"""
|
|
||||||
if not action:
|
|
||||||
return None
|
|
||||||
keys = [token_action_key(i) for i in range(TOKEN_DIM)]
|
|
||||||
if any(key not in action for key in keys):
|
|
||||||
return None
|
|
||||||
return np.fromiter((float(action[key]) for key in keys), dtype=np.float32, count=TOKEN_DIM)
|
|
||||||
|
|
||||||
|
|
||||||
class SonicDecoder:
|
|
||||||
"""Runs the SONIC decoder ONNX model and owns the proprioception history.
|
|
||||||
|
|
||||||
Each tick it appends the latest robot state to 10-frame history buffers, then maps the
|
|
||||||
supplied 64-D ``token`` + that history to a residual action added onto ``default_angles``.
|
|
||||||
The encoder is bypassed entirely (token supplied by the policy). ``default_angles`` and
|
|
||||||
``action_scale`` are (29,) float32 in IsaacLab order, loaded from the checkpoint.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, decoder, default_angles, action_scale):
|
|
||||||
self.decoder = decoder
|
|
||||||
self.decoder_input = decoder.get_inputs()[0].name
|
|
||||||
self.default_angles = np.asarray(default_angles, np.float32)
|
|
||||||
self.action_scale = np.asarray(action_scale, np.float32)
|
|
||||||
self.default_angles_mj = _to_mujoco(self.default_angles)
|
|
||||||
self.token = np.zeros(TOKEN_DIM, np.float32)
|
|
||||||
self.last_action_mj = np.zeros(29, np.float32)
|
|
||||||
self.h_q_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_dq_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_ang = [np.zeros(3, np.float32)] * 10
|
|
||||||
self.h_act_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_quat = [np.array([1, 0, 0, 0], np.float32)] * 10
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""Clear the token and 10-frame proprioception history.
|
|
||||||
|
|
||||||
``UnitreeG1.reset()`` relies on this so the first decoder outputs of a new episode
|
|
||||||
are not contaminated by the previous episode's state.
|
|
||||||
"""
|
|
||||||
self.token = np.zeros(TOKEN_DIM, np.float32)
|
|
||||||
self.last_action_mj = np.zeros(29, np.float32)
|
|
||||||
self.h_q_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_dq_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_ang = [np.zeros(3, np.float32)] * 10
|
|
||||||
self.h_act_mj = [np.zeros(29, np.float32)] * 10
|
|
||||||
self.h_quat = [np.array([1, 0, 0, 0], np.float32)] * 10
|
|
||||||
|
|
||||||
def update_history(self, q, dq, ang, quat):
|
|
||||||
"""Push the latest proprioception (pos/vel/gyro/orientation) into the 10-frame buffers."""
|
|
||||||
quat = quat / (np.linalg.norm(quat) + 1e-8)
|
|
||||||
q_mj = _to_mujoco(q)
|
|
||||||
dq_mj = _to_mujoco(dq)
|
|
||||||
self.h_q_mj = [q_mj - self.default_angles_mj] + self.h_q_mj[:-1]
|
|
||||||
self.h_dq_mj = [dq_mj] + self.h_dq_mj[:-1]
|
|
||||||
self.h_ang = [ang.copy()] + self.h_ang[:-1]
|
|
||||||
self.h_act_mj = [self.last_action_mj.copy()] + self.h_act_mj[:-1]
|
|
||||||
self.h_quat = [quat.copy()] + self.h_quat[:-1]
|
|
||||||
|
|
||||||
def build_decoder_obs(self):
|
|
||||||
"""Assemble the 994-D decoder input: token + 10-frame proprioception history + gravity."""
|
|
||||||
obs = np.zeros(994, np.float32)
|
|
||||||
off = 0
|
|
||||||
obs[off : off + 64] = self.token
|
|
||||||
off += 64
|
|
||||||
for h, sz in [
|
|
||||||
(list(reversed(self.h_ang)), 3),
|
|
||||||
(list(reversed(self.h_q_mj)), 29),
|
|
||||||
(list(reversed(self.h_dq_mj)), 29),
|
|
||||||
(list(reversed(self.h_act_mj)), 29),
|
|
||||||
]:
|
|
||||||
for f in range(10):
|
|
||||||
obs[off : off + sz] = h[f]
|
|
||||||
off += sz
|
|
||||||
for q in reversed(self.h_quat):
|
|
||||||
obs[off : off + 3] = get_gravity_orientation(q)
|
|
||||||
off += 3
|
|
||||||
assert off == 994, f"Decoder obs mismatch: {off}"
|
|
||||||
return obs
|
|
||||||
|
|
||||||
def step(self, robot_obs, token, debug=False):
|
|
||||||
"""One control tick: read robot obs, decode the supplied token -> joint targets.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
robot_obs: dict with ``<joint>.q``/``.dq`` and ``imu.*`` fields.
|
|
||||||
token: 64-D latent supplied by the policy (encoder bypassed).
|
|
||||||
debug: log action/delta norms.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict of ``<joint>.q`` target positions (rad) in IsaacLab joint order.
|
|
||||||
"""
|
|
||||||
self.token = np.asarray(token, np.float32)
|
|
||||||
jnames = [m.name for m in G1_29_JointIndex]
|
|
||||||
q = np.array(
|
|
||||||
[
|
|
||||||
robot_obs.get(f"{n}.q", self.default_angles[m.value])
|
|
||||||
for m, n in zip(G1_29_JointIndex, jnames, strict=False)
|
|
||||||
],
|
|
||||||
np.float32,
|
|
||||||
)
|
|
||||||
dq = np.array([robot_obs.get(f"{n}.dq", 0.0) for n in jnames], np.float32)
|
|
||||||
quat = np.array(
|
|
||||||
[
|
|
||||||
robot_obs.get("imu.quat.w", 1),
|
|
||||||
robot_obs.get("imu.quat.x", 0),
|
|
||||||
robot_obs.get("imu.quat.y", 0),
|
|
||||||
robot_obs.get("imu.quat.z", 0),
|
|
||||||
],
|
|
||||||
np.float32,
|
|
||||||
)
|
|
||||||
ang = np.array([robot_obs.get(f"imu.gyro.{a}", 0) for a in "xyz"], np.float32)
|
|
||||||
self.update_history(q, dq, ang, quat)
|
|
||||||
action_mj = (
|
|
||||||
self.decoder.run(None, {self.decoder_input: self.build_decoder_obs().reshape(1, -1)})[0]
|
|
||||||
.squeeze()
|
|
||||||
.astype(np.float32)
|
|
||||||
)
|
|
||||||
self.last_action_mj = action_mj.copy()
|
|
||||||
target = self.default_angles + action_mj[ISAACLAB_TO_MUJOCO] * self.action_scale
|
|
||||||
if debug:
|
|
||||||
delta = target - q
|
|
||||||
logger.debug(
|
|
||||||
"token_norm=%.4f action_norm=%.4f delta_max=%.4f delta_rms=%.4f",
|
|
||||||
np.linalg.norm(self.token),
|
|
||||||
np.linalg.norm(action_mj),
|
|
||||||
np.max(np.abs(delta)),
|
|
||||||
np.sqrt(np.mean(delta**2)),
|
|
||||||
)
|
|
||||||
return {f"{m.name}.q": float(target[m.value]) for m in G1_29_JointIndex}
|
|
||||||
|
|
||||||
|
|
||||||
class SonicRuntime:
|
|
||||||
"""Loads the SONIC decoder ONNX model and owns the decode controller.
|
|
||||||
|
|
||||||
Token-only deploy: the encoder is bypassed; each tick the decoder consumes a 64-D
|
|
||||||
latent token supplied directly by the policy.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
decoder_sess, self.kp, self.kd, default_angles, action_scale, neutral_token = load_sonic_decoder()
|
|
||||||
self.default_angles = default_angles
|
|
||||||
self.neutral_token = neutral_token
|
|
||||||
self.controller = SonicDecoder(decoder_sess, default_angles, action_scale)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pipeline(self):
|
|
||||||
return self.controller
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.controller.reset()
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SonicWholeBodyController:
|
|
||||||
"""Full-body SONIC controller for UnitreeG1's background controller thread."""
|
|
||||||
|
|
||||||
control_dt = CONTROL_DT
|
|
||||||
full_body = True
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
logger.info("Loading SONIC whole-body controller...")
|
|
||||||
self._runtime = SonicRuntime()
|
|
||||||
self.kp = self._runtime.kp
|
|
||||||
self.kd = self._runtime.kd
|
|
||||||
self.controller = self._runtime.controller
|
|
||||||
self._default_angles = self._runtime.default_angles
|
|
||||||
self._neutral_token = self._runtime.neutral_token
|
|
||||||
|
|
||||||
# Startup blend: ease from the robot's initial pose into the first commanded policy
|
|
||||||
# targets over INIT_RAMP_S (captured on the first control tick).
|
|
||||||
self._init_ramp_steps = max(1, round(INIT_RAMP_S / CONTROL_DT))
|
|
||||||
self._init_step = 0
|
|
||||||
self._start_pose: dict[str, float] = {}
|
|
||||||
|
|
||||||
# Token-interface state. ``token_mode`` is set True by the robot whenever a SONIC
|
|
||||||
# whole-body controller is selected (token-driven deploy): the controller then holds a
|
|
||||||
# stable *neutral* token until the first real token arrives, and afterwards holds the
|
|
||||||
# *last* token received between ticks (the async controller runs ~50 Hz while a token
|
|
||||||
# VLA streams ~30 Hz). This lives here (not in the entry-point script) so it applies
|
|
||||||
# uniformly to run_g1_server, lerobot-rollout and the sim replays.
|
|
||||||
self.token_mode = False
|
|
||||||
self._last_token: np.ndarray | None = None
|
|
||||||
|
|
||||||
logger.info("SONIC ready (decoder, 64-D token command path)")
|
|
||||||
|
|
||||||
def _startup_blend(self, obs: dict, out: dict) -> dict:
|
|
||||||
"""Ease into policy control at startup: for the first ``INIT_RAMP_S`` seconds,
|
|
||||||
interpolate between the robot's pose captured on the first tick and the policy's
|
|
||||||
live commanded target, so the handoff has no snap.
|
|
||||||
|
|
||||||
``out`` is the policy's ``<joint>.q`` target dict for this tick; the blend ratio
|
|
||||||
climbs 0->1 over the ramp, after which the raw policy target passes through.
|
|
||||||
"""
|
|
||||||
if self._init_step >= self._init_ramp_steps or not out:
|
|
||||||
return out
|
|
||||||
if self._init_step == 0:
|
|
||||||
# Capture the robot's actual pose as the interpolation start point.
|
|
||||||
self._start_pose = {
|
|
||||||
f"{m.name}.q": float(obs.get(f"{m.name}.q", self._default_angles[m.value]))
|
|
||||||
for m in G1_29_JointIndex
|
|
||||||
}
|
|
||||||
self._init_step += 1
|
|
||||||
ratio = min(1.0, self._init_step / self._init_ramp_steps)
|
|
||||||
blended = {
|
|
||||||
k: self._start_pose.get(k, float(tgt)) * (1.0 - ratio) + float(tgt) * ratio
|
|
||||||
for k, tgt in out.items()
|
|
||||||
}
|
|
||||||
if self._init_step >= self._init_ramp_steps:
|
|
||||||
logger.info("SONIC startup blend complete -> full policy control")
|
|
||||||
return blended
|
|
||||||
|
|
||||||
def run_step(self, action: dict, lowstate) -> dict:
|
|
||||||
if lowstate is None:
|
|
||||||
return {}
|
|
||||||
obs = lowstate_to_obs(lowstate)
|
|
||||||
|
|
||||||
# Token-only interface (token-output VLA): a dense 64-D ``motion_token.{i}`` command
|
|
||||||
# is decoded directly, encoder bypassed.
|
|
||||||
token = _extract_token_from_action(action)
|
|
||||||
if token is not None:
|
|
||||||
self._last_token = token
|
|
||||||
elif self._last_token is None and self.token_mode:
|
|
||||||
# Token-driven deploy, but no token has arrived yet: hold the checkpoint's neutral
|
|
||||||
# token, which the decoder maps to a stable, natural standing pose.
|
|
||||||
self._last_token = self._neutral_token.copy()
|
|
||||||
if self._last_token is None:
|
|
||||||
# No token yet and not in token_mode: hold (keep last target).
|
|
||||||
return {}
|
|
||||||
# Either a fresh token this tick or the last one received (held between the ~30 Hz
|
|
||||||
# token stream and the ~50 Hz control loop).
|
|
||||||
return self._startup_blend(obs, self.controller.step(obs, self._last_token))
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self._runtime.reset()
|
|
||||||
self._init_step = 0 # re-run the startup blend after a reset
|
|
||||||
self._start_pose = {}
|
|
||||||
# Drop the held token so token_mode re-seeds the neutral token after a reset.
|
|
||||||
self._last_token = None
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
self._runtime.shutdown()
|
|
||||||
@@ -23,47 +23,6 @@ import numpy as np
|
|||||||
|
|
||||||
NUM_MOTORS = 29
|
NUM_MOTORS = 29
|
||||||
|
|
||||||
# Joint-order permutations between the two 29-DoF layouts used across the G1 stack:
|
|
||||||
# IsaacLab (policy/training order) and MuJoCo (deploy order). ``a[ISAACLAB_TO_MUJOCO]``
|
|
||||||
# reorders an IsaacLab-ordered vector into MuJoCo order, and vice-versa.
|
|
||||||
ISAACLAB_TO_MUJOCO = np.array(
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
6,
|
|
||||||
9,
|
|
||||||
13,
|
|
||||||
17,
|
|
||||||
1,
|
|
||||||
4,
|
|
||||||
7,
|
|
||||||
10,
|
|
||||||
14,
|
|
||||||
18,
|
|
||||||
2,
|
|
||||||
5,
|
|
||||||
8,
|
|
||||||
11,
|
|
||||||
15,
|
|
||||||
19,
|
|
||||||
21,
|
|
||||||
23,
|
|
||||||
25,
|
|
||||||
27,
|
|
||||||
12,
|
|
||||||
16,
|
|
||||||
20,
|
|
||||||
22,
|
|
||||||
24,
|
|
||||||
26,
|
|
||||||
28,
|
|
||||||
],
|
|
||||||
dtype=np.int32,
|
|
||||||
)
|
|
||||||
# The two orderings are inverses of each other, so derive one from the other (argsort) to
|
|
||||||
# guarantee they can never drift out of sync.
|
|
||||||
MUJOCO_TO_ISAACLAB = np.argsort(ISAACLAB_TO_MUJOCO).astype(np.int32)
|
|
||||||
|
|
||||||
REMOTE_AXES = ("remote.lx", "remote.ly", "remote.rx", "remote.ry")
|
REMOTE_AXES = ("remote.lx", "remote.ly", "remote.rx", "remote.ry")
|
||||||
REMOTE_BUTTONS = tuple(f"remote.button.{i}" for i in range(16))
|
REMOTE_BUTTONS = tuple(f"remote.button.{i}" for i in range(16))
|
||||||
REMOTE_KEYS = REMOTE_AXES + REMOTE_BUTTONS
|
REMOTE_KEYS = REMOTE_AXES + REMOTE_BUTTONS
|
||||||
@@ -109,9 +68,8 @@ def make_locomotion_controller(name: str | None):
|
|||||||
if name is None:
|
if name is None:
|
||||||
return None
|
return None
|
||||||
controllers = {
|
controllers = {
|
||||||
"GrootLocomotionController": "lerobot.robots.unitree_g1.controllers.gr00t_locomotion",
|
"GrootLocomotionController": "lerobot.robots.unitree_g1.gr00t_locomotion",
|
||||||
"HolosomaLocomotionController": "lerobot.robots.unitree_g1.controllers.holosoma_locomotion",
|
"HolosomaLocomotionController": "lerobot.robots.unitree_g1.holosoma_locomotion",
|
||||||
"SonicWholeBodyController": "lerobot.robots.unitree_g1.controllers.sonic_whole_body",
|
|
||||||
}
|
}
|
||||||
module_path = controllers.get(name)
|
module_path = controllers.get(name)
|
||||||
if module_path is None:
|
if module_path is None:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user