00001
00002
00003
00004
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)
00029 self.tf_cache_time = rospy.get_param("~tf_cache_time", 60)
00030 if self.publish_tf:
00031 self.broadcast = tf.TransformBroadcaster()
00032 self.invert_tf = rospy.get_param("~invert_tf", True)
00033 self.odom = None
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
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:
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
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
00102 if enable_feedback:
00103 rospy.loginfo("%s: Feedback enabled.", rospy.get_name())
00104
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
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)
00112
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
00124 new_odom_pose, new_odom_cov = self.calculate_mean_and_covariance(self.odom.pose, self.feedback_odom.pose)
00125
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
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
00137 self.offset_homogeneous_matrix = numpy.dot(numpy.linalg.inv(source_homogeneous_matrix), new_pose_homogeneous_matrix)
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]]]))
00152 covs.append(numpy.mat(src.covariance).reshape(6, 6))
00153
00154
00155
00156
00157
00158
00159
00160
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()
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
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
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
00219 if self.odom != None:
00220 result_odom.pose.covariance = self.odom.pose.covariance
00221 dt = (self.source_odom.header.stamp - self.odom.header.stamp).to_sec()
00222 else:
00223
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