| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| This script demonstrates how to evaluate a pretrained policy from the HuggingFace Hub or from your local |
| training outputs directory. In the latter case, you might want to run kuavo_train/train_policy.py first. |
| |
| It requires the installation of the 'gym_pusht' simulation environment. Install it by running: |
| ```bash |
| pip install -e ".[pusht]" |
| ``` |
| """ |
| import sys,os |
| import gc |
| from std_srvs.srv import Trigger, TriggerRequest, TriggerResponse |
| from lerobot_patches import custom_patches |
|
|
| from pathlib import Path |
|
|
| from sympy import im |
| from dataclasses import dataclass, field |
| import hydra |
| import gymnasium as gym |
| import imageio |
| import numpy |
| import torch |
| from tqdm import tqdm |
| from lerobot.utils.random_utils import set_seed |
| import datetime |
| import time |
| import numpy as np |
| import json |
| from omegaconf import DictConfig, ListConfig, OmegaConf |
| from torchvision.transforms.functional import to_tensor |
| from std_msgs.msg import Bool |
| import rospy |
| import threading |
| import traceback |
| from geometry_msgs.msg import PoseStamped |
| from kuavo_deploy.config import KuavoConfig |
| from kuavo_deploy.utils.logging_utils import setup_logger |
| from kuavo_deploy.kuavo_service.client import PolicyClient |
| from kuavo_deploy.utils.policy_loader import ( |
| inject_task_prompt, |
| load_native_policy_bundle, |
| resolve_eval_output_dir, |
| ) |
| log_model = setup_logger("model") |
| log_robot = setup_logger("robot") |
|
|
| from kuavo_deploy.kuavo_env.KuavoSimEnv import KuavoSimEnv |
| from kuavo_deploy.kuavo_env.KuavoRealEnv import KuavoRealEnv |
| from kuavo_deploy.utils.ros_manager import ROSManager |
|
|
|
|
| init_evt = threading.Event() |
| pause_flag = threading.Event() |
| stop_flag = threading.Event() |
| success_evt = threading.Event() |
|
|
| def env_init_service(req): |
| log_robot.info(f"env_init_callback! req = {req}") |
| init_evt.set() |
| return TriggerResponse(success=True, message="Env init successful") |
|
|
| def pause_callback(msg): |
| if msg.data: |
| pause_flag.set() |
| else: |
| pause_flag.clear() |
|
|
| def stop_callback(msg): |
| if msg.data: |
| stop_flag.set() |
|
|
| def env_success_callback(msg): |
| |
| if msg.data: |
| success_evt.set() |
|
|
|
|
| pause_sub = rospy.Subscriber('/kuavo/pause_state', Bool, pause_callback, queue_size=10) |
| stop_sub = rospy.Subscriber('/kuavo/stop_state', Bool, stop_callback, queue_size=10) |
|
|
|
|
| def save_rollout_video(output_path: Path, frames: list[np.ndarray], fps: int | float) -> None: |
| if not frames: |
| return |
| try: |
| imageio.mimsave(str(output_path), frames, fps=fps, codec="libx264") |
| except Exception as exc: |
| log_robot.warning(f"Failed to write mp4 '{output_path.name}': {exc}. Falling back to gif.") |
| gif_path = output_path.with_suffix(".gif") |
| imageio.mimsave(str(gif_path), frames, fps=fps) |
|
|
| def safe_reset_service(reset_service) -> None: |
| """安全重置服务""" |
| try: |
| |
| response = reset_service(TriggerRequest()) |
| if response.success: |
| log_robot.info(f"Reset service successful: {response.message}") |
| else: |
| log_robot.warning(f"Reset service failed: {response.message}") |
| except rospy.ServiceException as e: |
| log_robot.error(f"Reset service exception: {e}") |
|
|
| def check_control_signals(): |
| """检查控制信号""" |
| |
| while pause_flag.is_set(): |
| log_robot.info("🔄 机械臂运动已暂停") |
| time.sleep(0.1) |
| if stop_flag.is_set(): |
| log_robot.info("🛑 机械臂运动被停止") |
| return False |
| |
| |
| if stop_flag.is_set(): |
| log_robot.info("🛑 收到停止信号,退出机械臂运动") |
| return False |
| |
| return True |
|
|
|
|
| |
| def setup_policy(pretrained_path, policy_type, device=torch.device("cuda"), task_prompt="robot manipulation"): |
| """ |
| Set up and load the policy model. |
| |
| Args: |
| pretrained_path: Path to the checkpoint |
| policy_type: Type of policy ('diffusion' or 'act') |
| |
| Returns: |
| Loaded policy model and device |
| """ |
| |
| if device.type == 'cpu': |
| log_model.warning("Warning: Using CPU for inference, this may be slow.") |
| time.sleep(3) |
| |
| if policy_type == 'client': |
| preprocessor, postprocessor = lambda obs: obs, lambda action: action |
| return PolicyClient(task_prompt=task_prompt), preprocessor, postprocessor, None |
|
|
| policy, preprocessor, postprocessor, pretrained_model_dir = load_native_policy_bundle( |
| pretrained_path=pretrained_path, |
| device=device, |
| strict=True, |
| ) |
| log_model.info(f"Model loaded from {pretrained_model_dir}") |
| log_model.info(f"Model type: {policy.config.type}") |
| log_model.info(f"Model n_obs_steps: {policy.config.n_obs_steps}") |
| log_model.info(f"Model device: {device}") |
| return policy, preprocessor, postprocessor, pretrained_model_dir |
|
|
| def run_single_episode(config, policy, preprocessor, postprocessor, episode, output_directory): |
| """运行单个episode""" |
| cfg = config.inference |
| seed = cfg.seed |
| task_prompt = cfg.task_prompt |
| |
| env = gym.make( |
| config.env.env_name, |
| max_episode_steps=cfg.max_episode_steps, |
| config=config, |
| ) |
|
|
| run_single_ros_manager = ROSManager() |
| |
| run_single_ros_manager.register_subscriber("/simulator/success", Bool, env_success_callback) |
|
|
| |
|
|
| start_service = rospy.ServiceProxy('/simulator/start', Trigger) |
|
|
|
|
| if cfg.policy_type != 'client': |
| log_model.info(f"policy.config.input_features: {policy.config.input_features}") |
| log_robot.info(f"env.observation_space: {env.observation_space}") |
| log_model.info(f"policy.config.output_features: {policy.config.output_features}") |
| log_robot.info(f"env.action_space: {env.action_space}") |
|
|
| |
| policy.reset() |
| observation, info = env.reset(seed=seed) |
| if cfg.policy_type != "client": |
| observation = inject_task_prompt(observation, task_prompt) |
| |
| |
| |
| |
| |
| |
| start_service(TriggerRequest()) |
|
|
| |
| |
| rewards = [] |
| cam_keys = [k for k in observation.keys() if "images" in k or "depth" in k] |
|
|
| frame_temp_dirs = {} |
| for k in cam_keys: |
| temp_dir = output_directory / f"temp_frames_{episode}_{k}" |
| temp_dir.mkdir(parents=True, exist_ok=True) |
| frame_temp_dirs[k] = temp_dir |
|
|
|
|
| average_exec_time = 0 |
| average_action_infer_time = 0 |
| average_step_time = 0 |
|
|
| step = 0 |
| done = False |
| while not done: |
| |
| if not check_control_signals(): |
| log_robot.info("🛑 收到停止信号,退出机械臂运动") |
| return 0 |
| |
| start_time = time.time() |
| if cfg.policy_type != "client": |
| observation = inject_task_prompt(observation, task_prompt) |
| observation = preprocessor(observation) |
| with torch.inference_mode(): |
| action = policy.select_action(observation) |
| log_model.info(f"Step {step}: predict action {action}") |
| action = postprocessor(action) |
| |
| action_infer_time = time.time() |
| log_model.info(f"episode {episode}, step {step}, action infer time: {action_infer_time - start_time:.3f}s") |
| average_action_infer_time += action_infer_time - start_time |
|
|
| numpy_action = action.squeeze(0).cpu().numpy() |
|
|
| log_model.info(f"Step {step}: Executing action {numpy_action}") |
| observation, reward, terminated, truncated, info = env.step(numpy_action) |
| if cfg.policy_type != "client": |
| observation = inject_task_prompt(observation, task_prompt) |
|
|
| exec_time = time.time() |
| log_model.debug(f"step {step}: exec time: {exec_time - action_infer_time:.3f}s") |
| average_exec_time += exec_time - action_infer_time |
| |
| rewards.append(reward) |
|
|
| for k in cam_keys: |
| frame_path = frame_temp_dirs[k] / f"frame_{step:04d}.png" |
| img = (observation[k].squeeze(0).cpu().numpy().transpose(1, 2, 0) * 255).astype(np.uint8) |
| if img.shape[-1] == 1: |
| img = img.squeeze(-1) |
| imageio.imwrite(str(frame_path), img) |
|
|
| |
| |
| done = terminated | truncated | done |
| done = done or success_evt.is_set() |
| step += 1 |
|
|
| end_time = time.time() |
| log_model.debug(f"Step {step} time: {end_time - start_time:.3f}s") |
| average_step_time += end_time - start_time |
| |
| |
| fps = env.unwrapped.ros_rate |
|
|
| log_model.info(f"average exec time: {average_exec_time / step:.3f}s") |
| log_model.info(f"average action infer time: {average_action_infer_time / step:.3f}s") |
| log_model.info(f"average step time: {average_step_time / step:.3f}s") |
| log_model.info(f"average sleep time: {env.unwrapped.average_sleep_time / step:.3f}s") |
| |
| for cam in cam_keys: |
| temp_dir = frame_temp_dirs[cam] |
| frame_files = sorted(temp_dir.glob("frame_*.png")) |
| frames = [imageio.imread(str(f)) for f in frame_files] |
| output_path = output_directory / f"rollout_{episode}_{cam}.mp4" |
| save_rollout_video(output_path, frames, fps) |
| |
|
|
| for f in frame_files: |
| f.unlink() |
| temp_dir.rmdir() |
| |
| del frames |
|
|
| success = success_evt.is_set() |
| |
| env.close() |
| run_single_ros_manager.close() |
| |
| del rewards |
| del observation |
| del env |
| del run_single_ros_manager |
| |
| gc.collect() |
| torch.cuda.empty_cache() |
| |
| return 1 if success else 0 |
|
|
|
|
| def kuavo_eval_autotest(config: KuavoConfig): |
| """执行自动测试""" |
| cfg = config.inference |
| eval_episodes = cfg.eval_episodes |
| seed = cfg.seed |
| policy_type = cfg.policy_type |
|
|
| |
| if cfg.pretrained_path: |
| pretrained_path = Path(cfg.pretrained_path) |
| else: |
| pretrained_path = Path(f"outputs/train/{cfg.task}/{cfg.method}/{cfg.timestamp}/epoch{cfg.epoch}") |
| output_directory = resolve_eval_output_dir(pretrained_path, Path("outputs/eval")) |
| output_directory.mkdir(parents=True, exist_ok=True) |
|
|
| |
| log_file_path = output_directory / "evaluation_autotest.log" |
| |
| with log_file_path.open("w") as log_file: |
| log_file.write(f"Evaluation Timestamp: {datetime.datetime.now()}\n") |
| log_file.write(f"Total Episodes: {eval_episodes}\n") |
| |
| |
| |
| set_seed(seed) |
| device = torch.device(cfg.device) |
| |
| task_prompt = getattr(cfg, 'task_prompt', "robot manipulation") |
|
|
| policy, preprocessor, postprocessor, pretrained_model_dir = setup_policy( |
| pretrained_path, policy_type, device, task_prompt=task_prompt |
| ) |
| |
| |
| reset_service = rospy.ServiceProxy('/simulator/reset', Trigger) |
| |
| init_service = rospy.Service("/simulator/init", Trigger, env_init_service) |
|
|
|
|
| wait_times = 8 |
| while not init_evt.is_set(): |
| log_robot.info("Waiting for first env init...") |
| if not check_control_signals(): |
| log_robot.info("🛑 收到停止信号,退出机械臂运动") |
| return |
| time.sleep(1) |
| wait_times -= 1 |
| if wait_times <=0: |
| break |
| safe_reset_service(reset_service) |
| init_evt.clear() |
|
|
| success_count = 0 |
| for episode in range(eval_episodes): |
|
|
| while not init_evt.is_set(): |
| log_robot.info("Waiting for env init...") |
| if not check_control_signals(): |
| log_robot.info("🛑 收到停止信号,退出机械臂运动") |
| return |
| time.sleep(1) |
| try: |
| result = run_single_episode(config, policy, preprocessor, postprocessor, episode, output_directory) |
| log_robot.info(f"Episode {episode+1} completed with return code: {result}") |
| |
| |
| policy.reset() |
| |
| |
| gc.collect() |
| torch.cuda.empty_cache() |
| |
| except Exception as e: |
| log_robot.error(f"Exception during episode {episode+1}: {e}") |
| log_robot.error(traceback.format_exc()) |
| result = 0 |
| safe_reset_service(reset_service) |
| init_evt.clear() |
| success_evt.clear() |
| |
| |
| gc.collect() |
| torch.cuda.empty_cache() |
| break |
|
|
| |
| episode_end_time = datetime.datetime.now().isoformat() |
| is_success = result == 1 |
| if is_success: |
| success_count += 1 |
| log_model.info(f"✅ Episode {episode+1}: Success!") |
| else: |
| log_model.info(f"❌ Episode {episode+1}: Failed!") |
|
|
|
|
|
|
| with log_file_path.open("a") as log_file: |
| log_file.write("\n") |
| log_file.write(f"Success Count: {success_count} / Already eval episodes: {episode+1}") |
| |
| safe_reset_service(reset_service) |
| init_evt.clear() |
| success_evt.clear() |
| |
|
|
| |
| log_model.info("\n" + "="*50) |
| log_model.info(f"🎯 Evaluation completed!") |
| log_model.info(f"📊 Success count: {success_count}/{eval_episodes}") |
| log_model.info(f"📈 Success rate: {success_count / eval_episodes:.2%}") |
| log_model.info(f"📁 Videos and logs saved to: {output_directory}") |
| log_model.info("="*50) |
| init_service.shutdown() |
| pause_sub.unregister() |
| stop_sub.unregister() |
|
|