00001
00002
00003 import cPickle as pickle
00004 import sys
00005 import copy
00006 import numpy as np
00007 from threading import Lock
00008
00009 import roslib
00010 roslib.load_manifest("hrl_ft")
00011 import rospy
00012 import rosparam
00013 import tf
00014 import tf.transformations as tf_trans
00015 from geometry_msgs.msg import WrenchStamped, Wrench, Vector3, Point, Quaternion, Pose, PoseArray
00016 from std_msgs.msg import Float64, Bool, ColorRGBA
00017 from visualization_msgs.msg import Marker
00018
00019 g = 9.80665
00020
00021 class WrenchListener(object):
00022 def __init__(self, wrench_topic):
00023 self.cur_wrench = WrenchStamped()
00024 rospy.Subscriber(wrench_topic, WrenchStamped, self.record_wrench)
00025
00026 def record_wrench(self, msg):
00027 self.cur_wrench = [msg.wrench.force.x,
00028 msg.wrench.force.y,
00029 msg.wrench.force.z,
00030 msg.wrench.torque.x,
00031 msg.wrench.torque.y,
00032 msg.wrench.torque.z]
00033
00034 def collect_data_pr2(n_4, n_5, n_6):
00035 roslib.load_manifest("hrl_pr2_arms")
00036 from hrl_pr2_arms.pr2_arm_joint_traj import PR2ArmJointTraj, create_ep_arm
00037 wrench_topic = rospy.get_param("~wrench_topic")
00038 gravity_frame = rospy.get_param("~gravity_frame")
00039 wrench_frame = rospy.get_param("~wrench_frame")
00040 jnt_arm = create_ep_arm('l', arm_type=PR2ArmJointTraj)
00041 wrench_list = WrenchListener(wrench_topic)
00042 tf_list = tf.TransformListener()
00043
00044 q_setup = [0.4, 0.0, 1.0, -1.2, 0, -0.1, -3.14]
00045 q_4_vals = np.linspace(0, 4.7, n_4)
00046 q_5_vals = np.linspace(-0.1, -1.8, n_5)
00047 q_6_vals = np.linspace(-3.14, -0.2, n_6)
00048
00049 jnt_arm.set_ep(q_setup, 8)
00050 rospy.sleep(8.)
00051
00052 data = []
00053 pose_array_pub = rospy.Publisher('wrench_train_poses', PoseArray, latch=True)
00054
00055 q_cur = q_setup
00056 for q_4 in q_4_vals:
00057 for q_5 in q_5_vals:
00058 for q_6 in q_6_vals:
00059 q_cur[4] = q_4
00060 q_cur[5] = q_5
00061 q_cur[6] = q_6
00062 jnt_arm.set_ep(q_cur, 2.)
00063 rospy.sleep(4.)
00064 (trans, rot) = tf_list.lookupTransform(gravity_frame, wrench_frame, rospy.Time(0))
00065 datum = (copy.copy(wrench_list.cur_wrench), rot)
00066 data.append(datum)
00067 print "Wrench: %s\r\nOrient: %s" %(datum[0], datum[1])
00068 if rospy.is_shutdown():
00069 return
00070 poses = []
00071 for datum in data:
00072 p = Pose(Point(0,0,0), Quaternion(*datum[1]))
00073 poses.append(copy.copy(p))
00074 pa = PoseArray()
00075 pa.header.frame_id = wrench_frame
00076 pa.header.stamp = rospy.Time.now()
00077 pa.poses = poses
00078 pose_array_pub.publish(pa)
00079 raw_input('holding to catch pose array. Press ENTER to continue.')
00080 return data
00081
00082 def collect_data_tool():
00083 wrench_topic = rospy.get_param("~wrench_topic")
00084 gravity_frame = rospy.get_param("~gravity_frame")
00085 wrench_frame = rospy.get_param("~wrench_frame")
00086 wrench_list = WrenchListener(wrench_topic)
00087 tf_list = tf.TransformListener()
00088 data = []
00089 while not rospy.is_shutdown():
00090 done_query = raw_input("Enter 'd' when done")
00091 if done_query == 'd':
00092 break
00093 (trans, rot) = tf_list.lookupTransform(gravity_frame, wrench_frame, rospy.Time(0))
00094 data.append((copy.copy(wrench_list.cur_wrench), rot))
00095 print "Wrench, orientation data", data
00096 return data
00097
00098 def process_data(data, is_backwards):
00099 wf_chain = []
00100 grav_chain = []
00101 if is_backwards:
00102
00103
00104 react_mult = -1.
00105 else:
00106 react_mult = 1.
00107 for w, quat in data:
00108 wf_chain.extend(w[:3])
00109 rot_mat = np.mat(tf_trans.quaternion_matrix(quat))[:3,:3]
00110 z_grav = react_mult * rot_mat.T * np.mat([0, 0, -1.]).T
00111 z_x, z_y, z_z = z_grav.T.A[0]
00112 grav_chain.append([g * z_x, 1, 0, 0])
00113 grav_chain.append([g * z_y, 0, 1, 0])
00114 grav_chain.append([g * z_z, 0, 0, 1])
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126 b_w = np.mat(wf_chain).T
00127 A_g = np.mat(grav_chain)
00128 x_p, res_p, _, _ = np.linalg.lstsq(A_g, b_w)
00129
00130
00131 mass = x_p[0,0]
00132
00133 wt_chain = []
00134 torque_chain = []
00135 for w, quat in data:
00136 wt_chain.extend(w[3:])
00137 rot_mat = np.mat(tf_trans.quaternion_matrix(quat))[:3,:3]
00138 z_grav = react_mult * rot_mat.T * np.mat([0, 0, -1.]).T
00139 force_grav = mass * g * z_grav
00140 f_x, f_y, f_z = force_grav.T.A[0]
00141 torque_chain.append([0, f_z, -f_y, 1, 0, 0])
00142 torque_chain.append([-f_z, 0, f_x, 0, 1, 0])
00143 torque_chain.append([f_y, -f_x, 0, 0, 0, 1])
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157 b_t = np.mat(wt_chain).T
00158 A_t = np.mat(torque_chain)
00159 x_t, res_t, _, _ = np.linalg.lstsq(A_t, b_t)
00160
00161
00162 print "Force residues:", res_p
00163 print "Torque residues:", res_t
00164
00165 return x_p.T.A[0].tolist() + x_t.T.A[0].tolist()
00166
00167 def save_params(p, filename):
00168 ft_params = { "mass" : p[0],
00169 "force_zero_x" : p[1],
00170 "force_zero_y" : p[2],
00171 "force_zero_z" : p[3],
00172 "com_pos_x" : p[4],
00173 "com_pos_y" : p[5],
00174 "com_pos_z" : p[6],
00175 "torque_zero_x" : p[7],
00176 "torque_zero_y" : p[8],
00177 "torque_zero_z" : p[9] }
00178 rosparam.upload_params(rospy.get_name(), ft_params)
00179 rospy.sleep(0.5)
00180 rosparam.dump_params(filename, rospy.get_name())
00181
00182 class WrenchZeroer(object):
00183 def __init__(self, start_zero=True, is_backwards=True):
00184
00185 wrench_topic = rospy.get_param("~wrench_topic")
00186 self.gravity_frame = rospy.get_param("~gravity_frame")
00187 self.wrench_frame = rospy.get_param("~wrench_frame")
00188 self.tf_list = tf.TransformListener()
00189 rospy.Subscriber("~rezero_wrench", Bool, self.rezero_wrench)
00190 self.zero_pub = rospy.Publisher("~wrench_zeroed", WrenchStamped)
00191 self.vis_pub = rospy.Publisher("~wrench_markers", Marker)
00192
00193 self.mass = rospy.get_param("~mass")
00194 self.wrench_zero = np.mat(np.zeros((6, 1)))
00195 self.wrench_zero[0, 0] = rospy.get_param("~force_zero_x")
00196 self.wrench_zero[1, 0] = rospy.get_param("~force_zero_y")
00197 self.wrench_zero[2, 0] = rospy.get_param("~force_zero_z")
00198 self.wrench_zero[3, 0] = rospy.get_param("~torque_zero_x")
00199 self.wrench_zero[4, 0] = rospy.get_param("~torque_zero_y")
00200 self.wrench_zero[5, 0] = rospy.get_param("~torque_zero_z")
00201 self.com_pos = np.mat(np.zeros((3, 1)))
00202 self.com_pos[0, 0] = rospy.get_param("~com_pos_x")
00203 self.com_pos[1, 0] = rospy.get_param("~com_pos_y")
00204 self.com_pos[2, 0] = rospy.get_param("~com_pos_z")
00205
00206 if start_zero:
00207 self.got_zero = False
00208 else:
00209 self.got_zero = True
00210
00211 if is_backwards:
00212 self.react_mult = -1.
00213 else:
00214 self.react_mult = 1.
00215
00216 self.wrench_location_frame = rospy.get_param("~wrench_location_frame")
00217 self.wrench_base_frame = rospy.get_param("~wrench_base_frame")
00218
00219 self.colors = [ColorRGBA(1., 0., 0., 1.), ColorRGBA(0., 1., 0., 1.)]
00220
00221 self.msg_lock = Lock()
00222 self.last_msg_time = 0.
00223 rospy.Subscriber(wrench_topic, WrenchStamped, self.save_cur_msg, queue_size=1)
00224 rospy.sleep(0.5)
00225
00226 def save_cur_msg(self, msg):
00227 with self.msg_lock:
00228 self.cur_msg = msg
00229 self.last_msg_time = rospy.get_time()
00230
00231 def _process_wrench(self, te):
00232 with self.msg_lock:
00233
00234
00235
00236 cur_wrench = np.mat([self.cur_msg.wrench.force.x,
00237 self.cur_msg.wrench.force.y,
00238 self.cur_msg.wrench.force.z,
00239 self.cur_msg.wrench.torque.x,
00240 self.cur_msg.wrench.torque.y,
00241 self.cur_msg.wrench.torque.z]).T
00242 header = self.cur_msg.header
00243 try:
00244 (ft_pos, ft_quat) = self.tf_list.lookupTransform(self.gravity_frame,
00245 self.wrench_frame,
00246 rospy.Time(0))
00247 except tf.LookupException as le:
00248
00249 return
00250 except tf.ExtrapolationException as ee:
00251
00252 return
00253 except tf.Exception as tfe:
00254
00255 return
00256 except:
00257 rospy.loginfo("[wrench_zeroing]: Unknown TF failure:\r\n%s" %sys.exc_info()[0])
00258 return
00259 rot_mat = np.mat(tf_trans.quaternion_matrix(ft_quat))[:3,:3]
00260 z_grav = self.react_mult * rot_mat.T * np.mat([0, 0, -1.]).T
00261 force_grav = np.mat(np.zeros((6, 1)))
00262 force_grav[:3, 0] = self.mass * g * z_grav
00263 torque_grav = np.mat(np.zeros((6, 1)))
00264 torque_grav[3:, 0] = np.mat(np.cross(self.com_pos.T.A[0], force_grav[:3, 0].T.A[0])).T
00265 zeroing_wrench = force_grav + torque_grav + self.wrench_zero
00266 zeroed_wrench = self.react_mult * (cur_wrench - zeroing_wrench)
00267
00268 if not self.got_zero:
00269 self.wrench_zero = (cur_wrench - (force_grav + torque_grav))
00270 self.got_zero = True
00271
00272 if self.wrench_frame == self.wrench_location_frame:
00273 tf_zeroed_wrench = self.transform_wrench(zeroed_wrench)
00274 if tf_zeroed_wrench is None:
00275 rospy.loginfo("Second TF Fail")
00276 return
00277 else:
00278 tf_zeroed_wrench = zeroed_wrench
00279 zero_msg = WrenchStamped(header,
00280 Wrench(Vector3(*tf_zeroed_wrench[:3,0]),
00281 Vector3(*tf_zeroed_wrench[3:,0])))
00282 self.zero_pub.publish(zero_msg)
00283 self.visualize_wrench(tf_zeroed_wrench)
00284
00285 def transform_wrench(self, wrench):
00286 try:
00287 ft_pos, ft_quat = self.tf_list.lookupTransform(self.gravity_frame,
00288 self.wrench_frame,
00289 rospy.Time(0))
00290 loc_pos, loc_quat = self.tf_list.lookupTransform(self.gravity_frame,
00291 self.wrench_location_frame,
00292 rospy.Time(0))
00293 base_pos, base_quat = self.tf_list.lookupTransform(self.wrench_base_frame,
00294 self.wrench_frame,
00295 rospy.Time(0))
00296 except:
00297 return None
00298 pos_diff = np.array(ft_pos) - np.array(loc_pos)
00299 loc_tf_wrench = wrench.copy()
00300 loc_tf_wrench[3:, 0] += np.mat(np.cross(pos_diff, loc_tf_wrench[:3, 0].T.A[0])).T
00301 base_rot_mat = np.mat(tf_trans.quaternion_matrix(base_quat))[:3,:3]
00302 zs = np.mat(np.zeros((3,3)))
00303 base_tf_wrench = np.bmat([[base_rot_mat, zs], [zs, base_rot_mat]]) * loc_tf_wrench
00304 return base_tf_wrench
00305
00306 def rezero_wrench(self, msg):
00307 self.got_zero = False
00308
00309 def visualize_wrench(self, wrench):
00310 try:
00311 loc_pos, loc_quat = self.tf_list.lookupTransform(self.wrench_base_frame,
00312 self.wrench_location_frame,
00313 rospy.Time(0))
00314 except:
00315 return
00316 self.publish_vector(self.wrench_base_frame, np.array(loc_pos), 0.05 * wrench[:3,0].A.T[0], 0)
00317 self.publish_vector(self.wrench_base_frame, np.array(loc_pos), 0.2 * wrench[3:,0].A.T[0], 1)
00318
00319 def publish_vector(self, frame, loc, v, m_id):
00320 m = Marker()
00321 m.header.frame_id = frame
00322 m.header.stamp = rospy.Time.now()
00323 m.ns = "wrench_zeroing"
00324 m.id = m_id
00325 m.type = Marker.ARROW
00326 m.action = Marker.ADD
00327 m.points.append(Point(*loc))
00328 m.points.append(Point(*(loc + v)))
00329 m.scale = Vector3(0.01, 0.02, 0.01)
00330 m.color = self.colors[m_id]
00331 self.vis_pub.publish(m)
00332
00333 def start_publisher(self, rate):
00334 self.timer_pub = rospy.Timer(rospy.Duration(1./rate), self._process_wrench)
00335
00336 def main():
00337 rospy.init_node("wrench_zeroing")
00338
00339 from optparse import OptionParser
00340 p = OptionParser()
00341 p.add_option('-f', '--file', dest="filename", default="",
00342 help="YAML file to save parameters in.")
00343 p.add_option('-l', '--load', dest="is_load",
00344 action="store_true", default=False,
00345 help="Load parameters from file.")
00346 p.add_option('-r', '--run', dest="is_run",
00347 action="store_true", default=False,
00348 help="Publish a zeroed wrench.")
00349 p.add_option('-t', '--train', dest="is_train",
00350 action="store_true", default=False,
00351 help="Train by moving the gripper to different poses and measuring the wrenches.")
00352 p.add_option('-b', '--backwards', dest="is_backwards",
00353 action="store_true", default=False,
00354 help="Set if the sensor is mounted backwards.")
00355 p.add_option('-p', '--pr2', dest="is_pr2",
00356 action="store_true", default=False,
00357 help="Will run automated data collection if this is the PR2.")
00358 p.add_option('-z', '--start_zero', dest="start_zero",
00359 action="store_true", default=False,
00360 help="Use the first value in to zero the sensor.")
00361 p.add_option('-n', '--ntrials', dest="n_trials",
00362 default="6,6,4",
00363 help="Number of trials for each of the last 3 joint angles to move through. (default: 6,6,4)")
00364 p.add_option('-c', '--rate', dest="rate", type="int", default=100,
00365 help="Rate at which to publish the output topics.")
00366 (opts, args) = p.parse_args()
00367
00368 try:
00369 n_4, n_5, n_6 = [int(n_str) for n_str in opts.n_trials.split(',')]
00370 except:
00371 print "Bad --ntrials parameter (format: --ntrials 6,6,4)"
00372 p.print_help()
00373 return
00374
00375 if opts.is_load:
00376 params = rosparam.load_file(opts.filename, rospy.get_name())[0][0]
00377 rosparam.upload_params(rospy.get_name(), params)
00378
00379 if opts.is_run:
00380 rospy.sleep(0.1)
00381 nft_z = WrenchZeroer(start_zero=opts.start_zero, is_backwards=opts.is_backwards)
00382 nft_z.start_publisher(opts.rate)
00383 rospy.spin()
00384 return
00385
00386 if opts.is_train:
00387 if opts.is_pr2:
00388 data = collect_data_pr2(n_4, n_5, n_6)
00389 param_vector = process_data(data, is_backwards=opts.is_backwards)
00390 save_params(param_vector, opts.filename)
00391 return
00392 else:
00393 data = collect_data_tool()
00394 param_vector = process_data(data, is_backwards=opts.is_backwards)
00395 save_params(param_vector, opts.filename)
00396 return
00397
00398 if __name__ == "__main__":
00399 main()