OdometryIntegrator.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 # license removed for brevity
00003 import rospy
00004 from nav_msgs.msg import Odometry
00005 from geometry_msgs.msg import Point, Quaternion, Vector3
00006 import numpy
00007 import tf
00008 import threading
00009 import sys
00010 import itertools
00011 
00012 # input assumption:
00013 # pose is described in each odom coordinate
00014 # twist is described in base_link_frame coordinate same as ~base_link_frame param
00015 # tf of each odom to base_link_frame is enabled
00016 
00017 class OdometryIntegrator:
00018     def __init__(self):
00019         rospy.init_node("OdometryIntegrator", anonymous=True)
00020         self.odoms = [None, None]
00021         self.result_odom = None
00022         self.lock = threading.Lock()
00023         self.pub = rospy.Publisher("~output", Odometry, queue_size=10)
00024         self.sub = rospy.Subscriber("~source_odom_0", Odometry, self.callback, 0)
00025         self.sub = rospy.Subscriber("~source_odom_1", Odometry, self.callback, 1)
00026         self.rate = rospy.get_param("~rate", 100)
00027         self.r = rospy.Rate(self.rate)
00028         self.publish_tf = rospy.get_param("~publish_tf", True)
00029         self.invert_tf = rospy.get_param("~invert_tf", True)
00030         self.odom_frame = rospy.get_param("~odom_frame", "integrated_odom")
00031         self.base_link_frame = rospy.get_param("~base_link_frame", "BODY")
00032         self.broadcast = tf.TransformBroadcaster()
00033         self.listener = tf.TransformListener()
00034         self.time_threshould = float(rospy.get_param("~time_threshould", 0.1)) # [sec]
00035 
00036     def execute(self):
00037         while not rospy.is_shutdown():
00038             with self.lock:
00039                 self.integrate_odom()
00040                 if self.publish_tf:
00041                     self.broadcast_transform()
00042             self.r.sleep()
00043 
00044     def callback(self, msg, index):
00045         if index < len(self.odoms):
00046             with self.lock:
00047                 self.odoms[index] = msg
00048 
00049     def integrate_odom(self):
00050         poses = []
00051         pose_covs = []
00052         twists = []
00053         twist_covs = []
00054 
00055         # check initialization
00056         for odom in self.odoms:
00057             if not odom:
00058                 rospy.logwarn("odometry source cannot be subscribed")
00059                 return
00060         
00061         # check timestamp
00062         for odom_pair in list(itertools.combinations(self.odoms, 2)):
00063             time_diff = abs((odom_pair[0].header.stamp - odom_pair[1].header.stamp).to_sec())
00064             if time_diff > self.time_threshould:
00065                 rospy.logwarn("odometry source timestamp is too distant: %f [sec]", time_diff)
00066                 return
00067 
00068         # adjust timestamps of odom sources
00069         # self.odoms.sort(cmp = lambda x, y: cmp(x.header.stamp, y.header.stamp))
00070         current_time = rospy.Time.now()
00071         for odom in self.odoms:
00072             dt = (current_time - odom.header.stamp).to_sec()
00073             self.update_odom_pose(odom, dt)
00074 
00075         # make state vector and covariance matrix for pose and twist from odometry source
00076         for i in range(len(self.odoms)):
00077             euler = tf.transformations.euler_from_quaternion([self.odoms[i].pose.pose.orientation.x, self.odoms[i].pose.pose.orientation.y,
00078                                                               self.odoms[i].pose.pose.orientation.z, self.odoms[i].pose.pose.orientation.w])
00079             poses.append(numpy.array([[self.odoms[i].pose.pose.position.x],
00080                                       [self.odoms[i].pose.pose.position.y],
00081                                       [self.odoms[i].pose.pose.position.z],
00082                                       [euler[0]],
00083                                       [euler[1]],
00084                                       [euler[2]]])) # 6d column vector
00085             pose_covs.append(numpy.mat(self.odoms[i].pose.covariance).reshape(6, 6))
00086             twists.append(numpy.array([[self.odoms[i].twist.twist.linear.x],
00087                                        [self.odoms[i].twist.twist.linear.y],
00088                                        [self.odoms[i].twist.twist.linear.z],
00089                                        [self.odoms[i].twist.twist.angular.x],
00090                                        [self.odoms[i].twist.twist.angular.y],
00091                                        [self.odoms[i].twist.twist.angular.z]])) # 6d column vector
00092             twist_covs.append(numpy.mat(self.odoms[i].twist.covariance).reshape(6, 6))
00093            
00094         new_pose, new_pose_cov = self.calculate_mean_and_covariance(poses, pose_covs) # assumed to be described in each odom frame
00095         new_twist, new_twist_cov = self.calculate_mean_and_covariance(twists, twist_covs) # assumed to be described in base_link_frame
00096 
00097         # publish integrated odometry
00098         self.result_odom = Odometry()
00099         self.result_odom.header.stamp = current_time
00100         self.result_odom.header.frame_id = self.odom_frame
00101         self.result_odom.child_frame_id = self.base_link_frame
00102         quat = tf.transformations.quaternion_from_euler(*new_pose[3:6])
00103         self.result_odom.pose.pose.position = Point(*new_pose[0:3])
00104         self.result_odom.pose.pose.orientation = Quaternion(*quat)
00105         self.result_odom.pose.covariance = numpy.array(new_pose_cov).reshape(-1,).tolist()
00106         self.result_odom.twist.twist.linear = Vector3(*new_twist[0:3])
00107         self.result_odom.twist.twist.angular = Vector3(*new_twist[3:6])
00108         self.result_odom.twist.covariance = numpy.array(new_twist_cov).reshape(-1,).tolist()
00109         self.pub.publish(self.result_odom)
00110 
00111     def calculate_mean_and_covariance(self, means, covs):
00112         # Calculate new state by most likelihood method:
00113         # sigma_new = (sigma_0^-1 + sigma_1^-1)^-1
00114         # x_new = sigma_new * (sigma_0^-1 * x_0 + sigma_1^-1 * x_1)
00115         # Less inverse form (same as above):
00116         # sigma__new = sigma_1 - sigma_1 * (sigma_1 + sigma_2)^-1 * sigma_1
00117         # x_new = x1 + sigma_1*(sigma_1+sigma_2)^-1*(x_2-x_1)
00118         # cf. "Distributed sensor fusion for object position estimation by multi-robot systems"
00119         cov0_inv_sum_covs = numpy.dot(covs[0], numpy.linalg.inv(covs[0] + covs[1]))
00120         new_cov = covs[0] - numpy.dot(cov0_inv_sum_covs, covs[0])        
00121         new_mean = means[0] + numpy.dot(cov0_inv_sum_covs, means[1] - means[0])
00122         return new_mean, new_cov
00123 
00124     def update_odom_pose(self, odom, dt):
00125         try:
00126             (trans,rot) = self.listener.lookupTransform(odom.header.frame_id, odom.child_frame_id, odom.header.stamp)
00127         except:
00128             try:
00129                 rospy.logwarn("timestamp %f of tf (%s to %s) is not correct. use rospy.Time(0).",  odom.header.stamp.to_sec(), odom.header.frame_id, odom.child_frame_id)
00130                 (trans,rot) = self.listener.lookupTransform(odom.header.frame_id, odom.child_frame_id, rospy.Time(0)) # todo: lookup odom.header.stamp
00131             except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
00132                 rospy.logwarn("failed to solve tf: %s to %s", odom.header.frame_id, odom.child_frame_id)
00133                 return
00134         rotation_matrix = tf.transformations.quaternion_matrix(rot)[:3, :3]
00135         global_velocity = numpy.dot(rotation_matrix, numpy.array([[odom.twist.twist.linear.x],
00136                                                                   [odom.twist.twist.linear.y],
00137                                                                   [odom.twist.twist.linear.z]]))
00138         global_omega = numpy.dot(rotation_matrix, numpy.array([[odom.twist.twist.angular.x],
00139                                                                [odom.twist.twist.angular.y],
00140                                                                [odom.twist.twist.angular.z]]))
00141         odom.pose.pose.position.x += global_velocity[0, 0] * dt
00142         odom.pose.pose.position.y += global_velocity[1, 0] * dt
00143         odom.pose.pose.position.z += global_velocity[2, 0] * dt
00144         euler = list(tf.transformations.euler_from_quaternion((odom.pose.pose.orientation.x, odom.pose.pose.orientation.y,
00145                                                                odom.pose.pose.orientation.z, odom.pose.pose.orientation.w)))
00146         euler[0] += global_omega[0, 0] * dt
00147         euler[1] += global_omega[1, 0] * dt
00148         euler[2] += global_omega[2, 0] * dt
00149         quat = tf.transformations.quaternion_from_euler(*euler)
00150         odom.pose.pose.orientation = Quaternion(*quat)
00151 
00152     def broadcast_transform(self):
00153         if not self.result_odom:
00154             return
00155         position = [self.result_odom.pose.pose.position.x, self.result_odom.pose.pose.position.y, self.result_odom.pose.pose.position.z]
00156         orientation = [self.result_odom.pose.pose.orientation.x, self.result_odom.pose.pose.orientation.y, self.result_odom.pose.pose.orientation.z, self.result_odom.pose.pose.orientation.w]
00157         if self.invert_tf:
00158             homogeneous_matrix = tf.transformations.quaternion_matrix(orientation)
00159             homogeneous_matrix[:3, 3] = numpy.array(position).reshape(1, 3)
00160             homogeneous_matrix_inv = numpy.linalg.inv(homogeneous_matrix)
00161             position = list(homogeneous_matrix_inv[:3, 3])
00162             orientation = list(tf.transformations.quaternion_from_matrix(homogeneous_matrix_inv))
00163             parent_frame = self.base_link_frame
00164             target_frame = self.odom_frame
00165         else:
00166             parent_frame = self.odom_frame
00167             target_frame = self.base_link_frame
00168         self.broadcast.sendTransform(position, orientation, rospy.Time.now(), target_frame, parent_frame)
00169         
00170 if __name__ == '__main__':
00171     try:
00172         node = OdometryIntegrator()
00173         node.execute()
00174     except rospy.ROSInterruptException: pass


jsk_robot_startup
Author(s):
autogenerated on Sat Jul 1 2017 02:42:18