calibrate.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # This program is free software; you can redistribute it and/or
00004 # modify it under the terms of the GNU General Public License
00005 # as published by the Free Software Foundation; either version 2
00006 # of the License, or (at your option) any later version.
00007 #
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 #
00013 # You should have received a copy of the GNU General Public License
00014 # along with this program; if not, write to the Free Software
00015 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00016 #
00017 # Katana.cpp
00018 #
00019 #  Created on: 08.03.2013
00020 #      Author: Michael Goerner <mgoerner@uos.de>
00021 
00022 import os
00023 
00024 import roslib; roslib.load_manifest('katana_kinect_calibration')
00025 import rospy
00026 
00027 import tf
00028 from tf.transformations import quaternion_slerp, euler_from_quaternion
00029 
00030 from geometry_msgs.msg import Point, Quaternion, Pose, PoseStamped
00031 from sensor_msgs.msg import JointState
00032 from katana_msgs.msg import JointMovementAction, JointMovementGoal
00033 from ar_pose.msg import ARMarkers
00034 
00035 from actionlib import SimpleActionClient
00036 
00037 from math import sqrt, acos
00038 
00039 
00040 class CalibrateException(Exception):
00041         pass
00042 class NoJointStatesFoundException(CalibrateException):
00043         pass
00044 class NoTransformCachedException(CalibrateException):
00045         pass
00046 class LastHopReachedException(CalibrateException):
00047         pass
00048 
00049 # order of katana joints in the whole calibrate module
00050 JOINTS=['katana_motor1_pan_joint', 'katana_motor2_lift_joint', 'katana_motor3_lift_joint', 'katana_motor4_lift_joint', 'katana_motor5_wrist_roll_joint', 'katana_r_finger_joint', 'katana_l_finger_joint']
00051 
00052 
00053 def averagePoses(pose1, pose2, t):
00054                 """Average two poses with weighting t to 1
00055 
00056                 This computes the average of the kartesian positions using
00057                 linear interpolation and of the rotation using SLERP"""
00058 
00059                 factor= float(t)/(t+1)
00060                 translation= tuple(map(lambda x,y: factor*x + (1-factor)*y, pose1[0], pose2[0]))
00061                 rotation= quaternion_slerp(pose1[1], pose2[1], 1.0/(t+1))
00062                 return (translation, rotation)
00063 
00064 
00065 class Dance:
00066         """Move Katana arm around in a given fashion"""
00067 
00068         def __init__(self):
00069                 self.poses= [
00070 [-0.07, 0.21, -0.14, -1.03, -2.96, 0.27, 0.27],
00071 [-1.35, 0.15, -0.25, -0.89, -2.96, 0.27, 0.27],
00072 [-1.3, 0.96, -0.09, 0.33, -2.96, 0.27, 0.27],
00073 [-0.07, 0.92, 0.1, 0.16, -2.96, 0.27, 0.27],
00074 [1.13, 0.93, 0.02, 0.16, -2.96, 0.27, 0.27],
00075 [1.64, 0.05, 0.26, -0.19, -2.96, 0.27, 0.27],
00076 # ... what a nice dance ;)
00077                 ]
00078                 self.i= -1
00079                 self.hopping= False
00080                 self.client= SimpleActionClient('katana_arm_controller/joint_movement_action', JointMovementAction)
00081                 self.client.wait_for_server()
00082 
00083         def hop(self, pose= None, noreset= False):
00084                 """Hop to next stance of the dance
00085 
00086                 pose - JointState, optional. If given, this pose will be taken instead of the next one
00087                 noreset - Boolean, optional. If True, don't reset the PoseBuffer during movement
00088                 """
00089                 if pose == None:
00090                         if self.i+1 >= len(self.poses):
00091                                 self.i= -1
00092                                 raise LastHopReachedException()
00093 
00094                         self.i= (self.i+1) % len(self.poses)
00095                         goal= JointMovementGoal( jointGoal= JointState(name=JOINTS, position=self.poses[self.i]) )
00096                 else:
00097                         goal= JointMovementGoal(jointGoal= pose)
00098 
00099                 self.hopping= True
00100                 if not noreset:
00101                         transform.reset()
00102                 self.client.send_goal(goal)
00103                 self.client.wait_for_result()
00104                 self.hopping= False
00105                 if not noreset:
00106                         transform.reset()
00107                 return self.client.get_result()
00108 
00109         def setup(self):
00110                 """Get as safe as possible to the first stance"""
00111 
00112                 try:
00113                         jointmsg= rospy.wait_for_message('/katana_joint_states', JointState, 2.0)
00114                         self.old_pose= []
00115                         for n in JOINTS:
00116                                 self.old_pose.append(jointmsg.position[jointmsg.name.index(n)])
00117                 except Exception, e:
00118                         raise NoJointStatesFoundException(e)
00119 
00120                 self.hop(JointState(
00121                         name= ['katana_motor3_lift_joint', 'katana_motor4_lift_joint', 'katana_motor5_wrist_roll_joint', 'katana_r_finger_joint', 'katana_l_finger_joint'],
00122                         position= [-2.18, -2.02, -2.96, 0.28, 0.28]
00123                 ), noreset= True)
00124                 self.hop( JointState(name= ['katana_motor2_lift_joint'], position= [2.16]), noreset= True )
00125                 self.hop( JointState(name= ['katana_motor1_pan_joint'], position= [0.00]), noreset= True )
00126                 self.hop(noreset= True)
00127 
00128         def restoreOldPose(self):
00129                 """This restores the pose of the arm before setup() was called"""
00130 
00131                 self.hop( JointState(name= JOINTS, position= [0.00, 2.16, -2.18, -2.02, -2.96, 0.28, 0.28]), noreset= True )
00132                 self.hop( JointState(name= [JOINTS[0]], position= [self.old_pose[0]]), noreset= True)
00133                 self.hop( JointState(name= [JOINTS[1]], position= [self.old_pose[1]]), noreset= True)
00134                 self.hop( JointState(name= JOINTS[2:], position= self.old_pose[2:]), noreset= True )
00135 
00136 
00137 class TransformBuffer:
00138         """Accumulate poses of the camera as average"""
00139 
00140         def __init__(self):
00141                 self.reset()
00142                 self.listener= tf.TransformListener()
00143                 rospy.Subscriber('/ar_pose_markers', ARMarkers, self.update_cb)
00144 
00145         def addTransform(self, transform):
00146                 """add new transform to current average"""
00147 
00148                 (self.translation, self.rotation)= averagePoses( (self.translation, self.rotation), transform, self.samples)
00149                 self.samples+= 1
00150 
00151         def getTransform(self):
00152                 if self.samples == 0:
00153                         raise NoTransformCachedException()
00154                 return (self.translation, self.rotation)
00155 
00156         def reset(self):
00157                 self.translation= (0,0,0)
00158                 self.rotation= (1,0,0,0)
00159                 self.samples= 0
00160                 self.last_reset= rospy.get_time()
00161 
00162         def update_cb(self, msg):
00163                 if len(msg.markers) > 0:
00164                         if dance.hopping:
00165                                 self.reset()
00166                         try:
00167                                 self.listener.waitForTransform('/katana_pattern_seen', '/kinect_link', msg.header.stamp, rospy.Duration(2.0))
00168                                 transform= self.listener.lookupTransform('/katana_pattern_seen', '/kinect_link', msg.header.stamp)
00169 
00170                                 # it's not beautiful, but transformPose expects a PoseStamped object
00171                                 pose= PoseStamped( msg.header, Pose(Point(*(transform[0])), Quaternion(*(transform[1]))) )
00172                                 pose.header.frame_id= '/katana_pattern'
00173                                 base_transform= self.listener.transformPose('/base_link', pose)
00174 
00175                                 p= base_transform.pose.position
00176                                 r= base_transform.pose.orientation
00177                                 self.addTransform(((p.x,p.y,p.z),(r.x, r.y, r.z, r.w)))
00178                         except tf.Exception, e:
00179                                 rospy.loginfo('no transform /katana_pattern_seen -> /kinect_link available')
00180 
00181 
00182 if __name__ == '__main__':
00183         rospy.init_node('kinect_transform')
00184 
00185         # fetch all parameters
00186         config_file=      rospy.get_param('~config_file', os.getenv('HOME') + '/.ros/kinect_pose.urdf.xacro')
00187         timeout=          max(1, rospy.get_param('~timeout', 20))
00188         samples_required= max(1, rospy.get_param('~samples_required', 300))
00189         runs=             max(1, rospy.get_param('~runs', 1))
00190         write_config=     rospy.get_param('~write_config', True)
00191         publish_tf=       rospy.get_param('~publish_tf', False)
00192 
00193         dance= Dance()
00194         transform= TransformBuffer()
00195 
00196         broadcaster= tf.TransformBroadcaster()
00197 
00198         r= rospy.Rate(10)
00199         dance.setup()
00200         poses= []
00201         current_run= 0
00202         while not rospy.is_shutdown():
00203                 if publish_tf:
00204                         try:
00205                                 t= transform.getTransform()
00206                                 broadcaster.sendTransform(t[0], t[1], rospy.Time.now(), '/kinect_link', '/base_link')
00207                         except NoTransformCachedException:
00208                                 pass
00209 
00210                 try:
00211                         if transform.samples >= samples_required:
00212                                 rospy.loginfo('finished pose with %d samples.' % samples_required)
00213                                 poses.append(transform.getTransform())
00214                                 dance.hop()
00215                         elif rospy.get_time() - transform.last_reset > timeout:
00216                                 if transform.samples > 0:
00217                                         rospy.logwarn('found only %d samples, but %d are required. Ignoring pose!' % (transform.samples, samples_required) )
00218                                 else:
00219                                         rospy.logwarn('could not detect marker! Ignoring pose!')
00220                                 dance.hop()
00221                 except LastHopReachedException:
00222                         current_run+= 1
00223                         if current_run >= runs:
00224                                 break
00225                         else:
00226                                 # dance resets its index before this exception
00227                                 # so there's nothing we want to catch here
00228                                 dance.hop()
00229                 r.sleep()
00230 
00231         dance.restoreOldPose()
00232 
00233         if len(poses) == 0:
00234                 rospy.logfatal('Could not use any pose at all! There were not enough (any?) recognized samples before timeout.')
00235                 exit(1)
00236 
00237         # averaging
00238         mean= ((0,0,0), (1,0,0,0))
00239         avgdev= [0,0,0,0]
00240         for i in xrange(len(poses)):
00241                 mean= averagePoses(mean, poses[i], i)
00242         for p in poses:
00243                 # cartesian diffs
00244                 avgdev[0]+= abs(p[0][0]-mean[0][0])/len(poses)
00245                 avgdev[1]+= abs(p[0][1]-mean[0][1])/len(poses)
00246                 avgdev[2]+= abs(p[0][2]-mean[0][2])/len(poses)
00247                 # angle diffs: acos( dotprod( mean_angle, pose_angle ) )
00248                 avgdev[3]+= acos(sum( map(lambda x,y: x*y, mean[1], p[1]) )) / len(poses)
00249 
00250                 rospy.logdebug('Dist: %f (%f, %f, %f) / Angle: %f' % (sqrt(sum( map(lambda x,y: (x-y)*(x-y), p[0], mean[0]) )), p[0][0]-mean[0][0], p[0][1]-mean[0][1], p[0][2]-mean[0][2], acos(sum( map(lambda x,y: x*y, mean[1], p[1])))) )
00251 
00252         rospy.loginfo('Averaged over %d poses\nMean: %s\nAvg. Dev.: (x: %.4f, y: %.4f, z: %.4f), rotation: %.4f' % (len(poses), str(mean), avgdev[0], avgdev[1], avgdev[2], avgdev[3]))
00253 
00254         xyz= mean[0]
00255         rpy= euler_from_quaternion(mean[1])
00256         if write_config:
00257                 config= open( config_file, 'w' )
00258                 config.write('<?xml version="1.0"?>\n<robot xmlns:xacro="http://ros.org/wiki/xacro">\n')
00259                 config.write('<property name="kinect_xyz" value="%.3f %.3f %.3f"/>\n' % (xyz[0], xyz[1], xyz[2]))
00260                 config.write('<property name="kinect_rpy" value="%.3f %.3f %.3f"/>\n' % (rpy[0], rpy[1], rpy[2]))
00261                 config.write('</robot>')
00262                 config.close()


katana_kinect_calibration
Author(s): Michael Goerner
autogenerated on Mon Oct 6 2014 10:45:10