00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 import roslib
00034 roslib.load_manifest('starmac_roslib')
00035 from visualization_msgs.msg import Marker, MarkerArray
00036 from geometry_msgs.msg import Point, Pose
00037 from std_msgs.msg import ColorRGBA
00038 import rospy
00039 import tf.transformations as tft
00040 from math import radians, degrees, sin, cos
00041 import numpy as np
00042 from numpy.linalg import norm
00043
00044 from colors import Alpha, Colors, Alphas, OpaqueColors, set_color
00045
00046 default_frame_id = "/ned"
00047
00048 def stamp_now(msg, time=None):
00049 if time is None:
00050 msg.header.stamp = rospy.Time.now()
00051 else:
00052 msg.header.stamp = time
00053 return msg
00054
00055 class MarkerManager(object):
00056 """
00057 Class to manage marker id's
00058 """
00059 _id_counters = {}
00060
00061 def get_ids(self, namespace, num_ids=1):
00062 return [self.get_id(namespace) for i in range(num_ids)]
00063
00064 def get_id(self, namespace):
00065 if namespace in self._id_counters:
00066 self._id_counters[namespace] += 1
00067 else:
00068 self._id_counters[namespace] = 0
00069 return self._id_counters[namespace]
00070
00071 class MarkerBase(object):
00072 """
00073 Base class for various marker classes
00074 """
00075
00076 _msg = None
00077
00078 _ns = ""
00079 _ids = []
00080 _frame_id = ""
00081
00082 _visible = True
00083 _marker_manager = MarkerManager()
00084 _pub_marker = False
00085
00086 def __init__(self, namespace="", num_ids=1, frame_id=None):
00087 self._ns = namespace
00088 self._ids = self._marker_manager.get_ids(self._ns, num_ids)
00089 if frame_id is not None:
00090 self._frame_id = frame_id
00091
00092 def publish(self):
00093 if self._pub_marker:
00094 self._publisher.publish(self._marker_msg)
00095
00096 def _default_marker(self, index=0):
00097 m = Marker()
00098 m.ns = self._ns
00099 m.id = self._ids[index]
00100 m.header.frame_id = self._frame_id
00101 m.action = Marker.ADD
00102 m.frame_locked = True
00103 return m
00104
00105 def get_msgs(self):
00106 raise NotImplementedError('get_msg must be implemented by derived class')
00107
00108 class MarkerGroup(object):
00109
00110 def __init__(self, publisher):
00111 self._publisher = publisher
00112 self._markers = set()
00113
00114 def add(self, *markers):
00115 for marker in markers:
00116 if not isinstance(marker, MarkerBase):
00117 raise TypeError('Can only add instances of ' + str(MarkerBase))
00118 self._markers.add(marker)
00119
00120 def publish(self, time=None):
00121 marker_msgs = []
00122 for marker in self._markers:
00123 temp = marker.get_msgs()
00124 if temp is not None:
00125 thismarker_msgs = [stamp_now(m,time) for m in temp]
00126 marker_msgs.extend(thismarker_msgs)
00127 marker_array = MarkerArray(markers=marker_msgs)
00128 self._publisher.publish(marker_array)
00129
00130 class VerticalLineMarker(MarkerBase):
00131 def __init__(self, namespace, xypos, zstart=0, zend=1, frame_id=None, **kwargs):
00132 MarkerBase.__init__(self, namespace)
00133 self._xypos = xypos
00134 self._zstart = zstart
00135 self._zend = zend
00136 self._kwargs = kwargs
00137 if frame_id is None:
00138 self._frame_id = default_frame_id
00139 else:
00140 self._frame_id = frame_id
00141
00142 def get_msgs(self):
00143 if self._msg is not None:
00144 return self._msg
00145 else:
00146 m = self._default_marker()
00147 m.type = Marker.LINE_STRIP
00148 m.scale.x = self._kwargs.get('width', 0.01)
00149 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00150 start_point = Point(self._xypos[0], self._xypos[1], self._zstart)
00151 end_point = Point(self._xypos[0], self._xypos[1], self._zend)
00152 m.points = [start_point, end_point]
00153 self._msg = (m,)
00154 return self._msg
00155
00156 def set(self, xypos=None, zstart=None, zend=None):
00157 if xypos is not None:
00158 self._xypos = xypos
00159 if zstart is not None:
00160 self._zstart = zstart
00161 if zend is not None:
00162 self._zend = zend
00163 self._msg = None
00164
00165 class TextMarker(MarkerBase):
00166 def __init__(self, namespace, text, pos, frame_id=None, **kwargs):
00167 MarkerBase.__init__(self, namespace)
00168 self._kwargs = kwargs
00169 self._kwargs.update({'text': text, 'pos': pos})
00170 if frame_id is None:
00171 self._frame_id = default_frame_id
00172 else:
00173 self._frame_id = frame_id
00174
00175 def get_msgs(self):
00176 if self._msg is not None:
00177 return self._msg
00178 else:
00179 m = self._default_marker()
00180 m.type = Marker.TEXT_VIEW_FACING
00181 m.scale.z = self._kwargs.get('height', 0.05)
00182 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00183 m.pose.position.x, m.pose.position.y, m.pose.position.z = self._kwargs['pos']
00184 m.text = self._kwargs['text']
00185 self._msg = m,
00186 return self._msg
00187
00188 def set(self, **kwargs):
00189 self._kwargs.update(kwargs)
00190 self._msg = None
00191
00192 class TrailMarker(MarkerBase):
00193 """
00194 There is already an rviz display for nav_msgs/Path messages, however it is not as
00195 flexible as this.
00196 """
00197 _max_points = 1000
00198 def __init__(self, namespace, points, colors=None, frame_id=None, **kwargs):
00199 MarkerBase.__init__(self, namespace)
00200 self._kwargs = kwargs
00201 self._kwargs.update({'points': points})
00202 self._kwargs.update({'colors': colors})
00203 if frame_id is None:
00204 self._frame_id = default_frame_id
00205 else:
00206 self._frame_id = frame_id
00207
00208 def get_msgs(self):
00209 if self._msg is not None:
00210 return self._msg
00211 else:
00212 m = self._default_marker()
00213 m.type = Marker.LINE_STRIP
00214 m.scale.x = self._kwargs.get('width', 0.01)
00215 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00216 m.points = [Point(*p) for p in self._kwargs['points'][-self._max_points:]]
00217 colors = self._kwargs.get('colors')
00218 if colors is not None:
00219 m.colors = [ColorRGBA(*c) for c in colors[-self._max_points:]]
00220 self._msg = (m,)
00221 return self._msg
00222
00223 def append_points(self, new_points, new_colors=None):
00224 self._kwargs['points'].extend(new_points)
00225 self._kwargs['points'] = self._kwargs['points'][-self._max_points:]
00226 colors = self._kwargs['colors']
00227 if colors is not None:
00228 if new_colors is not None:
00229 self._kwargs['colors'].extend(new_colors)
00230 else:
00231 self._kwargs['colors'].extend([self._kwargs['colors'][-1]]*len(new_points))
00232 self._kwargs['colors'] = self._kwargs['colors'][-self._max_points:]
00233 self._msg = None
00234
00235 def set_max_points(self, new_max):
00236 if new_max < self._max_points:
00237 self._kwargs['points'] = self._kwargs['points'][-new_max:]
00238 if self._kwargs['colors'] is not None:
00239 self._kwargs['colors'] = self._kwargs['colors'][-new_max:]
00240 self._max_points = new_max
00241
00242 class HeadingMarker(MarkerBase):
00243 """
00244 Actually 3 markers: a flat disc, an arrow and text. The text will show heading in degrees CW of +X,
00245 with a range of (-180,180].
00246 """
00247 def __init__(self, namespace, pos, heading=0, frame_id=None, **kwargs):
00248 MarkerBase.__init__(self, namespace, num_ids=3)
00249 self._kwargs = kwargs
00250 self._kwargs.update({'pos': pos})
00251 self._kwargs.update({'heading': heading})
00252 if frame_id is None:
00253 self._frame_id = default_frame_id
00254 else:
00255 self._frame_id = frame_id
00256
00257 def get_msgs(self):
00258 if self._msg is not None:
00259 return self._msg
00260 else:
00261 pos = np.array(self._kwargs['pos'])
00262 length = self._kwargs.get('arrow_length', 0.15)
00263 heading = radians(self._kwargs['heading'])
00264
00265 mdisc = self._default_marker(0)
00266 mdisc.type = Marker.CYLINDER
00267 mdisc.pose.position.x, mdisc.pose.position.y, mdisc.pose.position.z = pos
00268 mdisc.scale.x = self._kwargs.get('radius', 0.1)*2
00269 mdisc.scale.y = self._kwargs.get('radius', 0.1)*2
00270 mdisc.scale.z = self._kwargs.get('height', 0.02)
00271 mdisc.color.r, mdisc.color.g, mdisc.color.b, mdisc.color.a = self._kwargs.get('color', Colors.WHITE + Alpha(0.3))
00272
00273 marrow = self._default_marker(1)
00274 marrow.type = Marker.ARROW
00275 marrow.pose.position.x, marrow.pose.position.y, marrow.pose.position.z = self._kwargs['pos']
00276 marrow.scale.x = length
00277 marrow.scale.y = marrow.scale.z = self._kwargs.get('shaft_radius', 0.2)*2
00278 marrow.color.r, marrow.color.g, marrow.color.b, marrow.color.a = self._kwargs.get('color', Colors.RED + Alpha(0.8))
00279 mpo = marrow.pose.orientation
00280 quat = tft.quaternion_from_euler(heading, 0, 0, axes='rzyx')
00281 mpo.x, mpo.y, mpo.z, mpo.w = quat
00282
00283 mtext = self._default_marker(2)
00284 mtext.type = Marker.TEXT_VIEW_FACING
00285 mtext.scale.z = self._kwargs.get('height', 0.05)
00286 mtext.color.r, mtext.color.g, mtext.color.b, mtext.color.a = self._kwargs.get('text_color', OpaqueColors.WHITE)
00287 offset = (length+0.1)*np.array([cos(heading), sin(heading) , 0])
00288 mtext.pose.position.x, mtext.pose.position.y, mtext.pose.position.z = pos + offset
00289 mtext.text = "%.1f" % degrees(heading)
00290
00291 self._msg = (marrow, mtext)
00292 return self._msg
00293
00294 def set(self, **kwargs):
00295 self._kwargs.update(kwargs)
00296 self._msg = None
00297
00298 class PolygonMarker(MarkerBase):
00299 def __init__(self, namespace, points, colors=None, closed=True, frame_id=None, **kwargs):
00300 MarkerBase.__init__(self, namespace)
00301 self._kwargs = kwargs
00302 self._kwargs.update({'points': points})
00303 self._kwargs.update({'colors': colors})
00304 self._kwargs.update({'closed': closed})
00305 if frame_id is None:
00306 self._frame_id = default_frame_id
00307 else:
00308 self._frame_id = frame_id
00309
00310 def get_msgs(self):
00311 if self._msg is not None:
00312 return self._msg
00313 else:
00314 m = self._default_marker()
00315 m.type = Marker.LINE_STRIP
00316 m.scale.x = self._kwargs.get('width', 0.01)
00317 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00318 m.points = [Point(*p) for p in self._kwargs['points']]
00319 if self._kwargs['closed']:
00320 m.points += [Point(*self._kwargs['points'][0])]
00321 colors = self._kwargs.get('colors')
00322 if colors is not None:
00323 m.colors = [ColorRGBA(*c) for c in colors]
00324 self._msg = (m,)
00325 return self._msg
00326
00327 def set(self, **kwargs):
00328 self._kwargs.update(kwargs)
00329 self._msg = None
00330
00331 class PlanarRectangleWithRoundedEnds(PolygonMarker):
00332 """
00333 Useful for reach set overapproximations. Produces a polygonal marker that represents a rectangle of
00334 given length aligned with the given axis and with ends replaced by semicircles (TODO: better wording!)
00335 _______
00336 (_______)
00337
00338 """
00339 def __init__(self, namespace, origin, axis, length, radius, frame_id=None, **kwargs):
00340 PolygonMarker.__init__(self, namespace, points=[], colors=None, frame_id=frame_id, **kwargs)
00341 self._kwargs.update(kwargs)
00342 self._kwargs.update({'origin': origin, 'axis':axis, 'length':length, 'radius':radius})
00343 self._kwargs['points'] = self._get_points()
00344 if frame_id is None:
00345 self._frame_id = default_frame_id
00346 else:
00347 self._frame_id = frame_id
00348
00349 def _get_points(self):
00350 origin = np.array(self._kwargs['origin'])
00351 axis = np.concatenate([self._kwargs['axis'], [0]])
00352 axis = axis/norm(axis)
00353 perp_axis = np.array([-axis[1], axis[0], 0])
00354 radius = self._kwargs['radius']
00355 length = self._kwargs['length']
00356 N = self._kwargs.get('n_segments', 20)
00357 semicirc1 = np.array([origin + radius*perp_axis*cos(angle) - radius*axis*sin(angle) for angle in np.linspace(0, np.pi, N)])
00358 origin2 = origin + axis*length
00359 semicirc2 = np.array([origin2 - radius*perp_axis*cos(angle) + radius*axis*sin(angle) for angle in np.linspace(0, np.pi, N)])
00360 points = np.concatenate([semicirc1, [origin - radius*perp_axis, origin2 - radius*perp_axis],
00361 semicirc2, [origin2 + radius*perp_axis, origin + radius*perp_axis]])
00362 return points
00363
00364 def set(self, **kwargs):
00365 self._kwargs.update(kwargs)
00366 self._kwargs['points'] = self._get_points()
00367 self._msg = None
00368
00369 class SphereMarker(MarkerBase):
00370 def __init__(self, namespace, origin, radius, color=None, frame_id=None, **kwargs):
00371 MarkerBase.__init__(self, namespace)
00372 self._kwargs = kwargs
00373 self._kwargs.update({'origin': origin})
00374 self._kwargs.update({'radius': radius})
00375 self._kwargs.update({'color': color} if color is not None else {})
00376 if frame_id is None:
00377 self._frame_id = default_frame_id
00378 else:
00379 self._frame_id = frame_id
00380
00381 def get_msgs(self):
00382 if self._msg is not None:
00383 return self._msg
00384 else:
00385 m = self._default_marker()
00386 m.type = Marker.SPHERE
00387 m.scale.x = m.scale.y = m.scale.z = self._kwargs['radius']*2.0
00388 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00389 m.pose.position.x, m.pose.position.y, m.pose.position.z = self._kwargs['origin']
00390 self._msg = (m,)
00391 return self._msg
00392
00393 def set(self, **kwargs):
00394 self._kwargs.update(kwargs)
00395 self._msg = None
00396
00397 class SphereListMarker(MarkerBase):
00398 def __init__(self, namespace, points, radius, color=None, frame_id=None, **kwargs):
00399 MarkerBase.__init__(self, namespace)
00400 self._kwargs = kwargs
00401 self._kwargs.update({'points': points})
00402 self._kwargs.update({'radius': radius})
00403 self._kwargs.update({'color': color} if color is not None else {})
00404 if frame_id is None:
00405 self._frame_id = default_frame_id
00406 else:
00407 self._frame_id = frame_id
00408
00409 def get_msgs(self):
00410 if self._msg is not None:
00411 return self._msg
00412 else:
00413 m = self._default_marker()
00414 m.type = Marker.SPHERE_LIST
00415 m.scale.x = m.scale.y = m.scale.z = self._kwargs['radius']*2.0
00416 m.color.r, m.color.g, m.color.b, m.color.a = self._kwargs.get('color', OpaqueColors.WHITE)
00417 m.points = [Point(*p) for p in self._kwargs['points']]
00418 self._msg = (m,)
00419 return self._msg
00420
00421 def set(self, **kwargs):
00422 self._kwargs.update(kwargs)
00423 self._msg = None
00424
00425 class QuadrotorMarker(MarkerBase):
00426 """
00427 Simplified quadrotor graphic consisting of:
00428 - 4 discs representing the rotors
00429 - 4 cylinders for motors
00430 - 4 lines (in one LINE_LIST) for rotor arms
00431 -
00432 """
00433 def __init__(self, namespace, frame_id=None, **kwargs):
00434 MarkerBase.__init__(self, namespace, num_ids=11)
00435 self._kwargs = kwargs
00436 if frame_id is None:
00437 self._frame_id = default_frame_id
00438 else:
00439 self._frame_id = frame_id
00440
00441 def get_msgs(self):
00442 if self._msg is not None:
00443 return self._msg
00444 else:
00445
00446 markers = []
00447 rotor_radius = self._kwargs.get('rotor_radius', 0.1)
00448 rotor_height = self._kwargs.get('rotor_height', 0.005)
00449 rotor_z_offset = self._kwargs.get('rotor_z_offset', 0.0)
00450 rotor_offset = self._kwargs.get('rotor_offset', 0.2)
00451 rotor_color = self._kwargs.get('rotor_color', Colors.WHITE + Alpha(0.1))
00452 motor_radius = self._kwargs.get('motor_radius', 0.01)
00453 motor_height = self._kwargs.get('motor_height', 0.02)
00454 motor_color = self._kwargs.get('motor_color', Colors.BLACK + Alpha(1.0))
00455 motor_z_offset = self._kwargs.get('motor_z_offset', 0.015)
00456 arm_thickness = self._kwargs.get('arm_thickness', 0.01)
00457 arm_color = self._kwargs.get('arm_color', Colors.BLACK + Alpha(1.0))
00458 body_height = self._kwargs.get('body_height', 0.20)
00459 body_width = self._kwargs.get('body_width', 0.1)
00460 body_z_offset = self._kwargs.get('body_z_offset', 0.05)
00461 body_color = self._kwargs.get('body_color', Colors.BLACK + Alpha(1.0))
00462 foot_height = self._kwargs.get('foot_height', 0.02)
00463 foot_width = self._kwargs.get('foot_width', 0.15)
00464 foot_z_offset = self._kwargs.get('foot_z_offset', body_z_offset + body_height/2.0 + foot_height/2.0)
00465 foot_color = self._kwargs.get('foot_color', Colors.DARK_GREY + Alpha(1.0))
00466 posx = (rotor_offset, 0.0, -rotor_offset, 0.0)
00467 posy = (0.0, rotor_offset, 0.0, -rotor_offset)
00468 arms_llist = aline = self._default_marker(8)
00469 arms_llist.type = Marker.LINE_LIST
00470 arms_llist.pose = Pose()
00471 arms_llist.scale.x = arms_llist.scale.y = arms_llist.scale.z = arm_thickness
00472 arms_llist.pose.orientation.w = 1.0
00473 arms_llist.color.a = 1.0
00474 for i in range(4):
00475
00476 rdisc = self._default_marker(i)
00477 rdisc.type = Marker.CYLINDER
00478 rdisc.pose.position.x = posx[i]
00479 rdisc.pose.position.y = posy[i]
00480 rdisc.pose.position.z = rotor_z_offset
00481 rdisc.scale.x = rotor_radius*2.0
00482 rdisc.scale.y = rotor_radius*2.0
00483 rdisc.scale.z = rotor_height
00484 set_color(rdisc.color, rotor_color)
00485 markers.append(rdisc)
00486
00487 mdisc = self._default_marker(i+4)
00488 mdisc.type = Marker.CYLINDER
00489 mdisc.pose.position.x = posx[i]
00490 mdisc.pose.position.y = posy[i]
00491 mdisc.pose.position.z = motor_z_offset
00492 mdisc.scale.x = motor_radius*2.0
00493 mdisc.scale.y = motor_radius*2.0
00494 mdisc.scale.z = motor_height
00495 set_color(mdisc.color, motor_color)
00496 markers.append(mdisc)
00497
00498 pointA = Point(0, 0, motor_z_offset + motor_height/2.0)
00499 pointB = Point(posx[i], posy[i], motor_z_offset + motor_height/2.0)
00500 arm_color_obj = ColorRGBA()
00501 set_color(arm_color_obj, arm_color)
00502 arms_llist.points.append(pointA)
00503 arms_llist.points.append(pointB)
00504 arms_llist.colors.append(arm_color_obj)
00505 arms_llist.colors.append(arm_color_obj)
00506 markers.append(arms_llist)
00507
00508 body = self._default_marker(9)
00509 quat_yaw45 = tft.quaternion_about_axis(radians(45.0), (0,0,1))
00510 bpo = body.pose.orientation
00511 bpo.x, bpo.y, bpo.z, bpo.w = quat_yaw45
00512 body.type = Marker.CUBE
00513 body.scale.x = body.scale.y = body_width
00514 body.scale.z = body_height
00515 body.pose.position.z = body_z_offset
00516 set_color(body.color, body_color)
00517 markers.append(body)
00518
00519 foot = self._default_marker(10)
00520 foot.type = Marker.CUBE
00521 fpo = foot.pose.orientation
00522 fpo.x, fpo.y, fpo.z, fpo.w = quat_yaw45
00523 foot.scale.x = foot.scale.y = foot_width
00524 foot.scale.z = foot_height
00525 foot.pose.position.z = foot_z_offset
00526 set_color(foot.color, foot_color)
00527 markers.append(foot)
00528 self._msg = tuple(markers)
00529
00530 return self._msg
00531
00532 def set(self, **kwargs):
00533 self._kwargs.update(kwargs)
00534 self._msg = None
00535
00536