15 from __future__
import print_function
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
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. 34 def __init__(self, source_pos, n_points, start_time):
35 """Plume class constructor, abstract class for plume model classes. 39 * `source_pos` (*type:* `list`): 3D position of the plume source 41 * `n_points` (*type:* `int`): maximum number of plume particles to 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 47 assert n_points > 0,
'Number of points to generate the plume must be' \
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' 81 """Factory function to create the plume model using the LABEL 82 attribute as identifier. 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 89 for model
in Plume.__subclasses__():
90 if model.LABEL == tag:
96 """Return the position vector with the Cartesian coordinates 103 """Set new plume source position. 107 * `new_pos`(*type:* `list`): 3D coordinates of the plume 110 assert isinstance(new_pos, list)
111 assert len(new_pos) == 3
129 """Return only the X coordinates for the particle positions. 131 if self.
_pnts is None:
133 return self.
_pnts[:, 0]
137 """Return the lower and upper limit for the bounding box on the X axis. 143 """Return only the Y coordinates for the particle positions. 145 if self.
_pnts is None:
147 return self.
_pnts[:, 1]
151 """Return the time of creation vector.""" 152 if self.
_pnts is None:
158 """Return the lower and upper limit for the bounding box on the Y axis. 164 """Return only the Z coordinates for the particle positions. 166 if self.
_pnts is None:
168 return self.
_pnts[:, 2]
172 """Return the lower and upper limit for the bounding box on the Z axis. 178 """Return list of `[N x 3]` position vectors for particles created. 184 """Return the maximum number of points to be created by the plume model. 190 """Return the current number of particles.""" 191 if self.
_pnts is None:
194 return self._pnts.shape[0]
197 """Set the maximum number of points to be created by the plume model. 201 * n_points (*type: `int`): number maximum of particles (must be greater 208 print(
'Number of plume particle points must be positive')
212 """Update the current velocity vector. 216 * `vel` (*type:* `list` or `numpy.array`): current velocity vector 217 containing three elements $(u, v, w)$. 222 """Set the X limits for the plume bounding box. The bounding box is 223 defined with respect to the ENU inertial frame. 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. 232 `True` if limits are valid. 234 assert min_value < max_value,
'Limit interval is invalid' 237 self.
_x_lim[0] = min_value
238 self.
_x_lim[1] = max_value
241 print(
'Plume source is outside of limits, ignoring new X limits')
245 """Set the Y limits for the plume bounding box. The bounding box is 246 defined with respect to the ENU inertial frame. 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. 255 `True` if limits are valid. 257 assert min_value < max_value,
'Limit interval is invalid' 260 self.
_y_lim[0] = min_value
261 self.
_y_lim[1] = max_value
264 print(
'Plume source is outside of limits, ignoring new Y limits')
268 """Set the Z limits for the plume bounding box. The bounding box is 269 defined with respect to the ENU inertial frame. 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. 278 `True` if limits are valid. 280 assert min_value < 0,
'Minimum value must be lower than zero' 281 assert min_value < max_value,
'Limit interval is invalid' 285 self.
_z_lim[0] = min_value
286 self.
_z_lim[1] = min(0, max_value)
289 print(
'Plume source is outside of limits, ignoring new Z limits')
293 """Reset point cloud and time of creating vectors.""" 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. 304 Logical vector with elements set to *True* if they fulfill the constraints. 306 particle_filter = self.
_pnts[:, 0].flatten() >= self.
_x_lim[0]
307 particle_filter = np.logical_and(
311 particle_filter = np.logical_and(
314 particle_filter = np.logical_and(
318 particle_filter = np.logical_and(
323 particle_filter = np.logical_and(
326 return particle_filter
329 """Truncate the position of the particle to the closest bounding box limit 330 if the particle is positioned outside of the limits. 332 assert self.
_pnts is not None,
'Plume points have not been initialized' 344 """Set the plume particles with the input coordinates wrt ENU frame 345 and time of creation vector in seconds. 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 356 self.
_pnts = np.zeros(shape=(len(x), 3))
363 self.
_pnts[:, 0] = np.array(x)
364 self.
_pnts[:, 1] = np.array(y)
365 self.
_pnts[:, 2] = np.array(z)
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 374 The plume particles as a `sensor_msgs/PointCloud` message or `None` 375 if no particles are found. 381 pc.header.stamp = rospy.Time.now()
382 pc.header.frame_id =
'world' 383 if self.
_pnts is None:
385 pc.points = [Point32(x, y, z)
for x, y, z
in zip(self.
x, self.
y, self.
z)]
387 channel = ChannelFloat32()
388 channel.name =
'time_creation' 391 channel.values = self._time_creation.tolist()
393 pc.channels.append(channel)
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 404 `visualizaton_msgs/MarkerArray` message with markers for the current 405 velocity arrow and source position or `None` if no plume particles are 408 if self.
_pnts is None:
411 marker_array = MarkerArray()
414 source_marker = Marker()
415 source_marker.header.stamp = rospy.Time.now()
416 source_marker.header.frame_id =
'world' 418 source_marker.ns =
'plume' 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
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]
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
439 marker_array.markers.append(source_marker)
443 limits_marker = Marker()
444 limits_marker.header.stamp = rospy.Time.now()
445 limits_marker.header.frame_id =
'world' 447 limits_marker.ns =
'plume' 449 limits_marker.type = Marker.CUBE
450 limits_marker.action = Marker.ADD
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
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
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)
472 cur_vel_marker = Marker()
473 cur_vel_marker.header.stamp = rospy.Time.now()
474 cur_vel_marker.header.frame_id =
'world' 476 cur_vel_marker.id = 1
477 cur_vel_marker.type = Marker.ARROW
480 cur_vel_marker.action = Marker.ADD
486 qt = quaternion_from_euler(0, -pitch, yaw)
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
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]
497 cur_vel_marker.scale.x = 1.0
498 cur_vel_marker.scale.y = 0.1
499 cur_vel_marker.scale.z = 0.1
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
506 cur_vel_marker.action = Marker.DELETE
508 marker_array.markers.append(cur_vel_marker)
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. 516 raise NotImplementedError()
def set_n_points(self, n_points)
def apply_constraints(self)
def set_z_lim(self, min_value, max_value)
def set_plume_particles(self, t, x, y, z, time_creation)
def time_of_creation(self)
def update_current_vel(self, vel)
def __init__(self, source_pos, n_points, start_time)
def get_point_cloud_as_msg(self)
def set_x_lim(self, min_value, max_value)
def get_contraints_filter(self)
def set_y_lim(self, min_value, max_value)
def create_plume_model(tag, args)