182 lines
5.8 KiB
Python
182 lines
5.8 KiB
Python
"""LIBERO benchmark and robosuite engine helpers.
|
|
|
|
This module may import robosuite and LIBERO. Adapter-side code should use
|
|
``libero_common.py`` instead, which only depends on NumPy.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from typing import Any, Optional
|
|
|
|
import numpy as np
|
|
from robosuite.wrappers import VisualizationWrapper
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ============================================================
|
|
# One-time side effect: auto-create ~/.libero/config.yaml so importing LIBERO
|
|
# does not trigger an interactive prompt.
|
|
# ============================================================
|
|
|
|
|
|
def _ensure_libero_config() -> None:
|
|
import yaml as _yaml
|
|
|
|
libero_config_path = os.environ.get(
|
|
"LIBERO_CONFIG_PATH", os.path.expanduser("~/.libero")
|
|
)
|
|
config_file = os.path.join(libero_config_path, "config.yaml")
|
|
|
|
if not os.path.exists(config_file):
|
|
os.makedirs(libero_config_path, exist_ok=True)
|
|
import libero.libero as _libero_pkg
|
|
|
|
benchmark_root = os.path.dirname(os.path.abspath(_libero_pkg.__file__))
|
|
default_paths = {
|
|
"benchmark_root": benchmark_root,
|
|
"bddl_files": os.path.join(benchmark_root, "./bddl_files"),
|
|
"init_states": os.path.join(benchmark_root, "./init_files"),
|
|
"datasets": os.path.join(benchmark_root, "../datasets"),
|
|
"assets": os.path.join(benchmark_root, "./assets"),
|
|
}
|
|
with open(config_file, "w") as f:
|
|
_yaml.dump(default_paths, f)
|
|
logger.info("Auto-created LIBERO config: %s", config_file)
|
|
|
|
|
|
_ensure_libero_config()
|
|
|
|
|
|
# ============================================================
|
|
# task-suite entry point
|
|
# ============================================================
|
|
|
|
|
|
def get_task_suite(task_suite_name: str):
|
|
"""Load a LIBERO task suite."""
|
|
from libero.libero import benchmark
|
|
|
|
return benchmark.get_benchmark_dict()[task_suite_name]()
|
|
|
|
|
|
# ============================================================
|
|
# actions
|
|
# ============================================================
|
|
|
|
|
|
def get_libero_dummy_action() -> list[float]:
|
|
"""Return the 7-dof dummy action used for episode warmup."""
|
|
return [0, 0, 0, 0, 0, 0, -1]
|
|
|
|
|
|
# ============================================================
|
|
# robosuite engine factory
|
|
# ============================================================
|
|
|
|
|
|
def create_libero_engine(
|
|
task_id: int,
|
|
task_suite_name: str,
|
|
resolution: int = 256,
|
|
seed: int = 7,
|
|
) -> Any:
|
|
"""Construct one LIBERO robosuite engine."""
|
|
from libero.libero import get_libero_path
|
|
from libero.libero.envs import OffScreenRenderEnv
|
|
|
|
task_suite = get_task_suite(task_suite_name)
|
|
task = task_suite.get_task(task_id)
|
|
task_bddl_file = os.path.join(
|
|
get_libero_path("bddl_files"), task.problem_folder, task.bddl_file
|
|
)
|
|
env = OffScreenRenderEnv(
|
|
bddl_file_name=task_bddl_file,
|
|
camera_heights=resolution,
|
|
camera_widths=resolution,
|
|
)
|
|
# The seed still affects object poses even when an initial state is fixed.
|
|
env.seed(seed)
|
|
env.env = VisualizationWrapper(env.env)
|
|
env.env.set_visualization_setting(setting="grippers", visible=False)
|
|
return env
|
|
|
|
|
|
# ============================================================
|
|
# task metadata / initial states
|
|
# ============================================================
|
|
|
|
|
|
def load_initial_states(initial_states_path: str) -> Optional[dict]:
|
|
"""Load custom initial states, or return None for suite defaults."""
|
|
if initial_states_path == "DEFAULT":
|
|
return None
|
|
with open(initial_states_path, "r") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def resolve_task_info(task_suite, task_id: int) -> tuple[str, Any]:
|
|
"""Return ``(task_desc, default_initial_states)`` for one task id."""
|
|
num_tasks = task_suite.n_tasks
|
|
if task_id < 0 or task_id >= num_tasks:
|
|
raise ValueError(f"invalid task_id={task_id}, num_tasks={num_tasks}")
|
|
task = task_suite.get_task(task_id)
|
|
return task.language, task_suite.get_task_init_states(task_id)
|
|
|
|
|
|
def pick_initial_state(
|
|
initial_states_path: str,
|
|
custom_initial_states: Optional[dict],
|
|
task_desc: str,
|
|
default_states: Any,
|
|
episode_idx: int,
|
|
) -> np.ndarray:
|
|
"""Pick one initial state from suite defaults or a custom states file."""
|
|
if initial_states_path == "DEFAULT":
|
|
if default_states is None:
|
|
raise ValueError("default states missing for DEFAULT mode")
|
|
return default_states[episode_idx]
|
|
|
|
if custom_initial_states is None:
|
|
raise ValueError(f"custom initial states not loaded for {initial_states_path}")
|
|
key = task_desc.replace(" ", "_")
|
|
ep_key = f"demo_{episode_idx}"
|
|
record = custom_initial_states[key][ep_key]
|
|
if not record["success"]:
|
|
raise ValueError(f"expert demo failed for {ep_key}")
|
|
return np.array(record["initial_state"])
|
|
|
|
|
|
def get_instruction(task_desc: str) -> str:
|
|
"""Return the instruction text for a LIBERO task description."""
|
|
return task_desc
|
|
|
|
|
|
# ============================================================
|
|
# render-skip: directly assign obs._enabled to avoid set_enabled() side effects.
|
|
# ============================================================
|
|
|
|
|
|
def find_image_observables(env) -> list:
|
|
"""Find image observables along the env.env wrapper chain."""
|
|
cur = env
|
|
seen = set()
|
|
while cur is not None and id(cur) not in seen:
|
|
seen.add(id(cur))
|
|
if hasattr(cur, "_observables") and isinstance(cur._observables, dict):
|
|
return [
|
|
obs
|
|
for obs in cur._observables.values()
|
|
if getattr(obs, "modality", None) == "image"
|
|
]
|
|
cur = getattr(cur, "env", None)
|
|
return []
|
|
|
|
|
|
def set_render_enabled(image_obs_list, enabled: bool) -> None:
|
|
for obs in image_obs_list:
|
|
obs._enabled = enabled
|