echo.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # tf2 echo code Copyright (c) 2018, Lucas Walter
4 # transformations.py code Copyright (c) 2006-2017, Christoph Gohlke
5 # transformations.py code Copyright (c) 2006-2017, The Regents of the University of California
6 # Produced at the Laboratory for Fluorescence Dynamics
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 # * Neither the name of the copyright holders nor the names of any
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from __future__ import print_function
34 
35 import argparse
36 import math
37 import numpy
38 import rospy
39 import sys
40 import tf2_py as tf2
41 import tf2_ros
42 
43 from geometry_msgs.msg import TransformStamped
44 # https://github.com/ros/geometry2/issues/222
45 # from tf import transformations
46 
47 """
48 The following euler conversion functions are from https://github.com/matthew-brett/transforms3d
49 which adapted it from transformations.py, it is needed here until transforms3d is available
50 as a dependency.
51 
52 They are for internal use only.
53 """
54 
55 # epsilon for testing whether a number is close to zero
56 _EPS = numpy.finfo(float).eps * 4.0
57 
58 # axis sequences for Euler angles
59 _NEXT_AXIS = [1, 2, 0, 1]
60 
61 # TODO(lucasw) if sxyz works then eliminate the other possibilities
62 # map axes strings to/from tuples of inner axis, parity, repetition, frame
63 _AXES2TUPLE = {
64  'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0),
65  'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0),
66  'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0),
67  'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0),
68  'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1),
69  'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1),
70  'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1),
71  'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)}
72 
73 def _euler_from_matrix(matrix, axes='sxyz'):
74  """temporaray import from https://github.com/matthew-brett/transforms3d/blob/master/transforms3d/_gohlketransforms.py for internal use only"""
75  try:
76  firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
77  except (AttributeError, KeyError):
78  _TUPLE2AXES[axes] # validation
79  firstaxis, parity, repetition, frame = axes
80 
81  i = firstaxis
82  j = _NEXT_AXIS[i+parity]
83  k = _NEXT_AXIS[i-parity+1]
84 
85  M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
86  if repetition:
87  sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k])
88  if sy > _EPS:
89  ax = math.atan2( M[i, j], M[i, k])
90  ay = math.atan2( sy, M[i, i])
91  az = math.atan2( M[j, i], -M[k, i])
92  else:
93  ax = math.atan2(-M[j, k], M[j, j])
94  ay = math.atan2( sy, M[i, i])
95  az = 0.0
96  else:
97  cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i])
98  if cy > _EPS:
99  ax = math.atan2( M[k, j], M[k, k])
100  ay = math.atan2(-M[k, i], cy)
101  az = math.atan2( M[j, i], M[i, i])
102  else:
103  ax = math.atan2(-M[j, k], M[j, j])
104  ay = math.atan2(-M[k, i], cy)
105  az = 0.0
106 
107  if parity:
108  ax, ay, az = -ax, -ay, -az
109  if frame:
110  ax, az = az, ax
111  return ax, ay, az
112 
113 def _quaternion_matrix(quaternion):
114  """temporaray import from https://github.com/matthew-brett/transforms3d/blob/master/transforms3d/_gohlketransforms.py for internal use only"""
115  q = numpy.array(quaternion, dtype=numpy.float64, copy=True)
116  n = numpy.dot(q, q)
117  if n < _EPS:
118  return numpy.identity(4)
119  q *= math.sqrt(2.0 / n)
120  q = numpy.outer(q, q)
121  return numpy.array([
122  [1.0-q[2, 2]-q[3, 3], q[1, 2]-q[3, 0], q[1, 3]+q[2, 0], 0.0],
123  [ q[1, 2]+q[3, 0], 1.0-q[1, 1]-q[3, 3], q[2, 3]-q[1, 0], 0.0],
124  [ q[1, 3]-q[2, 0], q[2, 3]+q[1, 0], 1.0-q[1, 1]-q[2, 2], 0.0],
125  [ 0.0, 0.0, 0.0, 1.0]])
126 
127 def _euler_from_quaternion(quaternion, axes='sxyz'):
128  """temporaray import from https://github.com/matthew-brett/transforms3d/blob/master/transforms3d/_gohlketransforms.py for internal use only"""
129  return _euler_from_matrix(_quaternion_matrix(quaternion), axes)
130 
132  # the above code is from transform3 which changed convention from old transformations.py
133  # from xyzw to wxyz
134  # return transformations.euler_from_quaternion([quaternion.x, quaternion.y, quaternion.z, quaternion.w])
135  return _euler_from_quaternion([quaternion.w,
136  quaternion.x,
137  quaternion.y,
138  quaternion.z])
139 
140 class Echo():
141  def __init__(self, args):
142  self.tf_buffer = tf2_ros.Buffer(cache_time=args.cache_time)
144  self.args = args
145 
146  self.count = 0
147  self.timer = rospy.Timer(rospy.Duration(1.0 / self.args.rate), self.lookup)
148 
149  def lookup(self, event):
150  self.count += 1
151  if self.args.limit:
152  if self.count > self.args.limit:
153  # TODO(lucasw) is there a better method to stop the spin()?
154  rospy.signal_shutdown("tf echo finished")
155  return
156 
157  cur_time = rospy.Time.now()
158  # If the transform is from tf_static the ts.header.stamp will be 0.0
159  # when offset == 0 or lookup_time is rospy.Time()
160  if self.args.time:
161  lookup_time = rospy.Time(self.args.time)
162  elif self.args.offset:
163  # If the transform is static this will always work
164  lookup_time = cur_time + rospy.Duration(self.args.offset)
165  else:
166  # Get the most recent transform
167  lookup_time = rospy.Time()
168 
169  try:
170  ts = self.tf_buffer.lookup_transform(self.args.source_frame,
171  self.args.target_frame,
172  lookup_time)
173  except tf2.LookupException as ex:
174  msg = "At time {}, (current time {}) ".format(lookup_time.to_sec(), cur_time.to_sec())
175  rospy.logerr(msg + str(ex))
176  return
177  except tf2.ExtrapolationException as ex:
178  msg = "(current time {}) ".format(cur_time.to_sec())
179  rospy.logerr(msg + str(ex))
180  return
181 
182  # The old tf1 static_transform_publisher (which published into /tf, not /tf_static)
183  # publishes transforms 0.5 seconds into future so the cur_time and header stamp
184  # will be identical.
185  msg = "At time {}, (current time {})".format(ts.header.stamp.to_sec(), cur_time.to_sec())
186  xyz = ts.transform.translation
187  msg += "\n- Translation: [{:.{p}f}, {:.{p}f}, {:.{p}f}]\n".format(xyz.x, xyz.y, xyz.z, p=self.args.precision)
188  quat = ts.transform.rotation
189  msg += "- Rotation: in Quaternion [{:.{p}f}, {:.{p}f}, {:.{p}f}, {:.{p}f}]\n".format(quat.x, quat.y, quat.z, quat.w, p=self.args.precision)
190  # TODO(lucasw) need to get quaternion to euler from somewhere, but not tf1
191  # or a dependency that isn't in Ubuntu or ros repos
192  euler = _euler_from_quaternion_msg(quat)
193  msg += " in RPY (radian) "
194  msg += "[{:.{p}f}, {:.{p}f}, {:.{p}f}]\n".format(euler[0], euler[1], euler[2], p=self.args.precision)
195  msg += " in RPY (degree) "
196  msg += "[{:.{p}f}, {:.{p}f}, {:.{p}f}]".format(math.degrees(euler[0]),
197  math.degrees(euler[1]),
198  math.degrees(euler[2]), p=self.args.precision)
199  print(msg)
200 
202  x = float(x)
203  if x <= 0.0:
204  raise argparse.ArgumentTypeError("{} must be > 0.0".format(x))
205  return x
206 
208  x = int(x)
209  if x <= 0:
210  raise argparse.ArgumentTypeError("{} must be > 0".format(x))
211  return x
212 
213 if __name__ == '__main__':
214  rospy.init_node("echo")
215 
216  other_args = rospy.myargv(argv=sys.argv)
217  precision=3
218  try:
219  precision = rospy.get_param('~precision')
220  rospy.loginfo("Precision default value was overriden, new value: %d", precision)
221  except KeyError:
222  pass
223 
224  parser = argparse.ArgumentParser()
225  parser.add_argument("source_frame") # parent
226  parser.add_argument("target_frame") # child
227  parser.add_argument("-r", "--rate",
228  help="update rate, must be > 0.0",
229  default=1.0,
230  type=positive_float)
231  parser.add_argument("-c", "--cache_time",
232  help="length of tf buffer cache in seconds",
233  type=positive_float)
234  parser.add_argument("-o", "--offset",
235  help="offset the lookup from current time, ignored if using -t",
236  type=float)
237  parser.add_argument("-t", "--time",
238  help="fixed time to do the lookup",
239  type=float)
240  parser.add_argument("-l", "--limit",
241  help="lookup fixed number of times",
242  type=positive_int)
243  parser.add_argument("-p", "--precision",
244  help="output precision",
245  default=precision,
246  type=positive_int)
247  args = parser.parse_args(other_args[1:]) # Remove first arg
248  echo = Echo(args)
249  rospy.spin()
def _euler_from_quaternion_msg(quaternion)
Definition: echo.py:131
def positive_float(x)
Definition: echo.py:201
def _euler_from_matrix(matrix, axes='sxyz')
Definition: echo.py:73
def _quaternion_matrix(quaternion)
Definition: echo.py:113
def lookup(self, event)
Definition: echo.py:149
def positive_int(x)
Definition: echo.py:207
tf_buffer
Definition: echo.py:142
def __init__(self, args)
Definition: echo.py:141
def _euler_from_quaternion(quaternion, axes='sxyz')
Definition: echo.py:127
tf_listener
Definition: echo.py:143


tf2_tools
Author(s): Wim Meeussen
autogenerated on Mon Jun 27 2022 02:43:33