From 9a03a2617283f4ba45229d357d65ccd87e1cf8f2 Mon Sep 17 00:00:00 2001 From: Michel Breyer Date: Tue, 6 Jul 2021 14:00:04 +0200 Subject: [PATCH] Cleanup --- bt_sim_node.py | 218 ++++++-------- config/active_grasp.yaml | 2 +- controller.py | 71 +++++ ...{simulation.launch => active_grasp.launch} | 11 +- ...a_visualization.rviz => active_grasp.rviz} | 103 ++++--- policies.py | 277 +++++------------- run.py | 32 -- sim_grasp.py | 49 ++++ simulation.py | 92 +++++- utils.py | 13 + 10 files changed, 445 insertions(+), 423 deletions(-) create mode 100644 controller.py rename launch/{simulation.launch => active_grasp.launch} (66%) rename launch/{panda_visualization.rviz => active_grasp.rviz} (81%) delete mode 100644 run.py create mode 100644 sim_grasp.py create mode 100644 utils.py diff --git a/bt_sim_node.py b/bt_sim_node.py index 49bbe15..04c6991 100755 --- a/bt_sim_node.py +++ b/bt_sim_node.py @@ -3,17 +3,14 @@ import actionlib import argparse import cv_bridge -import numpy as np -import rospy -import tf2_ros - -import control_msgs.msg import franka_gripper.msg from geometry_msgs.msg import PoseStamped +import numpy as np +import rospy from sensor_msgs.msg import JointState, Image, CameraInfo -import std_srvs.srv +import std_srvs.srv as std_srvs +import tf2_ros -from robot_utils.controllers import CartesianPoseController from robot_utils.ros.conversions import * from simulation import Simulation @@ -21,76 +18,36 @@ from simulation import Simulation class BtSimNode: def __init__(self, gui): self.sim = Simulation(gui=gui) - - self.controller_update_rate = 60 - self.joint_state_publish_rate = 60 - self.camera_publish_rate = 5 - - self.cv_bridge = cv_bridge.CvBridge() - - self.setup_robot_topics() - self.setup_camera_topics() - self.setup_controllers() - self.setup_gripper_interface() - self.broadcast_transforms() - - rospy.Service("reset", std_srvs.srv.Trigger, self.reset) + self.robot_state_interface = RobotStateInterface(self.sim.arm, self.sim.gripper) + self.arm_interface = ArmInterface(self.sim.arm, self.sim.controller) + self.gripper_interface = GripperInterface(self.sim.gripper) + self.camera_interface = CameraInterface(self.sim.camera) self.step_cnt = 0 self.reset_requested = False - def setup_robot_topics(self): - self.joint_pub = rospy.Publisher("joint_states", JointState, queue_size=10) + self.advertise_services() + self.broadcast_transforms() - def setup_controllers(self): - self.cartesian_pose_controller = CartesianPoseController(self.sim.arm) - rospy.Subscriber("command", PoseStamped, self.target_pose_cb) - - def target_pose_cb(self, msg): - assert msg.header.frame_id == self.sim.arm.base_frame - self.cartesian_pose_controller.x_d = from_pose_msg(msg.pose) - - def setup_gripper_interface(self): - self.gripper_interface = GripperInterface(self.sim.gripper) - - def setup_camera_topics(self): - self.cam_info_msg = to_camera_info_msg(self.sim.camera.intrinsic) - self.cam_info_msg.header.frame_id = "cam_optical_frame" - self.cam_info_pub = rospy.Publisher( - "/cam/depth/camera_info", - CameraInfo, - queue_size=10, - ) - self.depth_pub = rospy.Publisher("/cam/depth/image_raw", Image, queue_size=10) + def advertise_services(self): + rospy.Service("reset", std_srvs.Trigger, self.reset) def broadcast_transforms(self): self.static_broadcaster = tf2_ros.StaticTransformBroadcaster() - - msgs = [] - msg = geometry_msgs.msg.TransformStamped() - msg.header.stamp = rospy.Time.now() - msg.header.frame_id = "world" - msg.child_frame_id = "panda_link0" - msg.transform = to_transform_msg(self.sim.T_W_B) - msgs.append(msg) - - msg = geometry_msgs.msg.TransformStamped() - msg.header.stamp = rospy.Time.now() - msg.header.frame_id = "world" - msg.child_frame_id = "task" - msg.transform = to_transform_msg(Transform.translation(self.sim.origin)) - - msgs.append(msg) - + msgs = [ + to_transform_stamped_msg(self.sim.T_W_B, "world", "panda_link0"), + to_transform_stamped_msg( + Transform.translation(self.sim.origin), "world", "task" + ), + ] self.static_broadcaster.sendTransform(msgs) def reset(self, req): self.reset_requested = True rospy.sleep(1.0) # wait for the latest sim step to finish self.sim.reset() - self.cartesian_pose_controller.x_d = self.sim.arm.pose() self.step_cnt = 0 self.reset_requested = False - return std_srvs.srv.TriggerResponse(success=True) + return std_srvs.TriggerResponse(success=True) def run(self): rate = rospy.Rate(self.sim.rate) @@ -102,18 +59,22 @@ class BtSimNode: rate.sleep() def handle_updates(self): - if self.step_cnt % int(self.sim.rate / self.controller_update_rate) == 0: - self.cartesian_pose_controller.update() - self.gripper_interface.update(1.0 / self.controller_update_rate) - if self.step_cnt % int(self.sim.rate / self.joint_state_publish_rate) == 0: - self.publish_joint_state() - if self.step_cnt % int(self.sim.rate / self.camera_publish_rate) == 0: - self.publish_cam_info() - self.publish_cam_imgs() + self.robot_state_interface.update() + self.arm_interface.update() + self.gripper_interface.update(self.sim.dt) + if self.step_cnt % int(self.sim.rate / 5) == 0: + self.camera_interface.update() - def publish_joint_state(self): - q, dq = self.sim.arm.get_state() - width = self.sim.gripper.read() + +class RobotStateInterface: + def __init__(self, arm, gripper): + self.arm = arm + self.gripper = gripper + self.joint_pub = rospy.Publisher("joint_states", JointState, queue_size=10) + + def update(self): + q, _ = self.arm.get_state() + width = self.gripper.read() msg = JointState() msg.header.stamp = rospy.Time.now() msg.name = ["panda_joint{}".format(i) for i in range(1, 8)] + [ @@ -121,18 +82,23 @@ class BtSimNode: "panda_finger_joint2", ] msg.position = np.r_[q, 0.5 * width, 0.5 * width] - msg.velocity = dq self.joint_pub.publish(msg) - def publish_cam_info(self): - self.cam_info_msg.header.stamp = rospy.Time.now() - self.cam_info_pub.publish(self.cam_info_msg) - def publish_cam_imgs(self): - _, depth = self.sim.camera.get_image() - depth_msg = self.cv_bridge.cv2_to_imgmsg(depth) - depth_msg.header.stamp = rospy.Time.now() - self.depth_pub.publish(depth_msg) +class ArmInterface: + def __init__(self, arm, controller): + self.arm = arm + self.controller = controller + rospy.Subscriber("command", PoseStamped, self.target_cb) + + def update(self): + q, _ = self.arm.get_state() + cmd = self.controller.update(q) + self.arm.set_desired_joint_velocities(cmd) + + def target_cb(self, msg): + assert msg.header.frame_id == self.arm.base_frame + self.controller.x_d = from_pose_msg(msg.pose) class GripperInterface: @@ -143,61 +109,59 @@ class GripperInterface: franka_gripper.msg.MoveAction, auto_start=False, ) - self.move_server.register_goal_callback(self.goal_cb) + self.move_server.register_goal_callback(self.move_action_goal_cb) self.move_server.start() - def goal_cb(self): + self.grasp_server = actionlib.SimpleActionServer( + "/franka_gripper/grasp", + franka_gripper.msg.GraspAction, + auto_start=False, + ) + self.grasp_server.register_goal_callback(self.grasp_action_goal_cb) + self.grasp_server.start() + + def move_action_goal_cb(self): + self.elapsed_time_since_move_action_goal = 0.0 goal = self.move_server.accept_new_goal() - self.gripper.move(goal.width) - self.elapsed_time = 0.0 + self.gripper.set_desired_width(goal.width) + + def grasp_action_goal_cb(self): + self.elapsed_time_since_grasp_action_goal = 0.0 + goal = self.grasp_server.accept_new_goal() + self.gripper.set_desired_width(goal.width) def update(self, dt): if self.move_server.is_active(): - self.elapsed_time += dt - if self.elapsed_time > 1.0: + self.elapsed_time_since_move_action_goal += dt + if self.elapsed_time_since_move_action_goal > 1.0: self.move_server.set_succeeded() + if self.grasp_server.is_active(): + self.elapsed_time_since_grasp_action_goal += dt + if self.elapsed_time_since_grasp_action_goal > 1.0: + self.grasp_server.set_succeeded() -class JointTrajectoryController: - def __init__(self, arm, stopped=False): - self.arm = arm - self.action_server = actionlib.SimpleActionServer( - "position_joint_trajectory_controller/follow_joint_trajectory", - control_msgs.msg.FollowJointTrajectoryAction, - auto_start=False, +class CameraInterface: + def __init__(self, camera): + self.camera = camera + self.cv_bridge = cv_bridge.CvBridge() + self.cam_info_msg = to_camera_info_msg(self.camera.intrinsic) + self.cam_info_msg.header.frame_id = "cam_optical_frame" + self.cam_info_pub = rospy.Publisher( + "/cam/depth/camera_info", + CameraInfo, + queue_size=10, ) - self.action_server.register_goal_callback(self.goal_cb) - self.action_server.start() + self.depth_pub = rospy.Publisher("/cam/depth/image_raw", Image, queue_size=10) - self.is_running = False - if not stopped: - self.start() - - def goal_cb(self): - goal = self.action_server.accept_new_goal() - self.elapsed_time = 0.0 - self.points = iter(goal.trajectory.points) - self.next_point = next(self.points) - - def start(self): - self.is_running = True - - def stop(self): - self.is_running = False - - def update(self, dt): - if not (self.is_running and self.action_server.is_active()): - return - - self.elapsed_time += dt - if self.elapsed_time > self.next_point.time_from_start.to_sec(): - try: - self.next_point = next(self.points) - except StopIteration: - self.action_server.set_succeeded() - return - - self.arm.set_desired_joint_positions(self.next_point.positions) + def update(self): + stamp = rospy.Time.now() + self.cam_info_msg.header.stamp = stamp + self.cam_info_pub.publish(self.cam_info_msg) + _, depth = self.camera.get_image() + depth_msg = self.cv_bridge.cv2_to_imgmsg(depth) + depth_msg.header.stamp = stamp + self.depth_pub.publish(depth_msg) def create_parser(): @@ -208,10 +172,8 @@ def create_parser(): def main(): rospy.init_node("bt_sim") - parser = create_parser() args, _ = parser.parse_known_args() - server = BtSimNode(args.gui) server.run() diff --git a/config/active_grasp.yaml b/config/active_grasp.yaml index 7fd374e..6ff8ce8 100644 --- a/config/active_grasp.yaml +++ b/config/active_grasp.yaml @@ -9,5 +9,5 @@ active_grasp: info_topic: /cam/depth/camera_info depth_topic: /cam/depth/image_raw vgn: - model: $(find vgn)/data/models/vgn_conv.pth + model: $(find vgn)/assets/models/vgn_conv.pth finger_depth: 0.05 diff --git a/controller.py b/controller.py new file mode 100644 index 0000000..deeed8f --- /dev/null +++ b/controller.py @@ -0,0 +1,71 @@ +import numpy as np +import rospy + +from robot_utils.ros import tf +from robot_utils.ros.panda import PandaGripperClient +from robot_utils.spatial import Rotation, Transform +from utils import CartesianPoseControllerClient + + +class GraspController: + def __init__(self, policy): + self.policy = policy + self.controller = CartesianPoseControllerClient() + self.gripper = PandaGripperClient() + self.load_parameters() + + def load_parameters(self): + self.T_G_EE = Transform.from_list(rospy.get_param("~ee_grasp_offset")).inv() + + def run(self): + self.reset() + grasp = self.explore() + if grasp: + self.execute_grasp(grasp) + + def reset(self): + raise NotImplementedError + + def explore(self): + self.policy.activate() + r = rospy.Rate(self.policy.rate) + while True: + cmd = self.policy.update() + if self.policy.done: + break + self.controller.send_target(cmd) + r.sleep() + return self.policy.best_grasp + + def execute_grasp(self, grasp): + T_B_G = self.postprocess(grasp) + + self.gripper.move(0.08) + + # Move to an initial pose offset. + self.controller.send_target( + T_B_G * Transform.translation([0, 0, -0.05]) * self.T_G_EE + ) + rospy.sleep(3.0) + + # Approach grasp pose. + self.controller.send_target(T_B_G * self.T_G_EE) + rospy.sleep(1.0) + + # Close the fingers. + self.gripper.grasp() + + # Lift the object. + target = Transform.translation([0, 0, 0.2]) * T_B_G * self.T_G_EE + self.controller.send_target(target) + rospy.sleep(2.0) + + # Check whether the object remains in the hand + return self.gripper.read() > 0.005 + + def postprocess(self, T_B_G): + # Ensure that the camera is pointing forward. + rot = T_B_G.rotation + if rot.as_matrix()[:, 0][0] < 0: + T_B_G.rotation = rot * Rotation.from_euler("z", np.pi) + return T_B_G diff --git a/launch/simulation.launch b/launch/active_grasp.launch similarity index 66% rename from launch/simulation.launch rename to launch/active_grasp.launch index b61c993..6d055b9 100644 --- a/launch/simulation.launch +++ b/launch/active_grasp.launch @@ -1,19 +1,12 @@ - - - - + - - - - - + diff --git a/launch/panda_visualization.rviz b/launch/active_grasp.rviz similarity index 81% rename from launch/panda_visualization.rviz rename to launch/active_grasp.rviz index 74e45a6..4776f8c 100644 --- a/launch/panda_visualization.rviz +++ b/launch/active_grasp.rviz @@ -4,9 +4,9 @@ Panels: Name: Displays Property Tree Widget: Expanded: - - /Global Options1 + - /TF1/Tree1 Splitter Ratio: 0.5 - Tree Height: 549 + Tree Height: 547 - Class: rviz/Selection Name: Selection - Class: rviz/Tool Properties @@ -25,7 +25,7 @@ Panels: Experimental: false Name: Time SyncMode: 0 - SyncSource: Scene Cloud + SyncSource: "" Preferences: PromptSaveOnExit: true Toolbars: @@ -137,13 +137,13 @@ Visualization Manager: Frames: All Enabled: false cam_optical_frame: - Value: true + Value: false panda_hand: - Value: true + Value: false panda_leftfinger: Value: false panda_link0: - Value: true + Value: false panda_link1: Value: false panda_link2: @@ -164,6 +164,8 @@ Visualization Manager: Value: false task: Value: true + world: + Value: true Marker Alpha: 1 Marker Scale: 0.5 Name: TF @@ -171,34 +173,27 @@ Visualization Manager: Show Axes: true Show Names: true Tree: - panda_link0: - panda_link1: - panda_link2: - panda_link3: - panda_link4: - panda_link5: - panda_link6: - panda_link7: - panda_link8: - panda_hand: - cam_optical_frame: - {} - panda_leftfinger: - {} - panda_rightfinger: - {} + world: + panda_link0: + panda_link1: + panda_link2: + panda_link3: + panda_link4: + panda_link5: + panda_link6: + panda_link7: + panda_link8: + panda_hand: + cam_optical_frame: + {} + panda_leftfinger: + {} + panda_rightfinger: + {} task: {} Update Interval: 0 Value: true - - Class: rviz/Marker - Enabled: true - Marker Topic: /workspace - Name: Workspace - Namespaces: - "": true - Queue Size: 100 - Value: true - Alpha: 0.20000000298023224 Autocompute Intensity Bounds: false Autocompute Value Bounds: @@ -214,7 +209,9 @@ Visualization Manager: Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 + Max Intensity: 4096 Min Color: 0; 0; 0 + Min Intensity: 0 Name: TSDF Position Transformer: XYZ Queue Size: 10 @@ -243,7 +240,7 @@ Visualization Manager: Invert Rainbow: false Max Color: 255; 255; 255 Min Color: 0; 0; 0 - Name: Scene Cloud + Name: SceneCloud Position Transformer: XYZ Queue Size: 10 Selectable: true @@ -255,12 +252,36 @@ Visualization Manager: Use Fixed Frame: true Use rainbow: true Value: true + - Alpha: 1 + Axes Length: 0.05000000074505806 + Axes Radius: 0.004999999888241291 + Class: rviz/Pose + Color: 255; 25; 0 + Enabled: true + Head Length: 0.30000001192092896 + Head Radius: 0.10000000149011612 + Name: EETarget + Queue Size: 10 + Shaft Length: 1 + Shaft Radius: 0.05000000074505806 + Shape: Axes + Topic: /command + Unreliable: false + Value: true + - Class: rviz/MarkerArray + Enabled: true + Marker Topic: /path + Name: CameraPath + Namespaces: + {} + Queue Size: 100 + Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /grasps - Name: Predicted Grasps + Name: PredictedGrasps Namespaces: - "": true + {} Queue Size: 100 Value: true Enabled: true @@ -291,7 +312,7 @@ Visualization Manager: Views: Current: Class: rviz/Orbit - Distance: 1.5115783214569092 + Distance: 1.5376757383346558 Enable Stereo Rendering: Stereo Eye Separation: 0.05999999865889549 Stereo Focal Distance: 1 @@ -307,17 +328,17 @@ Visualization Manager: Invert Z Axis: false Name: Current View Near Clip Distance: 0.009999999776482582 - Pitch: 0.20979644358158112 + Pitch: 0.21479728817939758 Target Frame: - Yaw: 5.238524913787842 + Yaw: 5.193514823913574 Saved: ~ Window Geometry: Displays: collapsed: false - Height: 846 + Height: 844 Hide Left Dock: false Hide Right Dock: true - QMainWindow State: 000000ff00000000fd000000040000000000000156000002b0fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002b0000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d000002b0000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004b00000003efc0100000002fb0000000800540069006d00650100000000000004b0000002eb00fffffffb0000000800540069006d0065010000000000000450000000000000000000000354000002b000000004000000040000000800000008fc00000002000000020000000000000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + QMainWindow State: 000000ff00000000fd000000040000000000000156000002aefc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000002ae000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f000002b0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d000002b0000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004f30000003efc0100000002fb0000000800540069006d00650100000000000004f3000002eb00fffffffb0000000800540069006d0065010000000000000450000000000000000000000397000002ae00000004000000040000000800000008fc00000002000000020000000000000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false Time: @@ -326,6 +347,6 @@ Window Geometry: collapsed: false Views: collapsed: true - Width: 1200 - X: 720 - Y: 27 + Width: 1267 + X: 100 + Y: 457 diff --git a/policies.py b/policies.py index d275890..d1d350e 100644 --- a/policies.py +++ b/policies.py @@ -2,259 +2,128 @@ import cv_bridge import numpy as np from pathlib import Path import rospy -import scipy.interpolate - -from geometry_msgs.msg import PoseStamped from sensor_msgs.msg import Image, CameraInfo -import std_srvs.srv from visualization_msgs.msg import Marker, MarkerArray - -from robot_utils.perception import * -from robot_utils.spatial import Rotation, Transform -from robot_utils.ros.conversions import * -from robot_utils.ros.panda import PandaGripperClient from robot_utils.ros import tf +from robot_utils.ros.conversions import * from robot_utils.ros.rviz import * +from robot_utils.spatial import Transform +from vgn.detection import VGN, compute_grasps +from vgn.perception import UniformTSDFVolume import vgn.vis -from vgn.detection import VGN, compute_grasps - -def get_controller(name): +def get_policy(name): if name == "single-view": return SingleViewBaseline() - elif name == "fixed-trajectory": - return FixedTrajectoryBaseline() - elif name == "mvp": - return MultiViewPicking() else: raise ValueError("{} policy does not exist.".format(name)) -class BaseController: +class BasePolicy: def __init__(self): - self.frame = rospy.get_param("~frame_id") - self.length = rospy.get_param("~length") - self.cv_bridge = cv_bridge.CvBridge() - self.reset_client = rospy.ServiceProxy("/reset", std_srvs.srv.Trigger) - self.tsdf = UniformTSDFVolume(0.3, 40) self.vgn = VGN(Path(rospy.get_param("vgn/model"))) - self._setup_robot_connection() - self._setup_camera_connection() - self._setup_rviz_connection() - self._lookup_transforms() + self.load_parameters() + self.lookup_transforms() + self.connect_to_camera() + self.connect_to_rviz() - def run(self): - self.reset() - self.explore() - self.execute_grasp() - # self.release_object() - - def reset(self): - self._reset_env() - self._clear_rviz() - rospy.sleep(1.0) # wait for states to be updated - self._init_policy() - - self.viewpoints = [] - self.done = False - self.best_grasp = None - - def explore(self): - r = rospy.Rate(self.rate) - while not self.done: - self._update() - r.sleep() - - def execute_grasp(self): - if not self.best_grasp: - return - - self.gripper.move(0.08) - - # Ensure that the camera is pointing forward. - T_O_G = self.best_grasp.pose - rot = T_O_G.rotation - if rot.as_matrix()[:, 0][0] < 0: - T_O_G.rotation = rot * Rotation.from_euler("z", np.pi) - - # Move to an initial pose offset. - target = T_O_G * Transform.translation([0, 0, -0.05]) * self.T_G_EE - self._send_target_pose(target) - rospy.sleep(3.0) - - # Approach grasp pose. - self._send_target_pose(T_O_G * self.T_G_EE) - rospy.sleep(1.0) - - # Close the fingers. - self.gripper.move(0.0) - - # Lift the object. - target = Transform.translation([0, 0, 0.2]) * T_O_G * self.T_G_EE - self._send_target_pose(target) - rospy.sleep(2.0) - - def _setup_robot_connection(self): + def load_parameters(self): self.base_frame = rospy.get_param("~base_frame_id") - self.ee_frame = rospy.get_param("~ee_frame_id") - self.T_G_EE = Transform.from_list(rospy.get_param("~ee_grasp_offset")).inv() - self.target_pose_pub = rospy.Publisher("/command", PoseStamped, queue_size=10) - self.gripper = PandaGripperClient() + self.task_frame = rospy.get_param("~frame_id") + self.cam_frame = rospy.get_param("~camera/frame_id") + self.info_topic = rospy.get_param("~camera/info_topic") + self.depth_topic = rospy.get_param("~camera/depth_topic") - def _setup_camera_connection(self): - self._cam_frame_id = rospy.get_param("~camera/frame_id") - info_topic = rospy.get_param("~camera/info_topic") - msg = rospy.wait_for_message(info_topic, CameraInfo, rospy.Duration(2.0)) + def lookup_transforms(self): + tf._init_listener() + rospy.sleep(1.0) # wait to receive transforms + self.T_B_task = tf.lookup(self.base_frame, self.task_frame) + + def connect_to_camera(self): + msg = rospy.wait_for_message(self.info_topic, CameraInfo, rospy.Duration(2.0)) self.intrinsic = from_camera_info_msg(msg) - depth_topic = rospy.get_param("~camera/depth_topic") - rospy.Subscriber(depth_topic, Image, self._sensor_cb, queue_size=1) + rospy.Subscriber(self.depth_topic, Image, self.sensor_cb, queue_size=1) - def _sensor_cb(self, msg): - self.last_depth_img = self.cv_bridge.imgmsg_to_cv2(msg).astype(np.float32) - self.last_extrinsic = tf.lookup( - self._cam_frame_id, - self.frame, + def sensor_cb(self, msg): + self.depth_img = self.cv_bridge.imgmsg_to_cv2(msg).astype(np.float32) + self.extrinsic = tf.lookup( + self.cam_frame, + self.task_frame, msg.header.stamp, - rospy.Duration(0.1), + rospy.Duration(0.2), ) - def _setup_rviz_connection(self): + def connect_to_rviz(self): self.path_pub = rospy.Publisher("path", MarkerArray, queue_size=1, latch=True) - def _lookup_transforms(self): - self.T_B_O = tf.lookup(self.base_frame, self.frame) + def activate(self): + self.viewpoints = [] + self.done = False + self.best_grasp = None # grasp pose defined w.r.t. the robot's base frame - def _reset_env(self): - req = std_srvs.srv.TriggerRequest() - self.reset_client(req) - - def _clear_rviz(self): - vgn.vis.clear() - self.path_pub.publish(DELETE_MARKER_ARRAY_MSG) - - def _init_policy(self): + def update(self): raise NotImplementedError - def _update(self): - raise NotImplementedError - - def _draw_camera_path(self, frustum=False): - identity = Transform.identity() - color = np.r_[31, 119, 180] / 255.0 - - # Spheres for each viewpoint - scale = 0.01 * np.ones(3) - spheres = create_marker(Marker.SPHERE_LIST, self.frame, identity, scale, color) - spheres.id = 0 - spheres.points = [to_point_msg(p.translation) for p in self.viewpoints] - - # Line strip connecting viewpoints - scale = [0.005, 0.0, 0.0] - lines = create_marker(Marker.LINE_STRIP, self.frame, identity, scale, color) - lines.id = 1 - lines.points = [to_point_msg(p.translation) for p in self.viewpoints] - - markers = [spheres, lines] - - # Frustums - if frustum: - for i, pose in enumerate(self.viewpoints): - msg = create_cam_marker(self.intrinsic, pose, self.frame) - msg.id = i + 2 - markers.append(msg) - - self.path_pub.publish(MarkerArray(markers)) - - def _draw_scene_cloud(self): - cloud = self.tsdf.get_scene_cloud() - vgn.vis.draw_points(np.asarray(cloud.points)) - - def _integrate_latest_image(self): - self.viewpoints.append(self.last_extrinsic.inv()) + def integrate_latest_image(self): + self.viewpoints.append(self.extrinsic.inv()) self.tsdf.integrate( - self.last_depth_img, + self.depth_img, self.intrinsic, - self.last_extrinsic, + self.extrinsic, ) - def _predict_best_grasp(self): + def predict_best_grasp(self): tsdf_grid = self.tsdf.get_grid() out = self.vgn.predict(tsdf_grid) score_fn = lambda g: g.pose.translation[2] grasps = compute_grasps(self.tsdf.voxel_size, out, score_fn) vgn.vis.draw_grasps(grasps, 0.05) - return grasps[0] if len(grasps) > 0 else None + return self.T_B_task * grasps[0].pose if len(grasps) > 0 else None - def _send_target_pose(self, target): - """Target is expected to be given w.r.t. the task frame.""" - msg = PoseStamped() - msg.header.frame_id = self.base_frame - msg.pose = to_pose_msg(self.T_B_O * target) - self.target_pose_pub.publish(msg) + def draw_scene_cloud(self): + cloud = self.tsdf.get_scene_cloud() + vgn.vis.draw_points(np.asarray(cloud.points)) + + def draw_camera_path(self): + identity = Transform.identity() + color = np.r_[31, 119, 180] / 255.0 + + # Spheres for each viewpoint + scale = 0.01 * np.ones(3) + spheres = create_marker( + Marker.SPHERE_LIST, self.task_frame, identity, scale, color + ) + spheres.id = 0 + spheres.points = [to_point_msg(p.translation) for p in self.viewpoints] + + # Line strip connecting viewpoints + scale = [0.005, 0.0, 0.0] + lines = create_marker( + Marker.LINE_STRIP, self.task_frame, identity, scale, color + ) + lines.id = 1 + lines.points = [to_point_msg(p.translation) for p in self.viewpoints] + + self.path_pub.publish(MarkerArray([spheres, lines])) -class SingleViewBaseline(BaseController): +class SingleViewBaseline(BasePolicy): """ - Integrate a single image from the initial viewpoint. + Process a single image from the initial viewpoint. """ def __init__(self): super().__init__() self.rate = 1 - def _init_policy(self): - pass - - def _update(self): - self._integrate_latest_image() - self._draw_scene_cloud() - self._draw_camera_path(frustum=True) - self.best_grasp = self._predict_best_grasp() + def update(self): + self.integrate_latest_image() + self.draw_scene_cloud() + self.draw_camera_path() + self.best_grasp = self.predict_best_grasp() self.done = True - - -class FixedTrajectoryBaseline(BaseController): - """Follow a pre-defined circular trajectory.""" - - def __init__(self): - super().__init__() - self.rate = 10 - self.duration = 4.0 - self.radius = 0.1 - self.m = scipy.interpolate.interp1d([0, self.duration], [np.pi, 3.0 * np.pi]) - - def _init_policy(self): - self.tic = rospy.Time.now() - x0 = tf.lookup(self.frame, self.ee_frame) - self.center = np.r_[x0.translation[0] + self.radius, x0.translation[1:]] - self.target = x0 - - def _update(self): - elapsed_time = (rospy.Time.now() - self.tic).to_sec() - if elapsed_time > self.duration: - self.best_grasp = self._predict_best_grasp() - self.done = True - else: - # Update state - self._integrate_latest_image() - - # Compute next viewpoint - t = self.m(elapsed_time) - self.target.translation = ( - self.center - + np.r_[self.radius * np.cos(t), self.radius * np.sin(t), 0.0] - ) - self._send_target_pose(self.target) - - # Draw - self._draw_scene_cloud() - self._draw_camera_path() - - -class MultiViewPicking(BaseController): - pass diff --git a/run.py b/run.py deleted file mode 100644 index c3787df..0000000 --- a/run.py +++ /dev/null @@ -1,32 +0,0 @@ -import argparse -import rospy - -from policies import get_controller - - -def create_parser(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--policy", - type=str, - choices=[ - "single-view", - "fixed-trajectory", - "mvp", - ], - ) - return parser - - -def main(): - rospy.init_node("active_grasp") - - parser = create_parser() - args = parser.parse_args() - - controller = get_controller(args.policy) - controller.run() - - -if __name__ == "__main__": - main() diff --git a/sim_grasp.py b/sim_grasp.py new file mode 100644 index 0000000..89b07ef --- /dev/null +++ b/sim_grasp.py @@ -0,0 +1,49 @@ +import argparse +import rospy +import std_srvs.srv as std_srvs + + +from controller import GraspController +from policies import get_policy + + +class SimGraspController(GraspController): + def __init__(self, policy): + super().__init__(policy) + self.reset_sim = rospy.ServiceProxy("/reset", std_srvs.Trigger) + + def reset(self): + req = std_srvs.TriggerRequest() + self.reset_sim(req) + rospy.sleep(1.0) # wait for states to be updated + + +def create_parser(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--policy", + type=str, + choices=[ + "single-view", + "top", + "alignment", + "random", + "fixed-trajectory", + ], + ) + return parser + + +def main(): + rospy.init_node("active_grasp") + parser = create_parser() + args = parser.parse_args() + policy = get_policy(args.policy) + controller = SimGraspController(policy) + + while True: + controller.run() + + +if __name__ == "__main__": + main() diff --git a/simulation.py b/simulation.py index 3ecf89e..6e3c831 100644 --- a/simulation.py +++ b/simulation.py @@ -3,43 +3,53 @@ import pybullet as p import rospkg from robot_utils.bullet import * +from robot_utils.controllers import CartesianPoseController from robot_utils.spatial import Rotation, Transform -class Simulation(BtManipulationSim): +class Simulation(BtSim): def __init__(self, gui=True): super().__init__(gui=gui, sleep=False) p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0) p.resetDebugVisualizerCamera(1.4, 50, -35, [0.0, 0.0, 0.6]) + self.object_uids = [] + self.find_object_urdfs() - self.add_table() - self.add_robot() + self.load_table() + self.load_robot() + self.load_controller() + + self.reset() def find_object_urdfs(self): rospack = rospkg.RosPack() - root = Path(rospack.get_path("vgn")) / "data/urdfs/packed/test" + root = Path(rospack.get_path("vgn")) / "assets/urdfs/packed/test" self.urdfs = [str(f) for f in root.iterdir() if f.suffix == ".urdf"] - def add_table(self): + def load_table(self): p.loadURDF("plane.urdf") ori = Rotation.from_rotvec(np.array([0, 0, np.pi / 2])).as_quat() p.loadURDF("table/table.urdf", baseOrientation=ori, useFixedBase=True) self.length = 0.3 self.origin = [-0.3, -0.5 * self.length, 0.5] - def add_robot(self): - self.T_W_B = Transform(Rotation.identity(), np.r_[-0.6, 0.0, 0.5]) + def load_robot(self): + self.T_W_B = Transform(Rotation.identity(), np.r_[-0.6, 0.0, 0.4]) self.arm = BtPandaArm(self.T_W_B) self.gripper = BtPandaGripper(self.arm) + self.model = Model(self.arm.urdf_path, self.arm.base_frame, self.arm.ee_frame) self.camera = BtCamera(320, 240, 1.047, 0.1, 1.0, self.arm.uid, 11) + def load_controller(self): + self.controller = CartesianPoseController(self.model, self.arm.ee_frame, None) + def reset(self): self.remove_all_objects() self.set_initial_arm_configuration() urdfs = np.random.choice(self.urdfs, 4) origin = np.r_[self.origin[:2], 0.625] - self.add_random_arrangement(urdfs, origin, self.length, 0.8) + self.random_object_arrangement(urdfs, origin, self.length, 0.8) def set_initial_arm_configuration(self): q = self.arm.configurations["ready"] @@ -47,3 +57,69 @@ class Simulation(BtManipulationSim): q[5] = np.deg2rad(np.random.uniform(90, 105)) for i, q_i in enumerate(q): p.resetJointState(self.arm.uid, i, q_i, 0) + p.resetJointState(self.arm.uid, 9, 0.04, 0) + p.resetJointState(self.arm.uid, 10, 0.04, 0) + x0 = self.model.pose(self.arm.ee_frame, q) + self.controller.x_d = x0 + + def load_object(self, urdf, ori, pos, scale=1.0): + uid = p.loadURDF(str(urdf), pos, ori.as_quat(), globalScaling=scale) + self.object_uids.append(uid) + return uid + + def remove_object(self, uid): + p.removeBody(uid) + self.object_uids.remove(uid) + + def remove_all_objects(self): + for uid in list(self.object_uids): + self.remove_object(uid) + + def random_object_arrangement(self, urdfs, origin, length, scale=1.0, attempts=10): + for urdf in urdfs: + # Load the object + uid = self.load_object(urdf, Rotation.identity(), [0.0, 0.0, 0.0], scale) + lower, upper = p.getAABB(uid) + z_offset = 0.5 * (upper[2] - lower[2]) + 0.002 + state_id = p.saveState() + for _ in range(attempts): + # Try to place the object without collision + ori = Rotation.from_rotvec([0.0, 0.0, np.random.uniform(0, 2 * np.pi)]) + offset = np.r_[np.random.uniform(0.2, 0.8, 2) * length, z_offset] + p.resetBasePositionAndOrientation(uid, origin + offset, ori.as_quat()) + self.step() + if not p.getContactPoints(uid): + break + else: + p.restoreState(stateId=state_id) + else: + # No placement found, remove the object + self.remove_object(uid) + + +class CartesianPoseController: + def __init__(self, model, frame, x0): + self._model = model + self._frame = frame + + self.kp = np.ones(6) * 4.0 + self.max_linear_vel = 0.2 + self.max_angular_vel = 1.57 + + self.x_d = x0 + + def update(self, q): + x = self._model.pose(self._frame, q) + error = np.zeros(6) + error[:3] = self.x_d.translation - x.translation + error[3:] = (self.x_d.rotation * x.rotation.inv()).as_rotvec() + dx = self._limit_rate(self.kp * error) + J_pinv = np.linalg.pinv(self._model.jacobian(self._frame, q)) + cmd = np.dot(J_pinv, dx) + return cmd + + def _limit_rate(self, dx): + linear, angular = dx[:3], dx[3:] + linear = np.clip(linear, -self.max_linear_vel, self.max_linear_vel) + angular = np.clip(angular, -self.max_angular_vel, self.max_angular_vel) + return np.r_[linear, angular] diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..7ef2723 --- /dev/null +++ b/utils.py @@ -0,0 +1,13 @@ +from geometry_msgs.msg import PoseStamped +import rospy + +from robot_utils.ros.conversions import * + + +class CartesianPoseControllerClient: + def __init__(self, topic="/command"): + self.target_pub = rospy.Publisher(topic, PoseStamped, queue_size=10) + + def send_target(self, pose): + msg = to_pose_stamped_msg(pose, "panda_link0") + self.target_pub.publish(msg)