OdometryFeedbackWrapper.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 # This script only calculate offset caused by odometry feedback and do not consider initial offset.
00004 # Initial offset should be calculated by OdometryOffset.py (source_odom is assumed to be offseted already) 
00005 
00006 import rospy
00007 import numpy
00008 from nav_msgs.msg import Odometry
00009 from std_msgs.msg import Float64, Empty
00010 from geometry_msgs.msg import Pose, Point, Quaternion, Twist, Vector3
00011 import tf
00012 import sys
00013 import threading
00014 import copy
00015 from scipy import signal
00016 from dynamic_reconfigure.server import Server
00017 from jsk_robot_startup.cfg import OdometryFeedbackWrapperReconfigureConfig
00018 
00019 from odometry_utils import transform_local_twist_to_global, transform_local_twist_covariance_to_global, update_pose, update_pose_covariance, broadcast_transform, make_homogeneous_matrix
00020 
00021 class OdometryFeedbackWrapper(object):
00022     def __init__(self):
00023         rospy.init_node("OdometryFeedbackWrapper", anonymous=True)
00024         self.rate = float(rospy.get_param("~rate", 100))
00025         self.publish_tf = rospy.get_param("~publish_tf", True)
00026         self.odom_frame = rospy.get_param("~odom_frame", "feedback_odom")
00027         self.base_link_frame = rospy.get_param("~base_link_frame", "BODY")
00028         self.max_feedback_time = rospy.get_param("~max_feedback_time", 60) # if max_feedback_time <= 0, feedback is not occurs by time
00029         self.tf_cache_time = rospy.get_param("~tf_cache_time", 60) # determined from frequency of feedback_odom
00030         if self.publish_tf:
00031             self.broadcast = tf.TransformBroadcaster()
00032             self.invert_tf = rospy.get_param("~invert_tf", True)
00033         self.odom = None # belief of this wrapper
00034         self.feedback_odom = None
00035         self.source_odom = None
00036         self.offset_homogeneous_matrix = None
00037         self.dt = 0.0
00038         self.prev_feedback_time = rospy.Time.now()
00039         self.r = rospy.Rate(self.rate)
00040         self.lock = threading.Lock()
00041         self.odom_history = []
00042         self.force_feedback_sigma = rospy.get_param("~force_feedback_sigma", 0.5)
00043         self.distribution_feedback_minimum_sigma = rospy.get_param("~distribution_feedback_minimum_sigma", 0.5)
00044         self.pub = rospy.Publisher("~output", Odometry, queue_size = 1)
00045         self.initialize_odometry()
00046         self.init_signal_sub = rospy.Subscriber("~init_signal", Empty, self.init_signal_callback, queue_size = 10)
00047         self.source_odom_sub = rospy.Subscriber("~source_odom", Odometry, self.source_odom_callback, queue_size = 10)
00048         self.feedback_odom_sub = rospy.Subscriber("~feedback_odom", Odometry, self.feedback_odom_callback, queue_size = 10)
00049         self.reconfigure_server = Server(OdometryFeedbackWrapperReconfigureConfig, self.reconfigure_callback)
00050 
00051     def execute(self):
00052         while not rospy.is_shutdown():
00053             self.r.sleep()
00054 
00055     def reconfigure_callback(self, config, level):
00056         self.force_feedback_sigma = config["force_feedback_sigma"]
00057         rospy.loginfo("[%s]: force feedback sigma is %f", rospy.get_name(), self.force_feedback_sigma)
00058         self.distribution_feedback_minimum_sigma = config["distribution_feedback_minimum_sigma"]
00059         rospy.loginfo("[%s]: distribution feedback minimum sigma is %f", rospy.get_name(), self.distribution_feedback_minimum_sigma)
00060         return config
00061 
00062     def init_signal_callback(self, msg):
00063         self.initialize_odometry()
00064 
00065     def initialize_odometry(self):
00066         with self.lock:
00067             self.odom_history = []
00068             self.prev_feedback_time = rospy.Time.now()
00069             self.offset_homogeneous_matrix = tf.transformations.quaternion_matrix([0, 0, 0, 1])
00070             self.odom = None
00071             self.source_odom = None
00072             self.feedback_odom = None
00073 
00074     def source_odom_callback(self, msg):
00075         with self.lock:
00076             self.source_odom = msg
00077             self.calculate_odometry(self.odom, self.source_odom)
00078             self.publish_odometry()
00079             if self.publish_tf:
00080                 broadcast_transform(self.broadcast, self.odom, self.invert_tf)
00081 
00082     def feedback_odom_callback(self, msg):
00083         if not self.odom:
00084             return
00085         self.feedback_odom = msg
00086         with self.lock:
00087             # check distribution accuracy
00088             nearest_odom = copy.deepcopy(self.odom)
00089             nearest_dt = (self.feedback_odom.header.stamp - self.odom.header.stamp).to_sec()
00090             for hist in self.odom_history: # get neaerest odom from feedback_odom referencing timestamp
00091                 dt = (self.feedback_odom.header.stamp - hist.header.stamp).to_sec()
00092                 if abs(dt) < abs(nearest_dt):
00093                     nearest_dt = dt
00094                     nearest_odom = copy.deepcopy(hist)
00095             # get approximate pose at feedback_odom timestamp (probably it is past) of nearest_odom
00096             global_nearest_odom_twist = transform_local_twist_to_global(nearest_odom.pose.pose, nearest_odom.twist.twist)
00097             nearest_odom.pose.pose = update_pose(nearest_odom.pose.pose, global_nearest_odom_twist, nearest_dt)
00098             global_nearest_odom_twist_covariance = transform_local_twist_covariance_to_global(nearest_odom.pose.pose, nearest_odom.twist.covariance)
00099             nearest_odom.pose.covariance = update_pose_covariance(nearest_odom.pose.covariance, global_nearest_odom_twist_covariance, nearest_dt)
00100             enable_feedback = self.check_covariance(nearest_odom) or self.check_distribution_difference(nearest_odom, self.feedback_odom) or self.check_feedback_time()
00101             # update feedback_odom to approximate odom at current timestamp using previsous velocities
00102             if enable_feedback:
00103                 rospy.loginfo("%s: Feedback enabled.", rospy.get_name())
00104                 # adjust timestamp of self.feedback_odom to current self.odom
00105                 for hist in self.odom_history:
00106                     dt = (hist.header.stamp - self.feedback_odom.header.stamp).to_sec()
00107                     if dt > 0.0:
00108                         # update pose and twist according to the history
00109                         self.update_twist(self.feedback_odom.twist, hist.twist)
00110                         global_hist_twist = transform_local_twist_to_global(hist.pose.pose, hist.twist.twist)
00111                         self.feedback_odom.pose.pose = update_pose(self.feedback_odom.pose.pose, global_hist_twist, dt) # update feedback_odom according to twist of hist
00112                         # update covariance
00113                         self.feedback_odom.twist.covariance = hist.twist.covariance
00114                         global_hist_twist_covariance = transform_local_twist_covariance_to_global(self.feedback_odom.pose.pose, hist.twist.covariance)
00115                         self.feedback_odom.pose.covariance = update_pose_covariance(self.feedback_odom.pose.covariance, global_hist_twist_covariance, dt)
00116                         self.feedback_odom.header.stamp = hist.header.stamp
00117                 dt = (self.odom.header.stamp - self.feedback_odom.header.stamp).to_sec()
00118                 global_feedback_odom_twist = transform_local_twist_to_global(self.feedback_odom.pose.pose, self.feedback_odom.twist.twist)
00119                 self.feedback_odom.pose.pose = update_pose(self.feedback_odom.pose.pose, global_feedback_odom_twist, dt)
00120                 global_feedback_odom_twist_covariance = transform_local_twist_covariance_to_global(self.feedback_odom.pose.pose, self.feedback_odom.twist.covariance)
00121                 self.feedback_odom.pose.covariance = update_pose_covariance(self.feedback_odom.pose.covariance, global_feedback_odom_twist_covariance, dt)
00122                 self.feedback_odom.header.stamp = self.odom.header.stamp
00123                 # integrate feedback_odom and current odom
00124                 new_odom_pose, new_odom_cov = self.calculate_mean_and_covariance(self.odom.pose, self.feedback_odom.pose)
00125                 # update self.odom according to the result of integration
00126                 quat = tf.transformations.quaternion_from_euler(*new_odom_pose[3:6])
00127                 self.odom.pose.pose = Pose(Point(*new_odom_pose[0:3]), Quaternion(*quat))
00128                 self.odom.pose.covariance = new_odom_cov
00129                 self.prev_feedback_time = self.odom.header.stamp
00130                 self.odom_history = []
00131                 # update offset
00132                 new_pose_homogeneous_matrix = make_homogeneous_matrix([self.odom.pose.pose.position.x, self.odom.pose.pose.position.y, self.odom.pose.pose.position.z],
00133                                                                       [self.odom.pose.pose.orientation.x, self.odom.pose.pose.orientation.y, self.odom.pose.pose.orientation.z, self.odom.pose.pose.orientation.w])
00134                 source_homogeneous_matrix = make_homogeneous_matrix([self.source_odom.pose.pose.position.x, self.source_odom.pose.pose.position.y, self.source_odom.pose.pose.position.z],
00135                                                                     [self.source_odom.pose.pose.orientation.x, self.source_odom.pose.pose.orientation.y, self.source_odom.pose.pose.orientation.z, self.source_odom.pose.pose.orientation.w])
00136                 # Hnew = Hold * T -> T = Hold^-1 * Hnew
00137                 self.offset_homogeneous_matrix = numpy.dot(numpy.linalg.inv(source_homogeneous_matrix), new_pose_homogeneous_matrix) # self.odom.header.stamp is assumed to be same as self.source_odom.header.stamp
00138                 
00139     def calculate_mean_and_covariance(self, current_pose, feedback_pose):
00140         sources = [current_pose, feedback_pose]
00141         means = []
00142         covs = []
00143         for src in sources:
00144             euler = tf.transformations.euler_from_quaternion([src.pose.orientation.x, src.pose.orientation.y,
00145                                                               src.pose.orientation.z, src.pose.orientation.w])
00146             means.append(numpy.array([[src.pose.position.x],
00147                                       [src.pose.position.y],
00148                                       [src.pose.position.z],
00149                                       [euler[0]],
00150                                       [euler[1]],
00151                                       [euler[2]]])) # 6d column vector
00152             covs.append(numpy.mat(src.covariance).reshape(6, 6))
00153     
00154         # Calculate new state by most likelihood method:
00155         # sigma_new = (sigma_0^-1 + sigma_1^-1)^-1
00156         # x_new = sigma_new * (sigma_0^-1 * x_0 + sigma_1^-1 * x_1)
00157         # Less inverse form (same as above):
00158         # sigma__new = sigma_1 - sigma_1 * (sigma_1 + sigma_2)^-1 * sigma_1
00159         # x_new = x1 + sigma_1*(sigma_1+sigma_2)^-1*(x_2-x_1)
00160         # cf. "Distributed sensor fusion for object position estimation by multi-robot systems"
00161         cov0_inv_sum_covs = numpy.dot(covs[0], numpy.linalg.inv(covs[0] + covs[1]))
00162         new_cov = covs[0] - numpy.dot(cov0_inv_sum_covs, covs[0])        
00163         new_mean = means[0] + numpy.dot(cov0_inv_sum_covs, means[1] - means[0])
00164         return numpy.array(new_mean).reshape(-1,).tolist(), numpy.array(new_cov).reshape(-1,).tolist() # return list
00165 
00166     def check_feedback_time(self):
00167         time_from_prev_feedback = (self.odom.header.stamp - self.prev_feedback_time).to_sec()
00168         if self.max_feedback_time > 0 and time_from_prev_feedback > self.max_feedback_time:
00169             rospy.loginfo("%s: Feedback time is exceeded. %f > %f", rospy.get_name(), time_from_prev_feedback, self.max_feedback_time)
00170             return True
00171         else:
00172             return False
00173 
00174     def check_covariance(self, odom):
00175         for cov in odom.pose.covariance:
00176             if cov > self.force_feedback_sigma ** 2:
00177                 rospy.loginfo("%s: Covariance exceeds limitation. %f > %f", rospy.get_name(), cov, self.force_feedback_sigma)
00178                 return True
00179         return False
00180 
00181     def check_distribution_difference(self, nearest_odom, feedback_odom):
00182         def make_pose_set(odom):
00183             odom_euler = tf.transformations.euler_from_quaternion((odom.pose.pose.orientation.x, odom.pose.pose.orientation.y,
00184                                                                    odom.pose.pose.orientation.z, odom.pose.pose.orientation.w))
00185             odom_pose_list = [odom.pose.pose.position.x, odom.pose.pose.position.y, odom.pose.pose.position.z,
00186                               odom_euler[0], odom_euler[1], odom_euler[2]]
00187             odom_cov_matrix = numpy.matrix(odom.pose.covariance).reshape(6, 6)
00188             return odom_pose_list, odom_cov_matrix
00189         nearest_odom_pose, nearest_odom_cov_matrix = make_pose_set(nearest_odom)
00190         feedback_odom_pose, feedback_odom_cov_matrix = make_pose_set(feedback_odom)
00191         for i in range(6):
00192             if abs(nearest_odom_pose[i] - feedback_odom_pose[i]) > 3 * numpy.sqrt(nearest_odom_cov_matrix[i, i]) and numpy.sqrt(nearest_odom_cov_matrix[i, i]) > self.distribution_feedback_minimum_sigma:
00193                 rospy.loginfo("%s: Pose difference is larger than original sigma * 3. %f > %f (> %f)",
00194                               rospy.get_name(), abs(nearest_odom_pose[i] - feedback_odom_pose[i]), numpy.sqrt(nearest_odom_cov_matrix[i, i]),
00195                               self.distribution_feedback_minimum_sigma)
00196                 return True
00197         return False
00198             
00199     def publish_odometry(self):
00200         self.pub.publish(self.odom)
00201         self.odom_history.append(copy.deepcopy(self.odom))
00202 
00203     def calculate_odometry(self, odom, source_odom):
00204         result_odom = copy.deepcopy(source_odom)
00205         result_odom.header.frame_id = self.odom_frame
00206         result_odom.child_frame_id = self.base_link_frame
00207 
00208         # consider only pose because twist is local and copied from source_odom
00209         position = [source_odom.pose.pose.position.x, source_odom.pose.pose.position.y, source_odom.pose.pose.position.z]
00210         orientation = [source_odom.pose.pose.orientation.x, source_odom.pose.pose.orientation.y, source_odom.pose.pose.orientation.z, source_odom.pose.pose.orientation.w]
00211 
00212         # calculate pose (use odometry source)
00213         source_homogeneous_matrix = make_homogeneous_matrix(position, orientation)
00214         result_homogeneous_matrix = numpy.dot(source_homogeneous_matrix, self.offset_homogeneous_matrix)
00215         result_odom.pose.pose.position = Point(*list(result_homogeneous_matrix[:3, 3]))
00216         result_odom.pose.pose.orientation = Quaternion(*list(tf.transformations.quaternion_from_matrix(result_homogeneous_matrix)))
00217 
00218         # calculate pose covariance (do not use odometry source)
00219         if self.odom != None:
00220             result_odom.pose.covariance =  self.odom.pose.covariance # do not use source_odom covariance in pose
00221             dt = (self.source_odom.header.stamp - self.odom.header.stamp).to_sec()
00222         else:
00223             # initial covariance of pose is defined as same value of source_odom
00224             dt = 0.0
00225         global_twist_covariance = transform_local_twist_covariance_to_global(result_odom.pose.pose, result_odom.twist.covariance)
00226         result_odom.pose.covariance = update_pose_covariance(result_odom.pose.covariance, global_twist_covariance, dt)
00227         self.odom = result_odom
00228 
00229     def update_twist(self, twist, new_twist):
00230         twist.twist = new_twist.twist


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