plume.py
Go to the documentation of this file.
1 # Copyright (c) 2016 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 __future__ import print_function
16 import rospy
17 import numpy as np
18 from sensor_msgs.msg import PointCloud, ChannelFloat32
19 from visualization_msgs.msg import Marker, MarkerArray
20 from geometry_msgs.msg import Point, Point32
21 from tf.transformations import quaternion_from_euler
22 
23 
24 class Plume(object):
25  """
26  Base class for plume model classes. Plume models should inherit this class
27  and implement the update function to maintain a standard interface with the
28  plume server. Each new inherited plume model must have an unique *LABEL*
29  tag, since it will be used by the factory method to create plume models
30  without the need to import all the plume model class files.
31  """
32  LABEL = ''
33 
34  def __init__(self, source_pos, n_points, start_time):
35  """Plume class constructor, abstract class for plume model classes.
36 
37  > **Parameters**
38 
39  * `source_pos` (*type:* `list`): 3D position of the plume source
40  wrt ENU frame
41  * `n_points` (*type:* `int`): maximum number of plume particles to
42  be generated
43  * `start_time (*type:* `float): time stamp of time of the creation of
44  the plume model in seconds. This will be used to compute the relative
45  time of creation of each particle
46  """
47  assert n_points > 0, 'Number of points to generate the plume must be' \
48  ' greater than zero'
49  assert len(source_pos) == 3, 'Source position must have three elements'
50  assert source_pos[2] <= 0, 'Source Z coordinate is above the water' \
51  ' surface with respect to the ENU inertial frame'
52 
53  # Vector [N x 3] holding all the position vectors for each created
54  # particle
55  self._pnts = None
56  # Time of creation vector will hold the time stamps of creation of each
57  # particle in the plume
58  self._time_creation = None
59 
60  # Limits for the bounding box where the plume particles can exist and
61  # be generated
62  self._x_lim = [-1e7, 1e7]
63  self._y_lim = [-1e7, 1e7]
64  self._z_lim = [-1e7, 0]
65 
66  # Position of the source of the plume
67  self._source_pos = source_pos
68  # Number of points in the point cloud
69  self._n_points = n_points
70  # Time stamp to be updated at each iteration
71  self._t = 0.0
72  # Time stamp for the creation of this plume model instance
73  self._start_time = start_time
74  # Time step between updates
75  self._dt = 0.0
76  # Current velocity vector to be updated by the plume server
77  self._current_vel = np.zeros(3)
78 
79  @staticmethod
80  def create_plume_model(tag, *args):
81  """Factory function to create the plume model using the LABEL
82  attribute as identifier.
83 
84  > **Parameters**
85 
86  * `tag` (*type:* `str`): label of the plume model to be created
87  * `args`: list of input arguments for the specific plume model to be created
88  """
89  for model in Plume.__subclasses__():
90  if model.LABEL == tag:
91  return model(*args)
92  return None
93 
94  @property
95  def source_pos(self):
96  """Return the position vector with the Cartesian coordinates
97  for the plume source
98  """
99  return self._source_pos
100 
101  @source_pos.setter
102  def source_pos(self, new_pos):
103  """Set new plume source position.
104 
105  > **Parameters**
106 
107  * `new_pos`(*type:* `list`): 3D coordinates of the plume
108  source wrt ENU frame
109  """
110  assert isinstance(new_pos, list)
111  assert len(new_pos) == 3
112 
113  # Reseting the plume
114  self.reset_plume()
115  self._source_pos = new_pos
116 
117  # Ensure that the new source position is inside the bounding box
118  self._source_pos[0] = max(self._source_pos[0], self._x_lim[0])
119  self._source_pos[0] = min(self._source_pos[0], self._x_lim[1])
120 
121  self._source_pos[1] = max(self._source_pos[1], self._y_lim[0])
122  self._source_pos[1] = min(self._source_pos[1], self._y_lim[1])
123 
124  self._source_pos[2] = max(self._source_pos[2], self._z_lim[0])
125  self._source_pos[2] = min(self._source_pos[2], self._z_lim[1])
126 
127  @property
128  def x(self):
129  """Return only the X coordinates for the particle positions.
130  """
131  if self._pnts is None:
132  return None
133  return self._pnts[:, 0]
134 
135  @property
136  def x_lim(self):
137  """Return the lower and upper limit for the bounding box on the X axis.
138  """
139  return self._x_lim
140 
141  @property
142  def y(self):
143  """Return only the Y coordinates for the particle positions.
144  """
145  if self._pnts is None:
146  return None
147  return self._pnts[:, 1]
148 
149  @property
150  def time_of_creation(self):
151  """Return the time of creation vector."""
152  if self._pnts is None:
153  return None
154  return self._time_creation
155 
156  @property
157  def y_lim(self):
158  """Return the lower and upper limit for the bounding box on the Y axis.
159  """
160  return self._y_lim
161 
162  @property
163  def z(self):
164  """Return only the Z coordinates for the particle positions.
165  """
166  if self._pnts is None:
167  return None
168  return self._pnts[:, 2]
169 
170  @property
171  def z_lim(self):
172  """Return the lower and upper limit for the bounding box on the Z axis.
173  """
174  return self._z_lim
175 
176  @property
177  def points(self):
178  """Return list of `[N x 3]` position vectors for particles created.
179  """
180  return self._pnts
181 
182  @property
183  def n_points(self):
184  """Return the maximum number of points to be created by the plume model.
185  """
186  return self._n_points
187 
188  @property
189  def num_particles(self):
190  """Return the current number of particles."""
191  if self._pnts is None:
192  return 0
193  else:
194  return self._pnts.shape[0]
195 
196  def set_n_points(self, n_points):
197  """Set the maximum number of points to be created by the plume model.
198 
199  > **Parameters**
200 
201  * n_points (*type: `int`): number maximum of particles (must be greater
202  than zero).
203  """
204  if n_points > 0:
205  self._n_points = n_points
206  return True
207  else:
208  print('Number of plume particle points must be positive')
209  return False
210 
211  def update_current_vel(self, vel):
212  """Update the current velocity vector.
213 
214  > **Parameters**
215 
216  * `vel` (*type:* `list` or `numpy.array`): current velocity vector
217  containing three elements $(u, v, w)$.
218  """
219  self._current_vel = vel
220 
221  def set_x_lim(self, min_value, max_value):
222  """Set the X limits for the plume bounding box. The bounding box is
223  defined with respect to the ENU inertial frame.
224 
225  > **Parameters**
226 
227  * `min_value` (*type:* `float`): lower limit for the bounding box over the X axis.
228  * `max_value` (*type:* `float`): upper limit for the bounding box over the X axis.
229 
230  > **Returns**
231 
232  `True` if limits are valid.
233  """
234  assert min_value < max_value, 'Limit interval is invalid'
235 
236  if self._source_pos[0] >= self._x_lim[0] and self._source_pos[0] <= self._x_lim[1]:
237  self._x_lim[0] = min_value
238  self._x_lim[1] = max_value
239  return True
240  else:
241  print('Plume source is outside of limits, ignoring new X limits')
242  return False
243 
244  def set_y_lim(self, min_value, max_value):
245  """Set the Y limits for the plume bounding box. The bounding box is
246  defined with respect to the ENU inertial frame.
247 
248  > **Parameters**
249 
250  * `min_value` (*type:* `float`): lower limit for the bounding box over the Y axis.
251  * `max_value` (*type:* `float`): upper limit for the bounding box over the Y axis.
252 
253  > **Returns**
254 
255  `True` if limits are valid.
256  """
257  assert min_value < max_value, 'Limit interval is invalid'
258 
259  if self._source_pos[1] >= self._y_lim[0] and self._source_pos[1] <= self._y_lim[1]:
260  self._y_lim[0] = min_value
261  self._y_lim[1] = max_value
262  return True
263  else:
264  print('Plume source is outside of limits, ignoring new Y limits')
265  return False
266 
267  def set_z_lim(self, min_value, max_value):
268  """Set the Z limits for the plume bounding box. The bounding box is
269  defined with respect to the ENU inertial frame.
270 
271  > **Parameters**
272 
273  * `min_value` (*type:* `float`): lower limit for the bounding box over the Z axis.
274  * `max_value` (*type:* `float`): upper limit for the bounding box over the Z axis.
275 
276  > **Returns**
277 
278  `True` if limits are valid.
279  """
280  assert min_value < 0, 'Minimum value must be lower than zero'
281  assert min_value < max_value, 'Limit interval is invalid'
282 
283  if self._source_pos[2] >= self._z_lim[0] and \
284  self._source_pos[2] <= self._z_lim[1]:
285  self._z_lim[0] = min_value
286  self._z_lim[1] = min(0, max_value)
287  return True
288  else:
289  print('Plume source is outside of limits, ignoring new Z limits')
290  return False
291 
292  def reset_plume(self):
293  """Reset point cloud and time of creating vectors."""
294  self._pnts = None
295  self._time_creation = None
296 
298  """Return a binary vector of $N$ elements, $N$ being current number of
299  particles created. The $i$-th element is set to False if the $i$-th
300  particle finds itself outside of the bounding box limits.
301 
302  > **Returns**
303 
304  Logical vector with elements set to *True* if they fulfill the constraints.
305  """
306  particle_filter = self._pnts[:, 0].flatten() >= self._x_lim[0]
307  particle_filter = np.logical_and(
308  particle_filter,
309  self._pnts[:, 0].flatten() <= self._x_lim[1])
310 
311  particle_filter = np.logical_and(
312  particle_filter,
313  self._pnts[:, 1].flatten() >= self._y_lim[0])
314  particle_filter = np.logical_and(
315  particle_filter,
316  self._pnts[:, 1].flatten() <= self._y_lim[1])
317 
318  particle_filter = np.logical_and(
319  particle_filter,
320  self._pnts[:, 2].flatten() >= self._z_lim[0])
321 
322  if self._z_lim[1] < 0:
323  particle_filter = np.logical_and(
324  particle_filter,
325  self._pnts[:, 2].flatten() <= self._z_lim[1])
326  return particle_filter
327 
328  def apply_constraints(self):
329  """Truncate the position of the particle to the closest bounding box limit
330  if the particle is positioned outside of the limits.
331  """
332  assert self._pnts is not None, 'Plume points have not been initialized'
333 
334  self._pnts[:, 0] = np.maximum(self._x_lim[0], self._pnts[:, 0])
335  self._pnts[:, 0] = np.minimum(self._x_lim[1], self._pnts[:, 0])
336 
337  self._pnts[:, 1] = np.maximum(self._y_lim[0], self._pnts[:, 1])
338  self._pnts[:, 1] = np.minimum(self._y_lim[1], self._pnts[:, 1])
339 
340  self._pnts[:, 2] = np.maximum(self._z_lim[0], self._pnts[:, 2])
341  self._pnts[:, 2] = np.minimum(self._z_lim[1], self._pnts[:, 2])
342 
343  def set_plume_particles(self, t, x, y, z, time_creation):
344  """Set the plume particles with the input coordinates wrt ENU frame
345  and time of creation vector in seconds.
346 
347  > **Parameters**
348 
349  * `t` (*type:* `float`): Current time stamp in seconds
350  * `x` (*type:* `list`): List of X coordinates for the plume particles' positions
351  * `y` (*type:* `list`): List of Y coordinates for the plume particles' positions
352  * `z` (*type:* `list`): List of Z coordinates for the plume particles' positions
353  * `time_creation`(*type:* `list`): List of the relative time of creation for
354  each particle in seconds
355  """
356  self._pnts = np.zeros(shape=(len(x), 3))
357  self._time_creation = np.zeros(len(time_creation))
358 
359  self._time_creation = np.array(time_creation)
360  self._time_creation -= np.max(time_creation)
361  self._time_creation += t
362 
363  self._pnts[:, 0] = np.array(x)
364  self._pnts[:, 1] = np.array(y)
365  self._pnts[:, 2] = np.array(z)
366 
368  """Return a ROS point cloud sensor message with the points representing
369  the plume's particles and one channel containing the time of creation
370  for each particle.
371 
372  > **Returns**
373 
374  The plume particles as a `sensor_msgs/PointCloud` message or `None`
375  if no particles are found.
376  """
377  if self._pnts is None or self._time_creation is None:
378  return None
379 
380  pc = PointCloud()
381  pc.header.stamp = rospy.Time.now()
382  pc.header.frame_id = 'world'
383  if self._pnts is None:
384  return None
385  pc.points = [Point32(x, y, z) for x, y, z in zip(self.x, self.y, self.z)]
386 
387  channel = ChannelFloat32()
388  channel.name = 'time_creation'
389  if self._time_creation is None:
390  return None
391  channel.values = self._time_creation.tolist()
392 
393  pc.channels.append(channel)
394  return pc
395 
396  def get_markers(self):
397  """Return a ROS marker array message structure with an sphere marker to
398  represent the plume source, the bounding box and an arrow marker to
399  show the direction of the current velocity if it's norm is greater
400  than zero.
401 
402  > **Returns**
403 
404  `visualizaton_msgs/MarkerArray` message with markers for the current
405  velocity arrow and source position or `None` if no plume particles are
406  found.
407  """
408  if self._pnts is None:
409  return None
410 
411  marker_array = MarkerArray()
412 
413  # Generate marker for the source
414  source_marker = Marker()
415  source_marker.header.stamp = rospy.Time.now()
416  source_marker.header.frame_id = 'world'
417 
418  source_marker.ns = 'plume'
419  source_marker.id = 0
420  source_marker.type = Marker.SPHERE
421  source_marker.action = Marker.ADD
422  source_marker.color.r = 0.35
423  source_marker.color.g = 0.45
424  source_marker.color.b = 0.89
425  source_marker.color.a = 1.0
426 
427  source_marker.scale.x = 1.0
428  source_marker.scale.y = 1.0
429  source_marker.scale.z = 1.0
430  source_marker.pose.position.x = self._source_pos[0]
431  source_marker.pose.position.y = self._source_pos[1]
432  source_marker.pose.position.z = self._source_pos[2]
433 
434  source_marker.pose.orientation.x = 0
435  source_marker.pose.orientation.y = 0
436  source_marker.pose.orientation.z = 0
437  source_marker.pose.orientation.w = 1
438 
439  marker_array.markers.append(source_marker)
440 
441  # Creating the marker for the bounding box limits where the plume
442  # particles are generated
443  limits_marker = Marker()
444  limits_marker.header.stamp = rospy.Time.now()
445  limits_marker.header.frame_id = 'world'
446 
447  limits_marker.ns = 'plume'
448  limits_marker.id = 1
449  limits_marker.type = Marker.CUBE
450  limits_marker.action = Marker.ADD
451 
452  limits_marker.color.r = 0.1
453  limits_marker.color.g = 0.1
454  limits_marker.color.b = 0.1
455  limits_marker.color.a = 0.3
456 
457  limits_marker.scale.x = self._x_lim[1] - self._x_lim[0]
458  limits_marker.scale.y = self._y_lim[1] - self._y_lim[0]
459  limits_marker.scale.z = self._z_lim[1] - self._z_lim[0]
460 
461  limits_marker.pose.position.x = self._x_lim[0] + (self._x_lim[1] - self._x_lim[0]) / 2
462  limits_marker.pose.position.y = self._y_lim[0] + (self._y_lim[1] - self._y_lim[0]) / 2
463  limits_marker.pose.position.z = self._z_lim[0] + (self._z_lim[1] - self._z_lim[0]) / 2
464 
465  limits_marker.pose.orientation.x = 0
466  limits_marker.pose.orientation.y = 0
467  limits_marker.pose.orientation.z = 0
468  limits_marker.pose.orientation.w = 0
469  marker_array.markers.append(limits_marker)
470 
471  # Creating marker for the current velocity vector
472  cur_vel_marker = Marker()
473  cur_vel_marker.header.stamp = rospy.Time.now()
474  cur_vel_marker.header.frame_id = 'world'
475 
476  cur_vel_marker.id = 1
477  cur_vel_marker.type = Marker.ARROW
478 
479  if np.linalg.norm(self._current_vel) > 0:
480  cur_vel_marker.action = Marker.ADD
481  # Calculating the pitch and yaw angles for the current velocity
482  # vector
483  yaw = np.arctan2(self._current_vel[1], self._current_vel[0])
484  pitch = np.arctan2(self._current_vel[2],
485  np.sqrt(self._current_vel[0]**2 + self._current_vel[1]**2))
486  qt = quaternion_from_euler(0, -pitch, yaw)
487 
488  cur_vel_marker.pose.position.x = self._source_pos[0]
489  cur_vel_marker.pose.position.y = self._source_pos[1]
490  cur_vel_marker.pose.position.z = self._source_pos[2] - 0.8
491 
492  cur_vel_marker.pose.orientation.x = qt[0]
493  cur_vel_marker.pose.orientation.y = qt[1]
494  cur_vel_marker.pose.orientation.z = qt[2]
495  cur_vel_marker.pose.orientation.w = qt[3]
496 
497  cur_vel_marker.scale.x = 1.0
498  cur_vel_marker.scale.y = 0.1
499  cur_vel_marker.scale.z = 0.1
500 
501  cur_vel_marker.color.a = 1.0
502  cur_vel_marker.color.r = 0.0
503  cur_vel_marker.color.g = 0.0
504  cur_vel_marker.color.b = 1.0
505  else:
506  cur_vel_marker.action = Marker.DELETE
507 
508  marker_array.markers.append(cur_vel_marker)
509  return marker_array
510 
511  def update(self, t=0.0):
512  """Plume dynamics update function. It must be implemented by the child
513  class and will be used by the plume server to update the position of
514  the plume particles at each iteration.
515  """
516  raise NotImplementedError()
def set_n_points(self, n_points)
Definition: plume.py:196
def apply_constraints(self)
Definition: plume.py:328
def set_z_lim(self, min_value, max_value)
Definition: plume.py:267
def update(self, t=0.0)
Definition: plume.py:511
def set_plume_particles(self, t, x, y, z, time_creation)
Definition: plume.py:343
def time_of_creation(self)
Definition: plume.py:150
def update_current_vel(self, vel)
Definition: plume.py:211
def __init__(self, source_pos, n_points, start_time)
Definition: plume.py:34
def get_point_cloud_as_msg(self)
Definition: plume.py:367
def set_x_lim(self, min_value, max_value)
Definition: plume.py:221
def get_contraints_filter(self)
Definition: plume.py:297
def set_y_lim(self, min_value, max_value)
Definition: plume.py:244
def create_plume_model(tag, args)
Definition: plume.py:80


uuv_plume_simulator
Author(s): Musa Morena Marcusso Manhaes
autogenerated on Mon Jun 10 2019 15:41:28