| import sys |
| from pathlib import Path |
| |
| robowin_root = Path("/home/dodo/fyc/RoboTwin") |
| |
| if str(robowin_root) not in sys.path: |
| sys.path.insert(0, str(robowin_root)) |
| |
| import os |
| os.chdir(robowin_root) |
|
|
| import argparse |
| import collections |
| from collections import Counter, defaultdict |
| import logging |
| import os |
| import importlib |
| import numpy as np |
| import torch |
| import yaml |
| import json_numpy |
| import requests |
| import PIL.Image as Image |
| from tqdm import tqdm |
| import traceback |
| import json |
| import random |
| import imageio |
| import cv2 |
| import sys |
| import numpy as np |
| from scipy.spatial.transform import Rotation as R |
| from scipy.interpolate import interp1d |
| import matplotlib.pyplot as plt |
| logger = logging.getLogger(__name__) |
| torch.set_default_dtype(torch.float32) |
|
|
| ALL_TASKS = [ |
| "adjust_bottle", |
| "beat_block_hammer", |
| "blocks_ranking_rgb", |
| "blocks_ranking_size", |
| "click_alarmclock", "click_bell", "dump_bin_bigbin", "grab_roller", "handover_block", |
| "handover_mic", "hanging_mug", "lift_pot", |
| "move_can_pot", |
| "move_pillbottle_pad", |
| "move_playingcard_away", "move_stapler_pad", "open_laptop", "open_microwave", |
| "pick_diverse_bottles", |
| "pick_dual_bottles", |
| "place_a2b_left", "place_a2b_right", |
| "place_bread_basket", "place_bread_skillet", "place_burger_fries", "place_can_basket", |
| "place_cans_plasticbox", "place_container_plate", |
| "place_dual_shoes", |
| "place_empty_cup", |
| "place_fan", "place_mouse_pad", |
| "place_object_basket", |
| "place_object_scale", "place_object_stand", "place_phone_stand", "place_shoe", "press_stapler", |
| "put_bottles_dustbin", |
| "put_object_cabinet", "rotate_qrcode", "scan_object", |
| "shake_bottle_horizontally", "shake_bottle", |
| "stack_blocks_three", "stack_blocks_two", |
| "stack_bowls_three", |
| "stack_bowls_two", "stamp_seal", "turn_switch" |
| ] |
|
|
| print("Number of tasks evaluating:", len(ALL_TASKS)) |
|
|
| def quat_to_rotate6D(q: np.ndarray) -> np.ndarray: |
| return R.from_quat(q).as_matrix()[..., :, :2].reshape(q.shape[:-1] + (6,)) |
|
|
|
|
| def rotate6D_to_quat(v6: np.ndarray) -> np.ndarray: |
| v6 = np.asarray(v6) |
| if v6.shape[-1] != 6: |
| raise ValueError("Last dimension must be 6 (got %s)" % (v6.shape[-1],)) |
| a1 = v6[..., 0:5:2] |
| a2 = v6[..., 1:6:2] |
| b1 = a1 / np.linalg.norm(a1, axis=-1, keepdims=True) |
| proj = np.sum(b1 * a2, axis=-1, keepdims=True) * b1 |
| b2 = a2 - proj |
| b2 = b2 / np.linalg.norm(b2, axis=-1, keepdims=True) |
| b3 = np.cross(b1, b2) |
| rot_mats = np.stack((b1, b2, b3), axis=-1) |
| return R.from_matrix(rot_mats).as_quat() |
|
|
| def decode_image_from_bytes(camera_rgb_image): |
| if isinstance(camera_rgb_image, (bytes, bytearray)): camera_rgb_image = np.frombuffer(camera_rgb_image, dtype=np.uint8) |
| rgb = cv2.imdecode(camera_rgb_image, cv2.IMREAD_COLOR) |
| if rgb is None: |
| rgb = np.frombuffer(camera_rgb_image, dtype=np.uint8) |
| if rgb.size == 2764800: |
| rgb = rgb.reshape(720, 1280, 3) |
| elif rgb.size == 921600: |
| rgb = rgb.reshape(480, 640, 3) |
| return Image.fromarray(rgb) |
|
|
|
|
| class ClientModel: |
| def __init__(self, host, port): |
| self.url = f"http://{host}:{port}/act" |
| self.vision_record = [] |
| |
| def set_instruction(self, instruction): |
| self.instruction = instruction |
| |
| def return_vision_record(self): |
| video = np.stack(self.vision_record) |
| self.vision_record = [] |
| return video |
| |
| def step(self, obs): |
| head_view = obs['observation']['head_camera']['rgb'] |
| left_view = obs['observation']['left_camera']['rgb'] |
| right_view = obs['observation']['right_camera']['rgb'] |
| front_view = obs['observation']['front_camera']['rgb'] |
| image_obs = np.stack([head_view, left_view, right_view, front_view])[None, ] |
| left_ee = np.expand_dims(np.array(obs["endpose"]["left_endpose"]), axis=0) |
| right_ee = np.expand_dims(np.array(obs["endpose"]["right_endpose"]), axis=0) |
| left_grip = np.expand_dims(np.array(obs["endpose"]["left_gripper"]), axis=0) |
| right_grip = np.expand_dims(np.array(obs["endpose"]["right_gripper"]), axis=0) |
| left_grip = 1 - left_grip * 2 |
| right_grip = 1 - right_grip * 2 |
| abs_eef = np.concatenate([ |
| left_ee[:, :3], |
| quat_to_rotate6D(left_ee[:, 3:]), |
| left_grip[:, None], |
| right_ee[:, :3], |
| quat_to_rotate6D(right_ee[:, 3:]), |
| right_grip[:, None] |
| ], axis=-1) |
| self.vision_record.append(image_obs) |
| query = { |
| "domain_id": 6, |
| "proprio": json_numpy.dumps(abs_eef.squeeze(0)), |
| "language_instruction": self.instruction, |
| "image0": json_numpy.dumps(head_view), |
| "image1": json_numpy.dumps(left_view), |
| "image2": json_numpy.dumps(right_view)} |
| |
| response = requests.post(self.url, json=query) |
| action = np.array(response.json()['action']) |
| return action |
|
|
| def class_decorator(task_name): |
| envs_module = importlib.import_module(f"envs.{task_name}") |
| try: |
| env_class = getattr(envs_module, task_name) |
| env_instance = env_class() |
| except: |
| raise SystemExit("No such task") |
| return env_instance |
|
|
|
|
| def get_embodiment_config(robot_file): |
| robot_config_file = os.path.join(robot_file, "config.yml") |
| with open(robot_config_file, "r", encoding="utf-8") as f: |
| embodiment_args = yaml.load(f.read(), Loader=yaml.FullLoader) |
| return embodiment_args |
|
|
|
|
| def load_env(task_name, task_config): |
| CONFIGS_PATH = "task_config" |
| task = class_decorator(task_name) |
|
|
| config_path = os.path.join(CONFIGS_PATH, f"{task_config}.yml") |
| with open(config_path, "r", encoding="utf-8") as f: |
| args = yaml.load(f.read(), Loader=yaml.FullLoader) |
|
|
| args['task_name'] = task_name |
|
|
| embodiment_type = args.get("embodiment") |
| embodiment_config_path = os.path.join(CONFIGS_PATH, "_embodiment_config.yml") |
|
|
| with open(embodiment_config_path, "r", encoding="utf-8") as f: |
| _embodiment_types = yaml.load(f.read(), Loader=yaml.FullLoader) |
|
|
| def get_embodiment_file(embodiment_type): |
| robot_file = _embodiment_types[embodiment_type]["file_path"] |
| if robot_file is None: |
| raise ValueError("missing embodiment files") |
| return robot_file |
|
|
| with open(os.path.join(CONFIGS_PATH, "_camera_config.yml"), "r", encoding="utf-8") as f: |
| _camera_config = yaml.load(f.read(), Loader=yaml.FullLoader) |
|
|
| head_camera_type = args["camera"]["head_camera_type"] |
| args["head_camera_h"] = _camera_config[head_camera_type]["h"] |
| args["head_camera_w"] = _camera_config[head_camera_type]["w"] |
|
|
| if len(embodiment_type) == 1: |
| args["left_robot_file"] = get_embodiment_file(embodiment_type[0]) |
| args["right_robot_file"] = get_embodiment_file(embodiment_type[0]) |
| args["dual_arm_embodied"] = True |
| elif len(embodiment_type) == 3: |
| args["left_robot_file"] = get_embodiment_file(embodiment_type[0]) |
| args["right_robot_file"] = get_embodiment_file(embodiment_type[1]) |
| args["embodiment_dis"] = embodiment_type[2] |
| args["dual_arm_embodied"] = False |
| else: |
| raise ValueError("number of embodiment config parameters should be 1 or 3") |
|
|
| args["left_embodiment_config"] = get_embodiment_config(args["left_robot_file"]) |
| args["right_embodiment_config"] = get_embodiment_config(args["right_robot_file"]) |
|
|
| args["embodiment_name"] = "+".join(embodiment_type) if len(embodiment_type) > 1 else embodiment_type[0] |
| args["task_config"] = task_config |
|
|
| return task, args |
|
|
|
|
| def _rollout(env, policy): |
| success_flag = False |
| error_flag = False |
| env._update_render() |
| if env.render_freq: env.viewer.render() |
| env.actor_pose = True |
| images = [] |
| idx = 0 |
| obs = env.get_obs() |
| |
| for j in range(10): |
| actions = policy.step(obs) |
| left_xyz = actions[:, :3] |
| left_rotate6d = actions[:, 3:9] |
| left_gripper = actions[:, 9:10] |
| |
| left_quat = rotate6D_to_quat(left_rotate6d) |
| left_grip = 1 - 2 * (left_gripper > 0.7) |
| |
| left_new = np.concatenate([left_xyz, left_quat, left_grip], axis=1) |
| |
| right_xyz = actions[:, 10:13].reshape(-1, 3) |
| right_rotate6d = actions[:, 13:19].reshape(-1, 6) |
| right_quat = rotate6D_to_quat(right_rotate6d) |
| right_gripper = actions[:, 19:20].reshape(-1, 1) |
| |
| right_grip = 1 - 2 * (right_gripper > 0.7) |
| right_new = np.concatenate([right_xyz, right_quat, right_grip], axis=1) |
| |
| rollout_action = np.concatenate([left_new, right_new], axis=1) |
| for action in tqdm(rollout_action): |
| env.take_action(action, action_type='ee') |
| obs = env.get_obs() |
| obs['endpose']['left_endpose'] = list(action[:7].reshape(7,)) |
| obs['endpose']['right_endpose'] = list(action[8:-1].reshape(7,)) |
| images.append(obs["observation"]["head_camera"]["rgb"] ) |
| idx += 1 |
| if env.check_success(): |
| success_flag = True |
| break |
| if env.actor_pose == False: |
| print('false actor_pose') |
| error_flag = True |
| break |
| if error_flag: |
| print("\nfail due to false actor_pose!") |
| return 0, images |
|
|
| if success_flag: |
| print("\nsuccess!") |
| env.suc +=1 |
| return 1, images |
| |
| if env.actor_pose == False: |
| print('false actor_pose2') |
| break |
| |
| j += 1 |
| env._update_render() |
| print("\nfail!") |
| return 0, images |
|
|
|
|
| def eval_episodes(task_name, task_config, policy, test_num=10, seed=0, eval_log_dir=None, instruction_type=None): |
| """ |
| rollout several episodes and log the mean episode return |
| """ |
| if not os.path.exists(os.path.join(eval_log_dir, task_name)): |
| print('save to', os.path.join(eval_log_dir, task_name)) |
| os.makedirs(os.path.join(eval_log_dir, task_name)) |
| |
| TASK_ENV, args = load_env(task_name, task_config) |
|
|
| st_seed = 2000 * (1 + seed) |
|
|
| expert_check = True |
| TASK_ENV.suc = 0 |
| TASK_ENV.test_num = 0 |
| now_id = 0 |
| succ_seed = 0 |
| suc_test_seed_list = [] |
| now_seed = st_seed |
| clear_cache_freq = args["clear_cache_freq"] |
| args['policy_name'] = 'V4' |
|
|
| args["eval_mode"] = True |
| args["render_freq"] = 0 |
| args['ckpt_setting'] = '60k' |
| while succ_seed < test_num: |
| render_freq = args["render_freq"] |
| print('Running test', now_id) |
| if expert_check: |
| try: |
| TASK_ENV.setup_demo(now_ep_num=now_id, seed=now_seed, is_test=True, **args) |
| episode_info = TASK_ENV.play_once() |
| TASK_ENV.close_env() |
| except Exception as e: |
| |
| print("Error: ", e) |
| |
| TASK_ENV.close_env() |
| now_seed += 1 |
| args["render_freq"] = render_freq |
| continue |
|
|
| if (not expert_check) or (TASK_ENV.plan_success and TASK_ENV.check_success()): |
| succ_seed += 1 |
| suc_test_seed_list.append(now_seed) |
| else: |
| now_seed += 1 |
| args["render_freq"] = render_freq |
| continue |
|
|
| args["render_freq"] = render_freq |
|
|
| TASK_ENV.setup_demo(now_ep_num=now_id, seed=now_seed, is_test=True, **args) |
| instruction = args["task_name"].replace('_', ' ') |
| print('instruction:', instruction) |
| policy.set_instruction(instruction=instruction) |
| |
| try: |
| status, images = _rollout(TASK_ENV, policy) |
| except Exception as e: |
| TASK_ENV.close_env() |
| now_seed += 1 |
| args["render_freq"] = render_freq |
| continue |
| save_path = f'{eval_log_dir}/{task_name}/{now_id}_{status}.mp4' |
| save_video(save_path, images) |
| |
| metrics = {f'sim/{task_name}': status} |
| save_path = f'{eval_log_dir}/results.json' |
| _log_results(metrics, save_path) |
| now_id += 1 |
| TASK_ENV.close_env(clear_cache=((succ_seed + 1) % clear_cache_freq == 0)) |
| if TASK_ENV.render_freq: |
| TASK_ENV.viewer.close() |
|
|
| TASK_ENV.test_num += 1 |
|
|
| print(f"Success rate: {round(TASK_ENV.suc/TASK_ENV.test_num*100, 1)}%") |
| now_seed += 1 |
| try: |
| point = round(TASK_ENV.suc/TASK_ENV.test_num*100, 1) |
| test_done = True |
| except Exception as e: |
| test_done = False |
| print('redo tests due to:', e) |
| metrics = {f'sim/{task_name}': point} |
| _log_results(metrics, f'{eval_log_dir}/perc.json') |
|
|
| return now_seed, TASK_ENV.suc |
|
|
|
|
| def save_video(output_path, frames, fps=30): |
| print('saving video to ', output_path) |
| imageio.mimsave(output_path, frames, fps=fps) |
|
|
|
|
| def _log_results(metrics, log_path): |
| with open(log_path, 'a+') as f: |
| line = json.dumps(metrics) |
| f.write(line+'\n') |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser(description="Evaluate a trained model on multistep sequences with language goals.") |
| parser.add_argument("--host", default='0.0.0.0', help="Your client host ip") |
| parser.add_argument("--port", default='8001', help="Your client port") |
| parser.add_argument("--eval_log_dir", default='/home/dodo/fyc/HeteroDiffusionPolicy/AbsEEFFlowV4/runnings/RoboTwin/', type=str, help="Where to log the evaluation results.") |
| parser.add_argument("--device", default=0, type=int, help="CUDA device") |
| parser.add_argument("--num_episodes", default=1000, type=int) |
| parser.add_argument("--seed", default=0, type=int) |
| parser.add_argument("--task_name", type=str, required=True, help="Name of the task (envs.<task>)") |
| parser.add_argument("--task_config", type=str, required=True, help="Task config name (without .yml)") |
| parser.add_argument("--output_path", type=str, required=True, help="Where to save the output video") |
| parser.add_argument("--instruction_type", type=str, required=False, help="Where to save the output video") |
| args = parser.parse_args() |
| kwargs = vars(args) |
| |
| model = ClientModel(host=kwargs['host'], port=kwargs['port']) |
| if args.task_name == 'all': |
| for task in tqdm(ALL_TASKS): |
| print(f"Evaluating task {task} for {kwargs['num_episodes']} episodes...") |
| rewards = eval_episodes(task_name=task, |
| task_config=args.task_config, |
| policy=model, |
| seed=args.seed, |
| test_num=kwargs['num_episodes'], |
| eval_log_dir=kwargs['eval_log_dir'], |
| instruction_type=args.instruction_type) |
| else: |
| print(f"Evaluating task {args.task_name} for {kwargs['num_episodes']} episodes...") |
| rewards = eval_episodes(task_name=args.task_name, |
| task_config=args.task_config, |
| policy=model, |
| seed=args.seed, |
| test_num=kwargs['num_episodes'], |
| eval_log_dir=kwargs['eval_log_dir'], |
| instruction_type=args.instruction_type) |
| if __name__ == "__main__": |
| main() |
|
|