mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-23 20:50:02 +00:00
mujoco sim viewer integration
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -23,12 +23,14 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Event, Lock, Thread
|
from threading import Event, Lock, Thread
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
import base64
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import zmq
|
import zmq
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
import base64
|
||||||
|
import msgpack
|
||||||
|
import msgpack_numpy as m
|
||||||
from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
||||||
|
|
||||||
from ..camera import Camera
|
from ..camera import Camera
|
||||||
@@ -303,30 +305,19 @@ class ZMQCamera(Camera):
|
|||||||
"""
|
"""
|
||||||
Reads a single frame synchronously from the ZMQ camera.
|
Reads a single frame synchronously from the ZMQ camera.
|
||||||
|
|
||||||
This is a blocking call. It waits for the next available frame from the
|
For the mujoco sim (127.0.0.1:5554), the server sends a msgpack-encoded
|
||||||
ZMQ socket.
|
dict with base64-encoded JPEG images:
|
||||||
|
{"timestamps": {...}, "images": {camera_name: "<b64 jpeg>"}}
|
||||||
Args:
|
For other ZMQ cameras, we assume a raw JPEG buffer.
|
||||||
color_mode: If specified, overrides the default color mode for this read.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
np.ndarray: The captured frame as a NumPy array in the format
|
|
||||||
(height, width, channels), using the specified or default
|
|
||||||
color mode.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
DeviceNotConnectedError: If the camera is not connected.
|
|
||||||
TimeoutError: If no frame is received within the timeout period.
|
|
||||||
RuntimeError: If reading the frame fails.
|
|
||||||
"""
|
"""
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
raise DeviceNotConnectedError(f"{self} is not connected.")
|
raise DeviceNotConnectedError(f"{self} is not connected.")
|
||||||
|
|
||||||
start_time = time.perf_counter()
|
|
||||||
|
|
||||||
if self.socket is None:
|
if self.socket is None:
|
||||||
raise DeviceNotConnectedError(f"{self} socket is not initialized")
|
raise DeviceNotConnectedError(f"{self} socket is not initialized")
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = self.socket.recv()
|
message = self.socket.recv()
|
||||||
except zmq.Again:
|
except zmq.Again:
|
||||||
@@ -334,11 +325,49 @@ class ZMQCamera(Camera):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"{self} read failed: {e}")
|
raise RuntimeError(f"{self} read failed: {e}")
|
||||||
|
|
||||||
# Decode JPEG
|
frame = None
|
||||||
np_img = np.frombuffer(message, dtype=np.uint8)
|
|
||||||
frame = cv2.imdecode(np_img, cv2.IMREAD_COLOR)
|
|
||||||
|
|
||||||
if frame is None:
|
# special-case: mujoco sim publisher (msgpack + base64)
|
||||||
|
if self.server_address in ("127.0.0.1", "localhost") and self.port == 5554:
|
||||||
|
try:
|
||||||
|
data = msgpack.unpackb(message, object_hook=m.decode)
|
||||||
|
|
||||||
|
if not isinstance(data, dict) or "images" not in data:
|
||||||
|
raise RuntimeError(f"{self} received invalid message format from sim")
|
||||||
|
|
||||||
|
images_dict = data["images"]
|
||||||
|
img_data = None
|
||||||
|
|
||||||
|
# prefer named camera if present
|
||||||
|
if self.camera_name in images_dict:
|
||||||
|
img_data = images_dict[self.camera_name]
|
||||||
|
elif len(images_dict) > 0:
|
||||||
|
# fallback: first available camera
|
||||||
|
img_data = next(iter(images_dict.values()))
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"{self} no images found in sim message")
|
||||||
|
|
||||||
|
# same logic as ImageUtils.decode_image
|
||||||
|
if isinstance(img_data, str):
|
||||||
|
color_bytes = base64.b64decode(img_data)
|
||||||
|
np_img = np.frombuffer(color_bytes, dtype=np.uint8)
|
||||||
|
frame = cv2.imdecode(np_img, cv2.IMREAD_COLOR)
|
||||||
|
elif isinstance(img_data, np.ndarray):
|
||||||
|
frame = img_data
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self} unknown image payload type from sim: {type(img_data)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"{self} failed to decode sim image: {e}")
|
||||||
|
|
||||||
|
# default path: raw jpeg over ZMQ (robot-side cameras etc.)
|
||||||
|
else:
|
||||||
|
np_img = np.frombuffer(message, dtype=np.uint8)
|
||||||
|
frame = cv2.imdecode(np_img, cv2.IMREAD_COLOR)
|
||||||
|
|
||||||
|
if frame is None or not isinstance(frame, np.ndarray):
|
||||||
raise RuntimeError(f"{self} failed to decode image")
|
raise RuntimeError(f"{self} failed to decode image")
|
||||||
|
|
||||||
processed_frame = self._postprocess_image(frame, color_mode)
|
processed_frame = self._postprocess_image(frame, color_mode)
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ def make_env(
|
|||||||
|
|
||||||
# import and surface clear import errors
|
# import and surface clear import errors
|
||||||
module = _import_hub_module(local_file, repo_id)
|
module = _import_hub_module(local_file, repo_id)
|
||||||
|
|
||||||
# call the hub-provided make_env
|
# call the hub-provided make_env
|
||||||
raw_result = _call_make_env(module, n_envs=n_envs, use_async_envs=use_async_envs)
|
raw_result = _call_make_env(module, n_envs=n_envs, use_async_envs=use_async_envs)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from ..config import RobotConfig
|
|||||||
class UnitreeG1Config(RobotConfig):
|
class UnitreeG1Config(RobotConfig):
|
||||||
# id: str = "unitree_g1"
|
# id: str = "unitree_g1"
|
||||||
motion_mode: bool = False
|
motion_mode: bool = False
|
||||||
simulation_mode: bool = False
|
simulation_mode: bool = True
|
||||||
kp_high = 40.0
|
kp_high = 40.0
|
||||||
kd_high = 3.0
|
kd_high = 3.0
|
||||||
kp_low = 80.0
|
kp_low = 80.0
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ def visualize_filter_comparison(filter_params, steps):
|
|||||||
plt.xlabel("Step")
|
plt.xlabel("Step")
|
||||||
plt.ylabel("Value")
|
plt.ylabel("Value")
|
||||||
plt.legend()
|
plt.legend()
|
||||||
|
|
||||||
# col13 should 2b filtered
|
# col13 should 2b filtered
|
||||||
plt.subplot(len(filter_params), 2, idx * 2 + 2)
|
plt.subplot(len(filter_params), 2, idx * 2 + 2)
|
||||||
plt.plot(filtered_data[:, 13], label=f"Filtered (Window {filter._window_size})")
|
plt.plot(filtered_data[:, 13], label=f"Filtered (Window {filter._window_size})")
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import (
|
|||||||
MotionSwitcherClient,
|
MotionSwitcherClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from lerobot.envs.factory import make_env
|
||||||
from scipy.spatial.transform import Rotation as R
|
from scipy.spatial.transform import Rotation as R
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
@@ -169,7 +170,6 @@ class UnitreeG1(Robot):
|
|||||||
|
|
||||||
# Launch MuJoCo simulation environment
|
# Launch MuJoCo simulation environment
|
||||||
logger_mp.info("Launching MuJoCo simulation environment...")
|
logger_mp.info("Launching MuJoCo simulation environment...")
|
||||||
from lerobot.envs.factory import make_env
|
|
||||||
self.mujoco_env = make_env("lerobot/unitree-g1-mujoco", trust_remote_code=True)
|
self.mujoco_env = make_env("lerobot/unitree-g1-mujoco", trust_remote_code=True)
|
||||||
logger_mp.info("MuJoCo environment launched successfully!")
|
logger_mp.info("MuJoCo environment launched successfully!")
|
||||||
else:
|
else:
|
||||||
@@ -573,7 +573,8 @@ class UnitreeG1(Robot):
|
|||||||
# Close MuJoCo environment if in simulation mode
|
# Close MuJoCo environment if in simulation mode
|
||||||
if self.simulation_mode and hasattr(self, 'mujoco_env'):
|
if self.simulation_mode and hasattr(self, 'mujoco_env'):
|
||||||
logger_mp.info("Closing MuJoCo environment...")
|
logger_mp.info("Closing MuJoCo environment...")
|
||||||
self.mujoco_env.close()
|
print(self.mujoco_env)
|
||||||
|
self.mujoco_env["hub_env"][0].envs[0].kill_sim()
|
||||||
|
|
||||||
logger_mp.info(f"{self} disconnected.")
|
logger_mp.info(f"{self} disconnected.")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user