Overhead map

From Brown University Robotics

(Difference between revisions)
Jump to: navigation, search
(Execution)
(Execution)
Line 27: Line 27:
  > roscd rosjs
  > roscd rosjs
-
  > ./rosjs.py and a node that is publishing type Overhead_Map_Objs on the topic '/overhead_map_objs':
+
  > ./rosjs.py and a node that is publishing type :
-
* Make sure overhead localization topics are being published:
+
* Make sure overhead localization messages Overhead_Map_Objs (on the topic '/overhead_map_objs'topics) are being published:
  > rostopic echo overhead_map_objs
  > rostopic echo overhead_map_objs
Line 66: Line 66:
   
   
<!-- file:///[path-to-overhead_map]/overhead_map.html?map=soccer -->
<!-- file:///[path-to-overhead_map]/overhead_map.html?map=soccer -->
 +
 +
If successful, you should see a rendering of the FC 148 field with tracked objects on this webpage, appearing with image overlays of recognized objects.
== Multi-machine setup ==
== Multi-machine setup ==

Revision as of 02:18, 5 October 2010

omclock is a node that publishes messages for overhead_map, seen above. The time was approximately 12:07:25 when this screenshot was captured.

The overhead_map webpage allows one to display images, lines, points, and arrows on a top-down rendering of a real-world surface.


Contents

Single machine setup

Installation

This requires:

  • rosjs
  • om_msgs
  • A websockets compatible web browser* (check) such as Chrome 4+, Firefox 4+, or Safari 5+
    • *Note: Chrome's cannot currently handle ajax requests under file://, so if you are using Chrome, the web browser must access overhead_map.html via http. (Other browsers, eg Firefox 4 beta, do not suffer from this problem, and can alternatively be used.)

rosjs and om_msgs are available in the most current version of brown-ros-pkg. Assuming this is installed:

> roscd irobot_create_2_1/../.. # (ie, the root directory of brown-ros-pkg)
> svn update

The om_msgs package needs to be "compiled" so that the message types Overhead_Map_Obj and Overhead_Map_Objs are available:

> rosmake om_msgs

Execution

  • Start the rosjs server:
> roscd rosjs
> ./rosjs.py and a node that is publishing type :
  • Make sure overhead localization messages Overhead_Map_Objs (on the topic '/overhead_map_objs'topics) are being published:
> rostopic echo overhead_map_objs
 <html>
   <head>
     <title>Overhead Map</title>
     <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
     <script type="text/javascript" src="js/jquery.query.js"></script>
     <script type="text/javascript" src="js/ros.min.js"></script>
     <script type="text/javascript" src="overhead_map.js"></script>
   </head>
   <body>
     <table>
       <tr><td>
           <!-- canvas width/height controls the max. size of the board -->
           <canvas id="board" width="800" height="600" ></canvas>
       </td></tr>
       <tr><td>
           Log:
       </td></tr>
       <tr><td>
           <textarea id="console" rows="10" cols="80" readonly="readonly"></textarea>
       </td></tr>
     </table>
   </body>
 </html>

overhead_map.js uses rosjs to listen for messages related to the visualizing overhead localization topics and a canvas element to render these objects in the web browser.

Note: This page depends on jquery and ros.min.js, which should either be available locally or accessible via an internet connection.


If successful, you should see a rendering of the FC 148 field with tracked objects on this webpage, appearing with image overlays of recognized objects.

Multi-machine setup

overhead_map is hosted by the server (right computer), rosjs and omclock are running on the "robot" (center computer), and a web browser is running on the laptop (left computer)

Make sure all computers are connected to the same network. The three tasks at hand are:

  • Server: A web server to host overhead_map with address [server].
  • Robot: A robot which is running and publishing information with address [robot].
  • Laptop: A laptop (or any other computer) which you want to view the webpage on.

Any single machine may take on one or multiple roles.

Installation

  • The server must have some web hosting capability, unless the server and laptop are one machine. (In the latter case, you can simply point the browser at file://...)
  • On robot, install:
    • rosjs
    • om_msgs
  • On laptop, install:
    • A websockets compatible web browser (check)

On robot, and only on robot, om_msgs needs to be "compiled" so that the om_msg types Overhead_Map_Obj and Overhead_Map_Objs are available:

> rosmake om_msgs

(In fact, neither server nor laptop need ever know ROS exists at all!)

Execution (Server, Robot, and Laptop)

  • Put the overhead_map files in a network-accessible location on the server. The network path will be called "http://[server]/[somepath]/overhead_map".
  • On the robot, in ROS, run rosjs and a node that is publishing type Overhead_Map_Objs on the topic '/overhead_map_objs'.
  • Point the laptop's web browser to:
http://[server]/[somepath]/overhead_map/overhead_map.html?map=soccer&address=[robot]

Execution (Server-Robot and Laptop)

Follow the execution steps from the single-computer execution. When connecting from the laptop, use:

http://[robot]/[somepath]/overhead_map/overhead_map.html?map=soccer&address=[robot]

Execution (Server and Robot-Laptop)

Follow the instructions for the three machine execution. When connecting to the website from the robot, use:

http://[server]/[somepath]/overhead_map/overhead_map.html?map=soccer

(By default, the address is automatically bound to 127.0.0.1 .)

Execution (Server-Laptop and Robot)

Follow the execution steps from the three computer execution. When connecting from the laptop, use:

file:///[path-to-overhead_map]/overhead_map.html?map=soccer&address=[robot]


Example Code

omclock: A simple "clock" application to test that all is running well on your system by drawing an analog clock on the overhead_map soccer field. The included launch file (omclock.launch) will launch omclock and rosjs, and it will attempt to launch Google Chrome to display output. (This last step can also be accomplished manually by directing your browser.)

> roslaunch omclock omclock.launch

omdc: A display controller which takes planar_tracker messages as input and publishes overhead_map messages as output. This can be used to, for instance, create a top-down visualization of a field with objects identifiable by color blobs or AR Tags.


Walkthrough of omclock.py

omclock handles each of the 4 basic types of messages passable by om_msgs, so it will be used to demonstrate how each of these is used. omclock.py can be found by:

> roscd omclock/bin
> gedit omclock.py # or your favorite editor

Message types

First, let's look at how our messages are specified. For each desired update, we will pass a single message of type Overhead_Map_Objs -- essentially, a list with elements of type Overhead_Map_Obj -- to rosjs. rosjs will then use websockets to send this information to your web browser, resulting in the clock appearing on your screen. The message types the overhead_map system (including but not limited to overhead_map, omclock, and omdc) uses are in the ROS node om_msgs:

> roscd om_msgs/msg

There are two message types specified, namely Overhead_Map_Obj.msg and Overhead_Map_Objs.msg

Overhead_Map_Obj.msg:

# For each type of name, there is an associated tuple:
#  * "point":  [x y]          draws a point at (x, y)
#  * "line":   [x0 y0 x1 y1]  draws a line from (x0, y0) to (x1, y1)
#  * "arrow":  [x0 y0 x1 y1]  draws an arrow (x0, y0) --> (x1, y1)
#  * "imname": [x y th]       draws an image named "imname" centered
#                               at (x,y), rotated th radians.
#                               location of image is
#                               "$OVERHEAD_MAP_ROOT/icons/imname.png".
string name
float32[] tuple

Overhead_Map_Objs.msg:

Header header
Overhead_Map_Obj[] objs

Scaffolding

Let's start making omclock.py. There's a bit of ROS scaffolding to do first:

#!/usr/bin/env python
import roslib; roslib.load_manifest('omclock')
import rospy, time
from math import cos,sin,pi
from om_msgs.msg import Overhead_Map_Obj, Overhead_Map_Objs

def go():
    pub = rospy.Publisher('overhead_map_objs',Overhead_Map_Objs)
    rospy.init_node('omclock')

    # main control loop
    r = rospy.Rate(10) # hz
    while not rospy.is_shutdown():
        
        #
        #
        # Make the clock!
        #
        #

        # wait for next loop
        r.sleep()

# Input: propTime in [0,1]
# propTime is, eg, hours/12 or minutes/60
# Returns the angle (in radians) of the clock pointer at
def clockAngle(propTime):
    return (2*pi*(1-propTime)) + pi/2

def dtrace(s,x): # for debugging
    rospy.loginfo(s+" == %s"%x)
    return x;

if __name__ == '__main__':
    go()

Basically, we are setting Python up to publish messages of type Overhead_Map_Objs on topic "overhead_map_objs" and to publish the information at about 10 frames per second. (This will let the robot, which represents the second hand, look like it is driving around the clock face.) We also have some helper functions tangential to this walkthrough, which won't be investigated further.

Making the clock

We will now create the clock face by using "Overhead_Map_Obj"s by filling the "Make the clock!" comment.

"Ideal" coordinates overlaid on the soccer background.

Before we can do that, however, we need to know how the coordinate system is set up. All the drawing in overhead_map is performed relative to an "ideal" coordinate frame, which is similar to the one presented in math classes. In our frame, the bottom-left corner of the background is at point (0,0), x points rightward, and y points upward. Theta is measured in radians, is equal to zero when pointing towards +x, and increases as one rotates counterclockwise. The unit is declared arbitrarily, and is meant to be some sort of real-world measure. For example, the "soccer field" in real life is about 4 meters long and 2.5 meters high, so the soccer map is configured to represent the area surrounded by coordinates (0,0), (4,0), (4,2.5), and (0,2.5), and one draws objects by specifying them in ideal coordinates. For instance, if one sees an iRobot Create centered at the real world at coordinates (3,1), one can simply instruct overhead_map to draw a picture of the iRobot Create at (3,1); all the graphics processing and rendering is handled behind-the-scenes by overhead_map.js!

Let's start making the clock by setting up some constants and reading in the time:

       # Get time.  Make an analog clock face with center (centerX,centerY)
       #   and largest radius "radius"
       # This one's set up for the soccer field's center
       centerX = 2
       centerY = 1.25
       radius = 1
       curTime = time.localtime();

       hours = curTime.tm_hour
       minutes = curTime.tm_min
       seconds = curTime.tm_sec + (time.time()%1) # for partial seconds

Arrow

Arrow drawn on the soccer map.

Now, let's draw the hour hand using an arrow:

       # Hour hand is an arrow
       hourAngle = clockAngle((hours+minutes*1./60+seconds*1./3600)*1./12)
       hourHand = Overhead_Map_Obj()
       hourHand.name = "arrow"
       hourHand.tuple = [centerX,
                         centerY,
                         centerX+0.5*radius*cos(hourAngle),
                         centerY+0.5*radius*sin(hourAngle)]

There is a bit of computation happening to compute the angle the hour hand needs to face, but let's not worry about the details. The important bits are:

       hourHand = Overhead_Map_Obj()

which makes the object,

       hourHand.name = "arrow"

which tells overhead_map to draw the object as an arrow, and

       hourHand.tuple = [A,B,C,D]

which tells overhead_map to draw an arrow from (A,B) to (pointing toward) (C,D) as specified in Overhead_Map_Obj.msg:

#  * "arrow":  [x0 y0 x1 y1]  draws an arrow (x0, y0) --> (x1, y1)

The name and tuple together form a complete specification of an Overhead_Map_Obj.

Line

Line drawn on the soccer map.

Next, we'll draw the minute hand using a line:

        # Minute hand is a line
        minuteAngle = clockAngle((minutes+seconds*1./60)*1./60)
        minuteHand = Overhead_Map_Obj()
        minuteHand.name = "line"
        minuteHand.tuple = [centerX,
                            centerY,
                            centerX+0.75*radius*cos(minuteAngle),
                            centerY+0.75*radius*sin(minuteAngle)]

Note the only difference between it and the hour hand is that the name is "line" instead of "arrow"; otherwise, the code takes the same form, as specified in Overhead_Map_Obj.msg:

#  * "line":   [x0 y0 x1 y1]  draws a line from (x0, y0) to (x1, y1)

Image

Image of the iRobot Create drawn on the soccer map.

Next, we'll draw the second hand by using an image of the iRobot Create:

        # Second "hand" is a robot going around the outside
        secondAngle = clockAngle(seconds*1./60)
        secondHand = Overhead_Map_Obj()
        secondHand.name = "irobot-create"
        secondHand.tuple = [centerX+radius*cos(secondAngle),
                            centerY+radius*sin(secondAngle),
                            secondAngle-pi/2]

Creating the blank Overhead_Map_Obj is the same as usual. The name of this object is "irobot-create", which is an image stored in overhead_map's icon subdirectory; recall from Overhead_Map_Obj.msg:

#  * "imname": [x y th]       draws an image named "imname" centered
#                               at (x,y), rotated th radians.
#                               location of image is
#                               "$OVERHEAD_MAP_ROOT/icons/imname.png".

The tuple we created reflects the x, y, and theta coordinates desired.

Point

Point drawn on the soccer map (located inside the brown logo).

Finally, let's put a dot in the center of the clock for looks, using a point:

        # Center "dot"
        centerDot = Overhead_Map_Obj()
        centerDot.name = "point"
        centerDot.tuple = [centerX, centerY]

Note the name "point" and the tuple specified in Overhead_Map_Obj.msg:

#  * "point":  [x y]          draws a point at (x, y)

Merging "Overhead_Map_Obj"s into one "Overhead_Map_Objs", and publishing

To merge all the objects we want drawn, we simply create an instance of Overhead_Map_Objs and put all of our objects in that instance's ".objs" field:

        # Put it all together (front drawn first)
        drawing = Overhead_Map_Objs()
        drawing.objs = [secondHand, minuteHand, hourHand, centerDot]

Finally, we need to publish our instance of Overhead_Map_Objs to rosjs:

        pub.publish(drawing)

...and our clock is drawn!

Full Source Code

Here's the entirety of omclock.py:

#!/usr/bin/env python
import roslib; roslib.load_manifest('omclock')
import rospy, time
from math import cos,sin,pi
from om_msgs.msg import Overhead_Map_Obj, Overhead_Map_Objs

def go():
    pub = rospy.Publisher('overhead_map_objs',Overhead_Map_Objs)
    rospy.init_node('omclock')

    # main control loop
    r = rospy.Rate(10) # hz
    while not rospy.is_shutdown():
        # Get time.  Make an analog clock face with center (centerX,centerY)
        #   and largest radius "radius"
        # This one's set up for the soccer field's center
        centerX = 2
        centerY = 1.25
        radius = 1
        curTime = time.localtime();

        hours = curTime.tm_hour
        minutes = curTime.tm_min
        seconds = curTime.tm_sec + (time.time()%1) # for partial seconds

        # Hour hand is an arrow
        hourAngle = clockAngle((hours+minutes*1./60+seconds*1./3600)*1./12)
        hourHand = Overhead_Map_Obj()
        hourHand.name = "arrow"
        hourHand.tuple = [centerX,
                          centerY,
                          centerX+0.5*radius*cos(hourAngle),
                          centerY+0.5*radius*sin(hourAngle)]
        # Minute hand is a line
        minuteAngle = clockAngle((minutes+seconds*1./60)*1./60)
        minuteHand = Overhead_Map_Obj()
        minuteHand.name = "line"
        minuteHand.tuple = [centerX,
                            centerY,
                            centerX+0.75*radius*cos(minuteAngle),
                            centerY+0.75*radius*sin(minuteAngle)]
        # Second "hand" is a robot going around the outside
        secondAngle = clockAngle(seconds*1./60)
        secondHand = Overhead_Map_Obj()
        secondHand.name = "irobot-create"
        secondHand.tuple = [centerX+radius*cos(secondAngle),
                            centerY+radius*sin(secondAngle),
                            secondAngle-pi/2]
        # Center "dot"
        centerDot = Overhead_Map_Obj()
        centerDot.name = "point"
        centerDot.tuple = [centerX, centerY]

        # Put it all together (front drawn first)
        drawing = Overhead_Map_Objs()
        drawing.objs = [secondHand, minuteHand, hourHand, centerDot]
        pub.publish(drawing)
        # wait for next loop
        r.sleep()

# Input: propTime in [0,1]
# propTime is, eg, hours/12 or minutes/60
# Returns the angle (in radians) of the clock pointer at
def clockAngle(propTime):
    return (2*pi*(1-propTime)) + pi/2

def dtrace(s,x):
    rospy.loginfo(s+" == %s"%x)
    return x;

if __name__ == '__main__':
    go()