wp_trajectory_generator.py
Go to the documentation of this file.
1 # Copyright (c) 2016-2019 The UUV Simulator Authors.
2 # All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 from rospy import get_namespace
16 import numpy as np
17 from copy import deepcopy
18 import logging
19 import sys
20 import time
21 
22 from .trajectory_point import TrajectoryPoint
23 from uuv_waypoints import Waypoint, WaypointSet
24 from tf_quaternion.transformations import quaternion_multiply, quaternion_inverse, \
25  quaternion_conjugate, quaternion_about_axis
26 
27 from .path_generator import PathGenerator
28 
29 
30 class WPTrajectoryGenerator(object):
31  """Class that generates a trajectory from the interpolated path generated
32  from a set of waypoints. It uses the information given for the waypoint's
33  maximum forward speed to estimate the velocity between waypoint and
34  parametrize the interpolated curve.
35  The velocity and acceleration profiles are the generated through finite
36  discretization. These profiles are not optimized, this class is a
37  simple solution for quick trajectory generation for waypoint navigation.
38 
39  > *Input arguments*
40 
41  * `full_dof` (*type:* `bool`, *default:* `False`): `True` to generate 6 DoF
42  trajectories
43  * `use_finite_diff` (*type:* `bool`, *default:* `True`): Use finite differentiation
44  if `True`, otherwise use the motion regression algorithm
45  * `interpolation_method` (*type:* `str`, *default:* `cubic`): Name of the interpolation
46  method, options are `cubic`, `dubins`, `lipb` or `linear`
47  * `stamped_pose_only` (*type:* `bool`, *default:* `False`): Generate only position
48  and quaternion vectors, velocities and accelerations are set to zero
49  """
50  def __init__(self, full_dof=False, use_finite_diff=True,
51  interpolation_method='cubic',
52  stamped_pose_only=False):
53  """Class constructor."""
54  self._logger = logging.getLogger('wp_trajectory_generator')
55  out_hdlr = logging.StreamHandler(sys.stdout)
56  out_hdlr.setFormatter(logging.Formatter(
57  get_namespace().replace('/', '').upper() + ' -- %(asctime)s | %(levelname)s | %(module)s | %(message)s'))
58  out_hdlr.setLevel(logging.INFO)
59  self._logger.addHandler(out_hdlr)
60  self._logger.setLevel(logging.INFO)
61 
62  self._path_generators = dict()
63  self._logger.info('Waypoint interpolators available:')
64  for gen in PathGenerator.get_all_generators():
65  self._logger.info('\t - ' + gen.get_label())
66  self._path_generators[gen.get_label()] = gen
67  self._path_generators[gen.get_label()].set_full_dof(full_dof)
68  # Time step between interpolated samples
69  self._dt = None
70  # Last time stamp
71  self._last_t = None
72  # Last interpolated point
73  self._last_pnt = None
74  self._this_pnt = None
75 
76  # Flag to generate only stamped pose, no velocity profiles
77  self._stamped_pose_only = stamped_pose_only
78 
79  self._t_step = 0.001
80 
81  # Interpolation method
82  self._interp_method = interpolation_method
83 
84  # True if the path is generated for all degrees of freedom, otherwise
85  # the path will be generated for (x, y, z, yaw) only
86  self._is_full_dof = full_dof
87 
88  # Use finite differentiation if true, otherwise use motion regression
89  # algorithm
90  self._use_finite_diff = use_finite_diff
91  # Time window used for the regression method
92  self._regression_window = 0.5
93  # If the regression method is used, adjust the time step
94  if not self._use_finite_diff:
95  self._t_step = self._regression_window / 30
96 
97  # Flags to indicate that the interpolation process has started and
98  # ended
99  self._has_started = False
100  self._has_ended = False
101 
102  # The parametric variable to use as input for the interpolator
103  self._cur_s = 0
104 
105  self._init_rot = quaternion_about_axis(0.0, [0, 0, 1])
106 
107  def __del__(self):
108  # Removing logging message handlers
109  while self._logger.handlers:
110  self._logger.handlers.pop()
111 
112  @property
113  def started(self):
114  """`bool`: Flag set to true if the interpolation has started."""
115  return self._has_started
116 
117  @property
118  def closest_waypoint(self):
119  """Return the closest waypoint to the current position on the path."""
120  return self._path_generators[self._interp_method].closest_waypoint
121 
122  @property
124  """`int`: Index of the closest waypoint to the current position on the
125  path.
126  """
127  return self._path_generators[self._interp_method].closest_waypoint_idx
128 
129  @property
130  def interpolator(self):
131  """`str`: Name of the interpolation method"""
132  return self._path_generators[self._interp_method]
133 
134  @property
135  def interpolator_tags(self):
136  """List of `str`: List of all interpolation method"""
137  return [gen.get_label() for gen in PathGenerator.get_all_generators()]
138 
139  @property
140  def use_finite_diff(self):
141  """`bool`: Use finite differentiation for computation of
142  trajectory points
143  """
144  return self._use_finite_diff
145 
146  @use_finite_diff.setter
147  def use_finite_diff(self, flag):
148  assert type(flag) == bool
149  self._use_finite_diff = flag
150 
151  @property
152  def stamped_pose_only(self):
153  """`bool`: Flag to enable computation of stamped poses"""
154  return self._stamped_pose_only
155 
156  @stamped_pose_only.setter
157  def stamped_pose_only(self, flag):
158  self._stamped_pose_only = flag
159 
161  return self._interp_method
162 
164  return self.interpolator.get_visual_markers()
165 
166  def set_interpolation_method(self, method):
167  if method in self._path_generators:
168  self._interp_method = method
169  self._logger.info('Interpolation method set: ' + str(method))
170  return True
171  else:
172  self._logger.info('Invalid interpolation method, keeping the current method <%s>' % self._interp_method)
173  return False
174 
175  def set_interpolator_parameters(self, method, params):
176  if method not in self.interpolator_tags:
177  self._logger.error('Invalid interpolation method: ' + str(method))
178  return False
179  return self._path_generators[method].set_parameters(params)
180 
181  def is_full_dof(self):
182  """Return true if the trajectory is generated for all 6 degrees of
183  freedom.
184  """
185  return self._is_full_dof
186 
187  def get_max_time(self):
188  """Return maximum trajectory time."""
189  return self.interpolator.max_time
190 
191  def set_duration(self, t):
192  """Set a new maximum trajectory time."""
193  if t > 0:
194  self.interpolator.duration = t
195  self.interpolator.s_step = self._t_step / self.interpolator.duration
196  self._logger.info('New duration, max. relative time=%.2f s' % self.interpolator.duration)
197  return True
198  else:
199  self._logger.info('Invalid max. time, time=%.2f s' % t)
200  return False
201 
202  def is_finished(self):
203  """Return true if the trajectory has finished."""
204  return self._has_ended
205 
206  def reset(self):
207  """Reset all class attributes to allow a new trajectory to be
208  computed.
209  """
210  self._dt = None
211  self._last_t = None
212  self._last_pnt = None
213  self._this_pnt = None
214  self._has_started = False
215  self._has_ended = False
216  self._cur_s = 0
217 
218  def init_waypoints(self, waypoint_set, init_rot=(0, 0, 0, 1)):
219  """Initialize the waypoint set."""
220  self.reset()
221  self.interpolator.reset()
222  return self.interpolator.init_waypoints(waypoint_set, init_rot)
223 
224  def add_waypoint(self, waypoint, add_to_beginning=False):
225  """Add waypoint to the existing waypoint set. If no waypoint set has
226  been initialized, create new waypoint set structure and add the given
227  waypoint."""
228  return self.interpolator.add_waypoint(waypoint, add_to_beginning)
229 
230  def get_waypoints(self):
231  """Return waypoint set."""
232  return self.interpolator.waypoints
233 
234  def update_dt(self, t):
235  """Update the time stamp."""
236  if self._last_t is None:
237  self._last_t = t
238  self._dt = 0.0
239  if self.interpolator.start_time is None:
240  self.interpolator.start_time = t
241  return False
242  self._dt = t - self._last_t
243  self._last_t = t
244  return (True if self._dt > 0 else False)
245 
246  def get_samples(self, step=0.005):
247  """Return pose samples from the interpolated path."""
248  assert step > 0, 'Step size must be positive'
249  return self.interpolator.get_samples(0.0, step)
250 
251  def set_start_time(self, t):
252  """Set a custom starting time to the interpolated trajectory."""
253  assert t >= 0, 'Starting time must be positive'
254  self.interpolator.start_time = t
255  self._logger.info('Setting new starting time, t=%.2f s' % t)
256 
257  def _motion_regression_1d(self, pnts, t):
258  """
259  Computation of the velocity and acceleration for the target time t
260  using a sequence of points with time stamps for one dimension. This
261  is an implementation of the algorithm presented by [1].
262 
263  !!! note
264 
265  [1] Sittel, Florian, Joerg Mueller, and Wolfram Burgard. Computing
266  velocities and accelerations from a pose time sequence in
267  three-dimensional space. Technical Report 272, University of
268  Freiburg, Department of Computer Science, 2013.
269  """
270 
271  sx = 0.0
272  stx = 0.0
273  st2x = 0.0
274  st = 0.0
275  st2 = 0.0
276  st3 = 0.0
277  st4 = 0.0
278  for pnt in pnts:
279  ti = pnt[1] - t
280  sx += pnt[0]
281  stx += pnt[0] * ti
282  st2x += pnt[0] * ti**2
283  st += ti
284  st2 += ti**2
285  st3 += ti**3
286  st4 += ti**4
287 
288  n = len(pnts)
289  A = n * (st3 * st3 - st2 * st4) + \
290  st * (st * st4 - st2 * st3) + \
291  st2 * (st2 * st2 - st * st3)
292 
293  if A == 0.0:
294  return 0.0, 0.0
295 
296  v = (1.0 / A) * (sx * (st * st4 - st2 * st3) +
297  stx * (st2 * st2 - n * st4) +
298  st2x * (n * st3 - st * st2))
299 
300  a = (2.0 / A) * (sx * (st2 * st2 - st * st3) +
301  stx * (n * st3 - st * st2) +
302  st2x * (st * st - n * st2))
303  return v, a
304 
305  def _motion_regression_6d(self, pnts, qt, t):
306  """
307  Compute translational and rotational velocities and accelerations in
308  the inertial frame at the target time t.
309 
310  !!! note
311 
312  [1] Sittel, Florian, Joerg Mueller, and Wolfram Burgard. Computing
313  velocities and accelerations from a pose time sequence in
314  three-dimensional space. Technical Report 272, University of
315  Freiburg, Department of Computer Science, 2013.
316  """
317 
318  lin_vel = np.zeros(3)
319  lin_acc = np.zeros(3)
320 
321  q_d = np.zeros(4)
322  q_dd = np.zeros(4)
323 
324  for i in range(3):
325  v, a = self._motion_regression_1d(
326  [(pnt['pos'][i], pnt['t']) for pnt in pnts], t)
327  lin_vel[i] = v
328  lin_acc[i] = a
329 
330  for i in range(4):
331  v, a = self._motion_regression_1d(
332  [(pnt['rot'][i], pnt['t']) for pnt in pnts], t)
333  q_d[i] = v
334  q_dd[i] = a
335 
336  # Keeping all velocities and accelerations in the inertial frame
337  ang_vel = 2 * quaternion_multiply(q_d, quaternion_conjugate(qt))
338  ang_acc = 2 * quaternion_multiply(q_dd, quaternion_conjugate(qt))
339 
340  return np.hstack((lin_vel, ang_vel[0:3])), np.hstack((lin_acc, ang_acc[0:3]))
341 
342  def generate_pnt(self, t, pos, rot):
343  """Return trajectory sample for the current parameter s."""
344  cur_s = (t - self.interpolator.start_time) / (self.interpolator.max_time - self.interpolator.start_time)
345  last_s = cur_s - self.interpolator.s_step
346  # Generate position and rotation quaternion for the current path
347  # generator method
348  pnt = self.interpolator.generate_pnt(
349  cur_s,
350  cur_s * (self.interpolator.max_time - self.interpolator.start_time) + self.interpolator.start_time,
351  pos,
352  rot)
353 
354  if self.get_interpolation_method() is not 'los':
355  if self._use_finite_diff:
356  # Set linear velocity
357  pnt.vel = self._generate_vel(cur_s)
358  # Compute linear and angular accelerations
359  last_vel = self._generate_vel(last_s)
360  pnt.acc = (pnt.vel - last_vel) / self._t_step
361  else:
362  pnts = list()
363  for ti in np.arange(pnt.t - self._regression_window / 2, pnt.t + self._regression_window, self._t_step):
364  if ti < 0:
365  si = 0
366  elif ti > self.interpolator.max_time - self.interpolator.start_time:
367  si = 1
368  else:
369  si = (ti - self.interpolator.start_time) / self.interpolator.max_time
370  pnts.append(dict(pos=self.interpolator.generate_pos(si),
371  rot=self.interpolator.generate_quat(si),
372  t=ti))
373  if not self._stamped_pose_only:
374  vel, acc = self._motion_regression_6d(pnts, pnt.rotq, pnt.t)
375  pnt.vel = vel
376  pnt.acc = acc
377  else:
378  pnt.vel = np.zeros(6)
379  pnt.acc = np.zeros(6)
380  else:
381  pnt.vel = np.zeros(6)
382  pnt.acc = np.zeros(6)
383  return pnt
384 
385  def _generate_vel(self, s=None):
386  if self._stamped_pose_only:
387  return np.zeros(6)
388  cur_s = (self._cur_s if s is None else s)
389  last_s = cur_s - self.interpolator.s_step
390 
391  if last_s < 0 or cur_s > 1:
392  return np.zeros(6)
393 
394  q_cur = self.interpolator.generate_quat(cur_s)
395  q_last = self.interpolator.generate_quat(last_s)
396 
397  cur_pos = self.interpolator.generate_pos(cur_s)
398  last_pos = self.interpolator.generate_pos(last_s)
399 
400  ########################################################
401  # Computing angular velocities
402  ########################################################
403  # Quaternion difference to the last step in the inertial frame
404  q_diff = quaternion_multiply(q_cur, quaternion_inverse(q_last))
405  # Angular velocity
406  ang_vel = 2 * q_diff[0:3] / self._t_step
407 
408  vel = [(cur_pos[0] - last_pos[0]) / self._t_step,
409  (cur_pos[1] - last_pos[1]) / self._t_step,
410  (cur_pos[2] - last_pos[2]) / self._t_step,
411  ang_vel[0],
412  ang_vel[1],
413  ang_vel[2]]
414  return np.array(vel)
415 
416  def generate_reference(self, t, *args):
417  t = max(t, self.interpolator.start_time)
418  t = min(t, self.interpolator.max_time)
419  pnt = self.generate_pnt(t, *args)
420  pnt.t = t
421  return pnt
422 
423  def interpolate(self, t, *args):
424  if not self._has_started:
425  tic = time.time()
426  if not self.interpolator.init_interpolator():
427  self._logger.error('Error initializing the waypoint interpolator')
428  return None
429  if self.interpolator.start_time is None:
430  self.set_start_time(t + (time.time() - tic))
431  self.interpolator.s_step = self._t_step / (self.interpolator.max_time - self.interpolator.start_time)
432  self.update_dt(t)
433  # Generate first point
434  self._cur_s = 0
435  self._has_started = True
436  self._has_ended = False
437  self._logger.info('Waypoint interpolator initialized! Start time: %.2f s' % self.interpolator.start_time)
438 
439  if self.interpolator.is_finished(t) or not self.interpolator.has_started(t):
440  if self.interpolator.is_finished(t):
441  self._has_ended = True
442  self._cur_s = 1
443  else:
444  self._this_pnt = self.generate_pnt(0, *args)
445  self._this_pnt.vel = np.zeros(6)
446  self._this_pnt.acc = np.zeros(6)
447  self._this_pnt.t = t
448  else:
449  self._has_started = True
450  self._has_ended = False
451 
452  # Retrieving current position and heading
453  self._cur_s = (t - self.interpolator.start_time) / (self.interpolator.max_time - self.interpolator.start_time)
454  self._this_pnt = self.generate_pnt(t, *args)
455  self._this_pnt.t = t
456 
457  self._last_pnt = deepcopy(self._this_pnt)
458  return self._this_pnt
def __init__(self, full_dof=False, use_finite_diff=True, interpolation_method='cubic', stamped_pose_only=False)


uuv_trajectory_control
Author(s):
autogenerated on Thu Jun 18 2020 03:28:42