Instructions to use lukasskellijs/env_assembly_bench with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LeRobot
How to use lukasskellijs/env_assembly_bench with LeRobot:
- Notebooks
- Google Colab
- Kaggle
| """VectorEnv wrapper adapting Isaac Lab's GPU-batched env for LeRobot EnvHub.""" | |
| from __future__ import annotations | |
| import atexit | |
| import logging | |
| import os | |
| import signal | |
| from contextlib import suppress | |
| from typing import Any | |
| import gymnasium as gym | |
| import numpy as np | |
| import torch | |
| try: | |
| from .errors import IsaacLabArenaError | |
| except ImportError: | |
| from errors import IsaacLabArenaError | |
| def cleanup_isaaclab(env, simulation_app) -> None: | |
| """Close the gym env and Kit app; ignore signals mid-teardown.""" | |
| old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN) | |
| old_sigterm = signal.signal(signal.SIGTERM, signal.SIG_IGN) | |
| try: | |
| with suppress(Exception): | |
| if env is not None: | |
| env.close() | |
| with suppress(Exception): | |
| if simulation_app is not None: | |
| simulation_app.app.close() | |
| finally: | |
| signal.signal(signal.SIGINT, old_sigint) | |
| signal.signal(signal.SIGTERM, old_sigterm) | |
| class IsaacLabEnvWrapper(gym.vector.AsyncVectorEnv): | |
| """Isaac Lab already vectorizes on GPU; this exposes an AsyncVectorEnv API.""" | |
| metadata = {"render_modes": ["rgb_array"], "render_fps": 15} | |
| _cleanup_in_progress = False | |
| def __init__( | |
| self, | |
| env, | |
| episode_length: int = 500, | |
| task: str | None = None, | |
| render_mode: str | None = "rgb_array", | |
| simulation_app=None, | |
| ): | |
| self._env = env | |
| self._num_envs = env.num_envs | |
| self._episode_length = episode_length | |
| self._closed = False | |
| self.render_mode = render_mode | |
| self._simulation_app = simulation_app | |
| self.observation_space = env.observation_space | |
| self.action_space = env.action_space | |
| self.single_observation_space = env.observation_space | |
| self.single_action_space = env.action_space | |
| self.task = task | |
| if hasattr(env, "metadata") and env.metadata: | |
| self.metadata = {**self.metadata, **env.metadata} | |
| atexit.register(self._cleanup) | |
| signal.signal(signal.SIGINT, self._signal_handler) | |
| signal.signal(signal.SIGTERM, self._signal_handler) | |
| def _signal_handler(self, signum, frame): | |
| if IsaacLabEnvWrapper._cleanup_in_progress: | |
| return | |
| IsaacLabEnvWrapper._cleanup_in_progress = True | |
| logging.info(f"Received signal {signum}, cleaning up...") | |
| self._cleanup() | |
| os._exit(0) | |
| def _check_closed(self): | |
| if self._closed: | |
| raise IsaacLabArenaError() | |
| def unwrapped(self): | |
| return self | |
| def num_envs(self) -> int: | |
| return self._num_envs | |
| def _max_episode_steps(self) -> int: | |
| return self._episode_length | |
| def device(self) -> str: | |
| return getattr(self._env, "device", "cpu") | |
| def reset( | |
| self, | |
| *, | |
| seed: int | list[int] | None = None, | |
| options: dict[str, Any] | None = None, | |
| ) -> tuple[dict[str, Any], dict[str, Any]]: | |
| self._check_closed() | |
| if isinstance(seed, (list, tuple, range)): | |
| seed = seed[0] if len(seed) > 0 else None | |
| obs, info = self._env.reset(seed=seed, options=options) | |
| if "final_info" not in info: | |
| zeros = np.zeros(self._num_envs, dtype=bool) | |
| info["final_info"] = {"is_success": zeros} | |
| return obs, info | |
| def step( | |
| self, actions: np.ndarray | torch.Tensor | |
| ) -> tuple[dict, np.ndarray, np.ndarray, np.ndarray, dict]: | |
| self._check_closed() | |
| if isinstance(actions, np.ndarray): | |
| actions = torch.from_numpy(actions).to(self._env.device) | |
| obs, reward, terminated, truncated, info = self._env.step(actions) | |
| reward = reward.cpu().numpy().astype(np.float32) | |
| terminated = terminated.cpu().numpy().astype(bool) | |
| truncated = truncated.cpu().numpy().astype(bool) | |
| is_success = self._get_success(terminated, truncated) | |
| info["final_info"] = {"is_success": is_success} | |
| return obs, reward, terminated, truncated, info | |
| def _get_success(self, terminated: np.ndarray, truncated: np.ndarray) -> np.ndarray: | |
| is_success = np.zeros(self._num_envs, dtype=bool) | |
| if not hasattr(self._env, "termination_manager"): | |
| return is_success & (terminated | truncated) | |
| term_manager = self._env.termination_manager | |
| if not hasattr(term_manager, "get_term"): | |
| return is_success & (terminated | truncated) | |
| success_tensor = term_manager.get_term("success") | |
| if success_tensor is None: | |
| return is_success & (terminated | truncated) | |
| is_success = success_tensor.cpu().numpy().astype(bool) | |
| return is_success & (terminated | truncated) | |
| def call(self, method_name: str, *args, **kwargs) -> list[Any]: | |
| if method_name == "_max_episode_steps": | |
| return [self._episode_length] * self._num_envs | |
| if method_name == "task": | |
| return [self.task] * self._num_envs | |
| if method_name == "render": | |
| return self.render_all() | |
| if hasattr(self._env, method_name): | |
| attr = getattr(self._env, method_name) | |
| result = attr(*args, **kwargs) if callable(attr) else attr | |
| if isinstance(result, list): | |
| return result | |
| return [result] * self._num_envs | |
| raise AttributeError(f"IsaacLab-Arena has no method/attribute '{method_name}'") | |
| def render_all(self) -> list[np.ndarray]: | |
| self._check_closed() | |
| frames = self.render() | |
| if frames is None: | |
| placeholder = np.zeros((480, 640, 3), dtype=np.uint8) | |
| return [placeholder] * self._num_envs | |
| return [frames] * self._num_envs | |
| def render(self) -> np.ndarray | None: | |
| self._check_closed() | |
| if self.render_mode != "rgb_array": | |
| return None | |
| frames = self._env.render() if hasattr(self._env, "render") else None | |
| if frames is None: | |
| return None | |
| if isinstance(frames, torch.Tensor): | |
| frames = frames.cpu().numpy() | |
| return frames[0] if frames.ndim == 4 else frames | |
| def _cleanup(self) -> None: | |
| if self._closed: | |
| return | |
| self._closed = True | |
| IsaacLabEnvWrapper._cleanup_in_progress = True | |
| logging.info("Cleaning up IsaacLab Arena environment...") | |
| cleanup_isaaclab(self._env, self._simulation_app) | |
| def close(self) -> None: | |
| self._cleanup() | |
| def envs(self) -> list[IsaacLabEnvWrapper]: | |
| return [self] * self._num_envs | |
| def __del__(self): | |
| self._cleanup() | |
| def __enter__(self): | |
| return self | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| self._cleanup() | |
| return False | |