Note: This tutorial assumes you have read through the DARRT page and followed the directions for downloading.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Simple Pick and Place

Description: Use DARRT to plan a simple pick and place and the sushi code to execute that pick and place.

Tutorial Level: INTERMEDIATE

Next Tutorial: Using Other Primitives

Pick and Place Goal

The DARRTGoal message is long and complicated but most of the parameters are optional. For a simple pick and place, only two fields matter: the pickup_goal and the place_goal. The pickup goal tells the robot which object to pick up and the place goal tells it where to put it back down. Make sure all of the mandatory parts of the pick and place goals are filled in. For a general overview of the object_manipulation_msgs/PickupGoal and object_manipulation_msgs/PlaceGoal messages see the object_manipulation_msgs stack.

Recall that the default goal plans for PICK, PLACE, and WARP which is all we shall discuss in this tutorial. For an example of sending more complicated goals, see the next tutorial.

Stationary Pick and Place

We will first go through a simple example that likely does not move the robot base.

The Code

The following code code uses the sushi code to find an object on the table in front of the robot, pick it up, and move it over 20cm. This code can also be found in darrt_actions/scripts/stationary_pickplace_demo.py.

   1 #!/usr/bin/env/python
   2 import roslib; roslib.load_manifest('darrt_actions')
   3 
   4 from darrt_msgs.msg import DARRTAction, DARRTGoal
   5 from darrt_actions.darrt_trajectories import get_trajectory_markers, Executor
   6 
   7 from pr2_tasks.tableware_detection import TablewareDetection
   8 from pr2_tasks.pickplace_definitions import PlaceGoal
   9 from pr2_python.planning_scene_interface import get_planning_scene_interface
  10 
  11 from actionlib import SimpleActionClient
  12 from visualization_msgs.msg import MarkerArray
  13 import rospy
  14 import copy
  15 
  16 def main():
  17     #Planning scene interface
  18     psi = get_planning_scene_interface()
  19 
  20     #Find an object to pick up
  21     rospy.loginfo('Doing detection')
  22     detector = TablewareDetection()
  23     det = detector.detect_objects(add_objects_as_mesh=False)
  24     if not det.pickup_goals:
  25         rospy.loginfo('Nothing to pick up!')
  26         return
  27 
  28     #Set planning scene to reflect the changes we have just made
  29     psi.reset()
  30 
  31     #DARRT action client
  32     client = SimpleActionClient('/darrt_planning/darrt_action', DARRTAction)
  33     rospy.loginfo('Waiting for DARRT action')
  34     client.wait_for_server()
  35     rospy.loginfo('Found DARRT action')
  36 
  37     #DARRTGoal
  38     goal = DARRTGoal()
  39     
  40     #pickup goal (from the tabletop detection)
  41     goal.pickup_goal = det.pickup_goals[0]
  42     goal.pickup_goal.arm_name = 'right_arm'
  43     
  44     #place goal (move left 20 cm)
  45     place_pose_stamped = copy.deepcopy(goal.pickup_goal.object_pose_stamped)
  46     place_pose_stamped.pose.position.y += 0.2
  47     goal.place_goal = PlaceGoal(goal.pickup_goal.arm_name, 
  48                                 [place_pose_stamped],
  49                                 collision_support_surface_name = 
  50                                 det.table_name,
  51                                 collision_object_name = 
  52                                 goal.pickup_goal.collision_object_name)
  53 
  54     #Send the goal to the action
  55     rospy.loginfo('Sending goal')
  56     client.send_goal_and_wait(goal)
  57     rospy.loginfo('Returned')
  58 
  59     
  60 
  61     #Check the result
  62     result = client.get_result()
  63     if result.error_code != result.SUCCESS:
  64         rospy.logerr('Planning failed.  Error code: '+str(result.error_code))
  65         return
  66     rospy.loginfo('Planning succeeded!')
  67 
  68     #at this point the planning is done.  
  69     #now we execute and visualize the plan
  70 
  71     #visualize trajectory
  72     pub = rospy.Publisher('darrt_trajectory', MarkerArray)
  73     marray = get_trajectory_markers(result.primitive_trajectories)
  74     for i in range(10):
  75         pub.publish(marray)
  76         rospy.sleep(0.05)
  77 
  78 
  79     #Executor is a Python class for executing DARRT plans
  80     executor = Executor()
  81 
  82     #execute trajectory
  83     rospy.loginfo('Press enter to execute')
  84     raw_input()
  85     executor.execute_trajectories(result)
  86 
  87 rospy.init_node('pickplace_test_node')
  88 main()

Examining the Code

Now, let's break the code down.

   5 from darrt_actions.darrt_trajectories import get_trajectory_markers, Executor

The darrt_trajectories.py file in darrt_actions/src/darrt_actions contains the function for visualizing trajectories and the class for executing them.

  20     #Find an object to pick up
  21     rospy.loginfo('Doing detection')
  22     detector = TablewareDetection()
  23     det = detector.detect_objects(add_objects_as_mesh=False)

This block of code calls tableware detection from the sushi block of code. See the sushi tutorial for more information. Note that we do not add objects as mesh because the meshes are too large for fast collision checking.

  41     goal.pickup_goal = det.pickup_goals[0]
  42     goal.pickup_goal.arm_name = 'right_arm'

We pick up the first thing we see. We also tell DARRT to use the right arm. The pickup goal returned by tabletop detection is already in the form DARRT expects.

  44     #place goal (move left 20 cm)
  45     place_pose_stamped = copy.deepcopy(goal.pickup_goal.object_pose_stamped)
  46     place_pose_stamped.pose.position.y += 0.2
  47     goal.place_goal = PlaceGoal(goal.pickup_goal.arm_name, 
  48                                 [place_pose_stamped],
  49                                 collision_support_surface_name = 
  50                                 det.table_name,
  51                                 collision_object_name = 
  52                                 goal.pickup_goal.collision_object_name)

We move the object left 20cm relative to the robot. The object_pose_stamped in the pickup goal is in a robot frame (not a world frame).

  71     #visualize trajectory
  72     pub = rospy.Publisher('darrt_trajectory', MarkerArray)
  73     marray = get_trajectory_markers(result.primitive_trajectories)

The trajectory is shown using the get_trajectory_markers function from darrt_trajectories.py. The color indicates the time along the trajectory (red->purple), not the primitive.

  79     #Executor is a Python class for executing DARRT plans
  80     executor = Executor()
  81 
  82     #execute trajectory
  83     rospy.loginfo('Press enter to execute')
  84     raw_input()
  85     executor.execute_trajectories(result)

We use the Executor class from darrt_trajectories.py to execute trajectories.

Running The Code

Add a visualization_msgs/MarkerArray to RViz with the topic /darrt_trajectory before you run the code. If the planning succeeds you should see a rainbow trajectory. Press enter when prompted to execute it.

Because the robot can remain stationary, it is likely it will since the number of base poses around a table that are not in collision are small. However, it is possible this plan will return a warp (for a true stationary pick and place you would only want to pass the PICK and PLACE primitives without WARP) and the base may move. Base movement is done using the SBPL 3D Nav planner from the sushi code.

Using Warp

The above code may not move the robot's base because the place point is specifically chosen near the pick point. For an example of moving the robot's base, choose a place point far away from the object you are picking.

Example Code

The following example works in the simulation (Willow) set of maps:

   1 #!/usr/bin/env/python
   2 import roslib; roslib.load_manifest('darrt_actions')
   3 
   4 from darrt_msgs.msg import DARRTAction, DARRTGoal
   5 from darrt_actions.darrt_trajectories import get_trajectory_markers, Executor
   6 
   7 from pr2_python.world_interface import WorldInterface
   8 from pr2_tasks.tableware_detection import TablewareDetection
   9 from pr2_tasks.pickplace_definitions import PlaceGoal
  10 from pr2_python.planning_scene_interface import get_planning_scene_interface
  11 
  12 from actionlib import SimpleActionClient
  13 from visualization_msgs.msg import MarkerArray
  14 from geometry_msgs.msg import PoseStamped
  15 import rospy
  16 
  17 def main():
  18     #Planning scene interface
  19     psi = get_planning_scene_interface()
  20 
  21     #Find an object to pick up
  22     rospy.loginfo('Doing detection')
  23     detector = TablewareDetection()
  24     det = detector.detect_objects(add_objects_as_mesh=False)
  25     if not det.pickup_goals:
  26         rospy.loginfo('Nothing to pick up!')
  27         return
  28 
  29     #Set the planning scene to reflect what we have just done
  30     psi.reset()
  31 
  32     #DARRT action client
  33     client = SimpleActionClient('/darrt_planning/darrt_action', DARRTAction)
  34     rospy.loginfo('Waiting for DARRT action')
  35     client.wait_for_server()
  36     rospy.loginfo('Found DARRT action')
  37 
  38     #DARRTGoal
  39     goal = DARRTGoal()
  40     
  41     #pickup goal (from the tabletop detection)
  42     goal.pickup_goal = det.pickup_goals[0]
  43     goal.pickup_goal.arm_name = 'right_arm'
  44     
  45     #world interface to tell us whether we're using the 
  46     #map or odom_combined frame
  47     wi = WorldInterface() 
  48 
  49     #place goal (move far away)
  50     place_pose_stamped = PoseStamped()
  51     place_pose_stamped.header.frame_id = wi.world_frame
  52     place_pose_stamped.pose.position.x = 0
  53     place_pose_stamped.pose.position.y = 0
  54     place_pose_stamped.pose.position.z  = 0.85
  55     place_pose_stamped.pose.orientation.w = 1.0
  56     goal.place_goal = PlaceGoal(goal.pickup_goal.arm_name, 
  57                                 [place_pose_stamped],
  58                                 collision_support_surface_name = 
  59                                 det.table_name,
  60                                 collision_object_name = 
  61                                 goal.pickup_goal.collision_object_name)
  62 
  63     #Send the goal to the action
  64     rospy.loginfo('Sending goal')
  65     client.send_goal_and_wait(goal)
  66     rospy.loginfo('Returned')
  67 
  68     #Check the result
  69     result = client.get_result()
  70     if result.error_code != result.SUCCESS:
  71         rospy.logerr('Planning failed.  Error code: '+str(result.error_code))
  72         return
  73     rospy.loginfo('Planning succeeded!')
  74 
  75     #at this point the planning is done.  
  76     #now we execute and visualize the plan
  77 
  78     #visualize trajectory
  79     pub = rospy.Publisher('darrt_trajectory', MarkerArray)
  80     marray = get_trajectory_markers(result.primitive_trajectories)
  81     for i in range(10):
  82         pub.publish(marray)
  83         rospy.sleep(0.05)
  84 
  85 
  86     #Executor is a Python class for executing DARRT plans
  87     executor = Executor()
  88 
  89     #execute trajectory
  90     rospy.loginfo('Press enter to execute')
  91     raw_input()
  92     executor.execute_trajectories(result)
  93 
  94 rospy.init_node('pickplace_test_node')
  95 main()

If you are not using the simulation maps, don't forget to change the place goal for your own environment:

  50     place_pose_stamped = PoseStamped()
  51     place_pose_stamped.header.frame_id = wi.world_frame
  52     place_pose_stamped.pose.position.x = 0
  53     place_pose_stamped.pose.position.y = 0
  54     place_pose_stamped.pose.position.z  = 0.85
  55     place_pose_stamped.pose.orientation.w = 1.0

World Frame

This example also illustrates the idea of the "world_frame". If the robot is localizing to a map, we want to treat the "/map" frame as the frame that is stationary with respect to stationary objects (like tables). If there is no map, we must use the "/odom_combined" frame. The world interface determines which frame is the world frame by checking the parent frame of the multi DOF joint in a call to /environment_server/get_robot_state. I find just instantiating a world interface and asking it for the world frame to be less work:

  45     #world interface to tell us whether we're using the 
  46     #map or odom_combined frame
  47     wi = WorldInterface() 
  48 
  49     #place goal (move far away)
  50     place_pose_stamped = PoseStamped()
  51     place_pose_stamped.header.frame_id = wi.world_frame

Running

First launch DARRT on the robot. For warp to work, you must have a map for 3D Nav to use:

roslaunch darrt darrt.launch use_map:=true

Before running this code add two visualization_msgs/MarkerArray topics in RViz: /darrt_trajectory and /visualization_msgs. The latter topic will show the plans generated by 3D Nav.

Run the code, pressing enter when prompted. After the pick, the robot will plan to move its arm to its side and then create and execute a 3D Nav plan to where it can place. This is the warp - the planning happens outside of the DARRT plan. Once it finishes the warp, it will execute the rigid transfer planned by DARRT.

Beyond Pick and Place

Firstly, this code will only work well when almost all grasps work for both the pick and the place. If you have constrained places, you should pass a non-zero object_sampling_fraction to the goal.

For an overview of using other primitives, see the next tutorial.

Wiki: darrt/Tutorials/Simple Pick and Place (last edited 2012-08-12 00:58:17 by JennyBarry)