markers.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 #  Copyright (c) 2011, UC Regents
00004 #  All rights reserved.
00005 #
00006 #  Redistribution and use in source and binary forms, with or without
00007 #  modification, are permitted provided that the following conditions
00008 #  are met:
00009 #
00010 #   * Redistributions of source code must retain the above copyright
00011 #     notice, this list of conditions and the following disclaimer.
00012 #   * Redistributions in binary form must reproduce the above
00013 #     copyright notice, this list of conditions and the following
00014 #     disclaimer in the documentation and/or other materials provided
00015 #     with the distribution.
00016 #   * Neither the name of the University of California nor the names of its
00017 #     contributors may be used to endorse or promote products derived
00018 #     from this software without specific prior written permission.
00019 #
00020 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 #  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 #  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 #  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 #  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 #  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 #  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 #  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 #  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 #  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 #  POSSIBILITY OF SUCH DAMAGE.
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     # for storing the message(s) for later publication:
00076     _msg = None
00077     # common settings:
00078     _ns = ""
00079     _ids = []
00080     _frame_id = ""
00081     # other members:
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] # will this work?
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 # force message regen
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 #force update
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 # force update
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             #disc
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             #arrow
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             # text
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 #force update
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 #force update
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 #force update
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 #force update
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             #discs
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() # all zeros
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                 # Rotor:
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                 # Motor:
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                 # Arm:
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             # body:
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             # foot:
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 #force update
00535         
00536         


starmac_roslib
Author(s): Patrick Bouffard
autogenerated on Sun Jan 5 2014 11:38:14