OdometryOffset.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 import rospy
00004 import numpy
00005 from nav_msgs.msg import Odometry
00006 from geometry_msgs.msg import Point, Quaternion, Twist, Vector3, TwistWithCovariance, TransformStamped
00007 from std_msgs.msg import Float64, Empty
00008 import tf
00009 import time
00010 import threading
00011 import copy
00012 from dynamic_reconfigure.server import Server
00013 from jsk_robot_startup.cfg import OdometryOffsetReconfigureConfig
00014 from odometry_utils import make_homogeneous_matrix, update_twist_covariance, update_pose, update_pose_covariance, broadcast_transform, transform_local_twist_to_global, transform_local_twist_covariance_to_global
00015 
00016 class OdometryOffset(object):
00017     def __init__(self):
00018         rospy.init_node("OdometryOffset", anonymous=True)
00019         self.offset_matrix = None
00020         self.prev_odom = None
00021         self.initial_base_link_transform = make_homogeneous_matrix([0, 0, 0], [0, 0, 0, 1])
00022         self.lock = threading.Lock()
00023         # execute rate
00024         self.rate = float(rospy.get_param("~rate", 100))
00025         self.r = rospy.Rate(self.rate)
00026         # tf parameters
00027         self.publish_tf = rospy.get_param("~publish_tf", True)
00028         if self.publish_tf:
00029             self.broadcast = tf.TransformBroadcaster()
00030             self.invert_tf = rospy.get_param("~invert_tf", True)
00031         self.odom_frame = rospy.get_param("~odom_frame", "offset_odom")
00032         self.base_link_frame = rospy.get_param("~base_link_frame", "BODY")
00033         self.tf_duration = rospy.get_param("~tf_duration", 1)
00034         # for filter (only used when use_twist_filter is True)
00035         self.use_twist_filter = rospy.get_param("~use_twist_filter", False)
00036         if self.use_twist_filter:
00037             self.filter_buffer_size = rospy.get_param("~filter_buffer_size", 5)
00038             self.filter_buffer = []
00039         # to overwrite probability density function (only used when overwrite_pdf is True)
00040         self.overwrite_pdf = rospy.get_param("~overwrite_pdf", False)
00041         if self.overwrite_pdf:
00042             self.twist_proportional_sigma = rospy.get_param("~twist_proportional_sigma", False)
00043             self.v_err_mean = [rospy.get_param("~mean_x", 0.0),
00044                                rospy.get_param("~mean_y", 0.0),
00045                                rospy.get_param("~mean_z", 0.0),
00046                                rospy.get_param("~mean_roll", 0.0),
00047                                rospy.get_param("~mean_pitch", 0.0),
00048                                rospy.get_param("~mean_yaw", 0.0)]
00049             self.v_err_sigma = [rospy.get_param("~sigma_x", 0.05),
00050                                 rospy.get_param("~sigma_y", 0.1),
00051                                 rospy.get_param("~sigma_z", 0.0001),
00052                                 rospy.get_param("~sigma_roll", 0.0001),
00053                                 rospy.get_param("~sigma_pitch", 0.0001),
00054                                 rospy.get_param("~sigma_yaw", 0.01)]
00055             self.reconfigure_server = Server(OdometryOffsetReconfigureConfig, self.reconfigure_callback)
00056         self.source_odom_sub = rospy.Subscriber("~source_odom", Odometry, self.source_odom_callback)
00057         self.init_transform_sub = rospy.Subscriber("~initial_base_link_transform", TransformStamped, self.init_transform_callback) # init_transform is assumed to be transform of init_odom -> base_link
00058         self.pub = rospy.Publisher("~output", Odometry, queue_size = 1)
00059 
00060     def reconfigure_callback(self, config, level):
00061         with self.lock:
00062             for i, mean in enumerate(["mean_x", "mean_y", "mean_z", "mean_roll", "mean_pitch", "mean_yaw"]):
00063                 self.v_err_mean[i] = config[mean]
00064             for i, sigma in enumerate(["sigma_x", "sigma_y", "sigma_z", "sigma_roll", "sigma_pitch", "sigma_yaw"]):
00065                 self.v_err_sigma[i] = config[sigma]
00066         rospy.loginfo("[%s]" + "velocity mean updated: x: {0}, y: {1}, z: {2}, roll: {3}, pitch: {4}, yaw: {5}".format(*self.v_err_mean), rospy.get_name())                
00067         rospy.loginfo("[%s]" + "velocity sigma updated: x: {0}, y: {1}, z: {2}, roll: {3}, pitch: {4}, yaw: {5}".format(*self.v_err_sigma), rospy.get_name())
00068         return config
00069 
00070     def calculate_offset(self, source_odom):
00071         # tf relationships: init_odom -> source_odom -> body_link <- init_odom
00072         # offset_odom is init_odom -> source_odom
00073         # TODO: check timestamps of base_odom and source_odom and fix if necessary
00074         if self.initial_base_link_transform == None:
00075             rospy.loginfo("[%s] initial transform is not subscribed yet", rospy.get_name())
00076             return None
00077         source_odom_matrix = make_homogeneous_matrix([getattr(source_odom.pose.pose.position, attr) for attr in ["x", "y", "z"]], [getattr(source_odom.pose.pose.orientation, attr) for attr in ["x", "y", "z", "w"]]) # source_odom -> body_link
00078         return self.initial_base_link_transform.dot(numpy.linalg.inv(source_odom_matrix)) # T(init->src) * T(src->body) = T(init->body)
00079 
00080     def execute(self):
00081         while not rospy.is_shutdown():
00082             self.r.sleep()
00083 
00084     def init_transform_callback(self, msg):
00085         with self.lock:
00086             self.initial_base_link_transform = make_homogeneous_matrix([getattr(msg.transform.translation, attr) for attr in ["x", "y", "z"]], [getattr(msg.transform.rotation, attr) for attr in ["x", "y", "z", "w"]]) # base_odom -> init_odom
00087             self.prev_odom = None
00088             self.offset_matrix = None
00089             if self.use_twist_filter:
00090                 self.filter_buffer = []
00091 
00092     def source_odom_callback(self, msg):
00093         with self.lock:
00094             if self.offset_matrix != None:
00095                 source_odom_matrix = make_homogeneous_matrix([getattr(msg.pose.pose.position, attr) for attr in ["x", "y", "z"]], [getattr(msg.pose.pose.orientation, attr) for attr in ["x", "y", "z", "w"]])
00096                 new_odom = copy.deepcopy(msg)
00097                 new_odom.header.frame_id = self.odom_frame
00098                 new_odom.child_frame_id = self.base_link_frame
00099                 twist_list = [new_odom.twist.twist.linear.x, new_odom.twist.twist.linear.y, new_odom.twist.twist.linear.z, new_odom.twist.twist.angular.x, new_odom.twist.twist.angular.y, new_odom.twist.twist.angular.z]
00100                 
00101                 # use median filter to cancel spike noise of twist when use_twist_filter is true
00102                 if self.use_twist_filter:                    
00103                     twist_list = self.median_filter(twist_list)
00104                     new_odom.twist.twist = Twist(Vector3(*twist_list[0:3]), Vector3(*twist_list[3: 6]))
00105                     
00106                 # overwrite twist covariance when calculate_covariance flag is True
00107                 if self.overwrite_pdf:
00108                     if not all([abs(x) < 1e-3 for x in twist_list]):
00109                         # shift twist according to error mean when moving (stopping state is trusted)
00110                         twist_list = [x + y for x, y in zip(twist_list, self.v_err_mean)]
00111                         new_odom.twist.twist = Twist(Vector3(*twist_list[0:3]), Vector3(*twist_list[3: 6]))
00112                     # calculate twist covariance according to standard diviation
00113                     if self.twist_proportional_sigma:
00114                         current_sigma = [x * y for x, y in zip(twist_list, self.v_err_sigma)]
00115                     else:
00116                         if all([abs(x) < 1e-3 for x in twist_list]):
00117                             current_sigma = [1e-6] * 6 # trust stopping state
00118                         else:
00119                             current_sigma = self.v_err_sigma
00120                     new_odom.twist.covariance = update_twist_covariance(new_odom.twist, current_sigma)
00121                     
00122                 # offset coords
00123                 new_odom_matrix = self.offset_matrix.dot(source_odom_matrix)
00124                 new_odom.pose.pose.position = Point(*list(new_odom_matrix[:3, 3]))
00125                 new_odom.pose.pose.orientation = Quaternion(*list(tf.transformations.quaternion_from_matrix(new_odom_matrix)))
00126 
00127                 if self.overwrite_pdf:
00128                     if self.prev_odom != None:
00129                         dt = (new_odom.header.stamp - self.prev_odom.header.stamp).to_sec()
00130                         global_twist_with_covariance = TwistWithCovariance(transform_local_twist_to_global(new_odom.pose.pose, new_odom.twist.twist),
00131                                                                            transform_local_twist_covariance_to_global(new_odom.pose.pose, new_odom.twist.covariance))
00132                         new_odom.pose.covariance = update_pose_covariance(self.prev_odom.pose.covariance, global_twist_with_covariance.covariance, dt)
00133                     else:
00134                         new_odom.pose.covariance = numpy.diag([0.01**2] * 6).reshape(-1,).tolist() # initial covariance is assumed to be constant
00135                 else:
00136                     # only offset pose covariance
00137                     new_pose_cov_matrix = numpy.matrix(new_odom.pose.covariance).reshape(6, 6)
00138                     rotation_matrix = self.offset_matrix[:3, :3]
00139                     new_pose_cov_matrix[:3, :3] = (rotation_matrix.T).dot(new_pose_cov_matrix[:3, :3].dot(rotation_matrix))
00140                     new_pose_cov_matrix[3:6, 3:6] = (rotation_matrix.T).dot(new_pose_cov_matrix[3:6, 3:6].dot(rotation_matrix))
00141                     new_odom.pose.covariance = numpy.array(new_pose_cov_matrix).reshape(-1,).tolist()
00142 
00143                 # publish
00144                 self.pub.publish(new_odom)
00145                 if self.publish_tf:
00146                     broadcast_transform(self.broadcast, new_odom, self.invert_tf)
00147                 self.prev_odom = new_odom
00148                 
00149             else:
00150                 self.offset_matrix = self.calculate_offset(msg)
00151 
00152     def median_filter(self, data):
00153         self.filter_buffer.append(data)
00154         ret = numpy.median(self.filter_buffer, axis = 0)
00155         if len(self.filter_buffer) >= self.filter_buffer_size:
00156             self.filter_buffer.pop(0) # filter_buffer has at least 1 member
00157         return ret


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