Source code for mani_skill.trajectory.utils.actions.conversion

"""Utilities to convert actions between different control modes. Note that this code is specifically designed for the Franka Panda robot arm, it is not guaranteed to work for other robots."""
from typing import Union

import numpy as np
import sapien
import torch
from tqdm.auto import tqdm
from transforms3d.quaternions import quat2axangle

from mani_skill.agents.controllers import (
    PDEEPosController,
    PDEEPoseController,
    PDJointPosController,
    PDJointVelController,
)
from mani_skill.agents.controllers.base_controller import CombinedController
from mani_skill.envs.sapien_env import BaseEnv
from mani_skill.utils import common, gym_utils
from mani_skill.utils.geometry import rotation_conversions
from mani_skill.utils.structs.link import Link
from mani_skill.utils.structs.pose import Pose


[docs]def qpos_to_pd_joint_delta_pos(controller: PDJointPosController, qpos): assert type(controller) == PDJointPosController assert controller.config.use_delta assert controller.config.normalize_action delta_qpos = qpos - controller.qpos.cpu().numpy()[0] low, high = controller.config.lower, controller.config.upper return gym_utils.inv_scale_action(delta_qpos, low, high)
[docs]def qpos_to_pd_joint_target_delta_pos(controller: PDJointPosController, qpos): assert type(controller) == PDJointPosController assert controller.config.use_delta assert controller.config.use_target assert controller.config.normalize_action delta_qpos = qpos - controller._target_qpos.cpu().numpy()[0] low, high = controller.config.lower, controller.config.upper return gym_utils.inv_scale_action(delta_qpos, low, high)
[docs]def qpos_to_pd_joint_vel(controller: PDJointVelController, qpos): assert type(controller) == PDJointVelController assert controller.config.normalize_action delta_qpos = qpos - controller.qpos.cpu().numpy()[0] qvel = delta_qpos * controller._control_freq low, high = controller.config.lower, controller.config.upper return gym_utils.inv_scale_action(qvel, low, high)
[docs]def compact_axis_angle_from_quaternion(quat: np.ndarray) -> np.ndarray: theta, omega = quat2axangle(quat) # - 2 * np.pi to make the angle symmetrical around 0 if omega > np.pi: omega = omega - 2 * np.pi return omega * theta
[docs]def delta_pose_to_pd_ee_delta( controller: Union[PDEEPoseController, PDEEPosController], delta_pose: sapien.Pose, pos_only=False, ): """ Given a delta pose, convert it to a PDEEPose/PDEEPos Controller action """ # TODO (stao): update this code to be parallelized / use GPU assert isinstance(controller, PDEEPosController) assert controller.config.use_delta assert controller.config.normalize_action low, high = controller.action_space_low, controller.action_space_high if pos_only: return gym_utils.inv_scale_action( delta_pose.p, low.cpu().numpy(), high.cpu().numpy() ) delta_pose = np.r_[ delta_pose.p, compact_axis_angle_from_quaternion(delta_pose.q), ] return gym_utils.inv_scale_action(delta_pose, low.cpu().numpy(), high.cpu().numpy())
[docs]def from_pd_joint_pos_to_ee( output_mode: str, ori_actions, ori_env: BaseEnv, env: BaseEnv, render=False, pbar=None, verbose=False, ): n = len(ori_actions) if pbar is not None: pbar.reset(total=n) ori_controller: CombinedController = ori_env.agent.controller controller: CombinedController = env.agent.controller assert ( "arm" in ori_controller.controllers ), "Could not find the controller for the robot arm. This controller conversion tool requires there to be a key called 'arm' in the controller" ori_arm_controller: PDJointPosController = ori_controller.controllers["arm"] arm_controller: PDEEPoseController = controller.controllers["arm"] assert isinstance(arm_controller, PDEEPoseController) or isinstance( arm_controller, PDEEPosController ), "the arm controller must inherit PDEEPoseController or PDEEPosController" assert arm_controller.config.frame in [ "root_translation:root_aligned_body_rotation", "root_translation", ], "Currently only support the 'root_translation:root_aligned_body_rotation' ee control frame for delta pose control and 'root_translation' ee control frame for delta pos control" target_controller_is_delta = arm_controller.config.use_delta ee_link: Link = arm_controller.ee_link pos_only = arm_controller.config.frame == "root_translation" use_target = arm_controller.config.use_target == True pin_model = ori_controller.articulation.create_pinocchio_model() info = {} for t in range(n): if pbar is not None: pbar.update() ori_action = common.to_tensor(ori_actions[t], device=env.device) ori_action_dict = common.to_tensor( ori_controller.to_action_dict(ori_action), device=env.device ) output_action_dict = common.to_tensor( ori_action_dict.copy(), device=env.device ) # do not in-place modify ori_env.step(ori_action) # NOTE (stao): for high success rate of pd joint pos to pd ee delta pos/pose control, we need to use the current target qpos of the original # environment controller to compute the target ee pose to try and reach. this is because if we attempt to reach the original envs ee link pose # we may fall short and fail. full_qpos = ori_controller.articulation.get_qpos() full_qpos[ :, ori_arm_controller.active_joint_indices ] = ori_arm_controller._target_qpos pin_model.compute_forward_kinematics(full_qpos.cpu().numpy()[0]) target_ee_pose_pin = Pose.create( ori_controller.articulation.pose.sp * pin_model.get_link_pose(arm_controller.ee_link.index) ) flag = True for _ in range(4): if target_controller_is_delta: delta_q = [1, 0, 0, 0] if "root_translation" in arm_controller.config.frame: if use_target: delta_position = ( target_ee_pose_pin.p - arm_controller._target_pose.p - arm_controller.articulation.pose.p ) else: delta_position = target_ee_pose_pin.p - ee_link.pose.p if "root_aligned_body_rotation" in arm_controller.config.frame: if use_target: delta_q = ( arm_controller._target_pose.sp * target_ee_pose_pin.sp.inv() ).q else: delta_q = (ee_link.pose.sp * target_ee_pose_pin.sp.inv()).q delta_pose = sapien.Pose(delta_position.cpu().numpy()[0], delta_q) arm_action = delta_pose_to_pd_ee_delta( arm_controller, delta_pose, pos_only=pos_only ) if (np.abs(arm_action[:3])).max() > 1: # position clipping if verbose: tqdm.write(f"Position action is clipped: {arm_action[:3]}") arm_action[:3] = np.clip(arm_action[:3], -1, 1) flag = False if not pos_only: if np.linalg.norm(arm_action[3:]) > 1: # rotation clipping if verbose: tqdm.write(f"Rotation action is clipped: {arm_action[3:]}") arm_action[3:] = arm_action[3:] / np.linalg.norm(arm_action[3:]) flag = False output_action_dict["arm"] = common.to_tensor( arm_action, device=env.unwrapped.device ) output_action = controller.from_action_dict(output_action_dict) else: # NOTE (stao): We convert from quaternion to matrix to euler angles since this is how the default pd ee pose controller does it # As far as I know this is not notably any slower than a batched version of transforms3d euler2quat. output_action_dict["arm"] = torch.cat( [ common.to_tensor( target_ee_pose_pin.p[0], device=env.unwrapped.device ), common.to_tensor( rotation_conversions.matrix_to_euler_angles( rotation_conversions.quaternion_to_matrix( target_ee_pose_pin.q[0] ), "XYZ", ), device=env.unwrapped.device, ), ] ) output_action_dict["arm"][:3] -= arm_controller.articulation.pose.p[0] output_action = controller.from_action_dict(output_action_dict) _, _, _, _, info = env.step(output_action) if render: env.render_human() if flag: break return info
[docs]def from_pd_joint_pos( output_mode, ori_actions, ori_env: BaseEnv, env: BaseEnv, render=False, pbar=None, verbose=False, ): if "ee" in output_mode: return from_pd_joint_pos_to_ee(**locals()) n = len(ori_actions) if pbar is not None: pbar.reset(total=n) ori_controller: CombinedController = ori_env.agent.controller controller: CombinedController = env.agent.controller info = {} for t in range(n): if pbar is not None: pbar.update() ori_action = ori_actions[t] ori_action_dict = ori_controller.to_action_dict(ori_action) output_action_dict = ori_action_dict.copy() # do not in-place modify ori_env.step(ori_action) flag = True for _ in range(2): if output_mode == "pd_joint_delta_pos": arm_action = qpos_to_pd_joint_delta_pos( controller.controllers["arm"], ori_action_dict["arm"] ) elif output_mode == "pd_joint_target_delta_pos": arm_action = qpos_to_pd_joint_target_delta_pos( controller.controllers["arm"], ori_action_dict["arm"] ) elif output_mode == "pd_joint_vel": arm_action = qpos_to_pd_joint_vel( controller.controllers["arm"], ori_action_dict["arm"] ) else: raise NotImplementedError( f"Does not support converting pd_joint_pos to {output_mode}" ) # Assume normalized action if np.max(np.abs(arm_action)) > 1 + 1e-3: if verbose: tqdm.write(f"Arm action is clipped: {arm_action}") flag = False arm_action = np.clip(arm_action, -1, 1) output_action_dict["arm"] = arm_action output_action = controller.from_action_dict( common.to_tensor(output_action_dict, device=env.device) ) _, _, _, _, info = env.step(output_action) if render: env.render_human() if flag: break return info
[docs]def from_pd_joint_delta_pos( output_mode, ori_actions, ori_env: BaseEnv, env: BaseEnv, render=False, pbar=None, verbose=False, ): n = len(ori_actions) if pbar is not None: pbar.reset(total=n) ori_controller: CombinedController = ori_env.agent.controller controller: CombinedController = env.agent.controller ori_arm_controller: PDJointPosController = ori_controller.controllers["arm"] assert output_mode == "pd_joint_pos", output_mode assert ori_arm_controller.config.normalize_action low, high = ori_arm_controller.config.lower, ori_arm_controller.config.upper info = {} for t in range(n): if pbar is not None: pbar.update() ori_action = ori_actions[t] ori_action_dict = ori_controller.to_action_dict(ori_action) output_action_dict = ori_action_dict.copy() # do not in-place modify prev_arm_qpos = ori_arm_controller.qpos delta_qpos = gym_utils.clip_and_scale_action(ori_action_dict["arm"], low, high) arm_action = prev_arm_qpos + delta_qpos ori_env.step(ori_action) output_action_dict["arm"] = arm_action output_action = controller.from_action_dict( common.to_tensor(output_action_dict, device=env.device) ) _, _, _, _, info = env.step(output_action) if render: env.render_human() return info