LoSurvey.C

Go to the documentation of this file.
00001 /**
00002    \file  Robots/LoBot/control/LoSurvey.C
00003    \brief This file defines the non-inline member functions of the
00004    lobot::Survey class.
00005 */
00006 
00007 // //////////////////////////////////////////////////////////////////// //
00008 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00009 // by the University of Southern California (USC) and the iLab at USC.  //
00010 // See http://iLab.usc.edu for information about this project.          //
00011 // //////////////////////////////////////////////////////////////////// //
00012 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00013 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00014 // in Visual Environments, and Applications'' by Christof Koch and      //
00015 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00016 // pending; application number 09/912,225 filed July 23, 2001; see      //
00017 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status).     //
00018 // //////////////////////////////////////////////////////////////////// //
00019 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00020 //                                                                      //
00021 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00022 // redistribute it and/or modify it under the terms of the GNU General  //
00023 // Public License as published by the Free Software Foundation; either  //
00024 // version 2 of the License, or (at your option) any later version.     //
00025 //                                                                      //
00026 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00027 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00028 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00029 // PURPOSE.  See the GNU General Public License for more details.       //
00030 //                                                                      //
00031 // You should have received a copy of the GNU General Public License    //
00032 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00033 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00034 // Boston, MA 02111-1307 USA.                                           //
00035 // //////////////////////////////////////////////////////////////////// //
00036 //
00037 // Primary maintainer for this file: mviswana usc edu
00038 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/control/LoSurvey.C $
00039 // $Id: LoSurvey.C 13782 2010-08-12 18:21:14Z mviswana $
00040 //
00041 
00042 //------------------------------ HEADERS --------------------------------
00043 
00044 // lobot headers
00045 #include "Robots/LoBot/control/LoSurvey.H"
00046 
00047 #include "Robots/LoBot/LoApp.H"
00048 
00049 #include "Robots/LoBot/slam/LoMap.H"
00050 #include "Robots/LoBot/slam/LoSlamParams.H"
00051 
00052 #include "Robots/LoBot/io/LoLRFData.H"
00053 #include "Robots/LoBot/config/LoConfigHelpers.H"
00054 
00055 #include "Robots/LoBot/thread/LoUpdateLock.H"
00056 #include "Robots/LoBot/thread/LoShutdown.H"
00057 
00058 #include "Robots/LoBot/misc/LoExcept.H"
00059 #include "Robots/LoBot/misc/LoRegistry.H"
00060 #include "Robots/LoBot/util/LoMath.H"
00061 
00062 // INVT utilities
00063 #include "Util/log.H"
00064 
00065 // OpenGL headers
00066 #ifdef INVT_HAVE_LIBGLU
00067 #include <GL/glu.h>
00068 #endif
00069 
00070 #ifdef INVT_HAVE_LIBGL
00071 #include <GL/gl.h>
00072 #endif
00073 
00074 // Boost headers
00075 #include <boost/bind.hpp>
00076 #include <boost/shared_ptr.hpp>
00077 
00078 // Standard C++ headers
00079 #include <string>
00080 #include <algorithm>
00081 #include <vector>
00082 #include <utility>
00083 
00084 //----------------------------- NAMESPACE -------------------------------
00085 
00086 namespace lobot {
00087 
00088 //-------------------------- KNOB TWIDDLING -----------------------------
00089 
00090 namespace {
00091 
00092 // Retrieve settings from survey section of config file
00093 template<typename T>
00094 static inline T conf(const std::string& key, const T& default_value)
00095 {
00096    return get_conf<T>(LOBE_SURVEY, key, default_value) ;
00097 }
00098 
00099 // Overload for retrieving STL pairs
00100 template<typename T>
00101 static inline std::pair<T, T>
00102 conf(const std::string& key, const std::pair<T, T>& default_value)
00103 {
00104    return get_conf<T>(LOBE_SURVEY, key, default_value) ;
00105 }
00106 
00107 /// This local class encapsulates various parameters that can be used to
00108 /// tweak different aspects of the survey behaviour.
00109 class Params : public singleton<Params> {
00110    /// Since SLAM updates can be quite intense, the survey behaviour
00111    /// waits for the robot to move or turn by at least some minimum
00112    /// amount before triggering the SLAM algorithm. This setting
00113    /// specifies the above-mentioned thresholds. The distance threshold
00114    /// is in mm and the angle threshold in degrees.
00115    ///
00116    /// NOTE: This setting expects its value to be two integers. The first
00117    /// number is used for the distance threshold and the second for the
00118    /// angle threshold. Set these values to -1 to turn the thresholds
00119    /// off.
00120    std::pair<int, int> m_thresholds ;
00121 
00122    /// Sometimes the low-level controller goes berserk and sends
00123    /// nonsensical odometry packets. This setting specifies the maximum
00124    /// acceptable values for the distance and rotation sent by the
00125    /// low-level. Values greater than these thresholds will be reported
00126    /// on stderr but ignored for SLAM updates.
00127    std::pair<int, int> m_odometry_max ;
00128 
00129    /// The number of milliseconds between successive iterations of this
00130    /// behaviour.
00131    ///
00132    /// WARNING: The ability to change a behaviour's update frequency is a
00133    /// very powerful feature whose misuse or abuse can wreak havoc! Be
00134    /// sure to use reasonable values for this setting.
00135    int m_update_delay ;
00136 
00137    /// Does the user want to show particle bearings as well as the
00138    /// particle positions? By default, the visualization only shows the
00139    /// particle positions.
00140    bool m_show_bearings ;
00141 
00142    /// Bearing scale factors: these "parameters" are scale factors used
00143    /// to convert the computer screen's pixel coordinates into the map's
00144    /// real/physical coordinates. These scale factors are used to show
00145    /// particle bearings in a fixed screen pixel size but in the map's
00146    /// coordinate system.
00147    std::pair<float, float> m_bearing_scale ;
00148 
00149    /// Private constructor because this is a singleton.
00150    Params() ;
00151    friend class singleton<Params> ;
00152 
00153 public:
00154    /// Accessing the various parameters
00155    //@{
00156    static const std::pair<int, int>& thresholds() {
00157       return instance().m_thresholds ;
00158    }
00159    static int   max_distance()  {return instance().m_odometry_max.first  ;}
00160    static int   max_rotation()  {return instance().m_odometry_max.second ;}
00161    static int   update_delay()  {return instance().m_update_delay  ;}
00162    static bool  show_bearings() {return instance().m_show_bearings ;}
00163    static float Bx() {return instance().m_bearing_scale.first  ;}
00164    static float By() {return instance().m_bearing_scale.second ;}
00165    //@}
00166 
00167    /// Clean-up
00168    ~Params() ;
00169 } ;
00170 
00171 // Parameter initialization
00172 Params::Params()
00173    : m_thresholds(conf("thresholds", std::make_pair(100, 5))),
00174      m_odometry_max(conf("odometry_max", std::make_pair(200, 30))),
00175      m_update_delay(clamp(conf("update_delay", 500), 250, 2500)),
00176      m_show_bearings(conf("show_bearings", false))
00177 {
00178    m_thresholds.first  =
00179       (m_thresholds.first  < 0) ? -1 : clamp(m_thresholds.first,  10, 250) ;
00180    m_thresholds.second =
00181       (m_thresholds.second < 0) ? -1 : clamp(m_thresholds.second,  1,  30) ;
00182 
00183    m_odometry_max.first  = clamp(m_odometry_max.first,  100, 250) ;
00184    m_odometry_max.second = clamp(m_odometry_max.second,   5,  45) ;
00185 
00186    // Compute scale factors to show particle bearings as lines exactly 10
00187    // pixels long.
00188    float L, R, B, T ;
00189    SlamParams::map_extents(&L, &R, &B, &T) ;
00190    Drawable::Geometry G(SlamParams::map_geometry());
00191    m_bearing_scale.first  = 15 * (R - L)/G.width   ;
00192    m_bearing_scale.second = 15 * (T - B)/G.height  ;
00193 }
00194 
00195 // Parameter clean-up
00196 Params::~Params(){}
00197 
00198 } // end of local anonymous namespace encapsulating above helpers
00199 
00200 //-------------------------- INITIALIZATION -----------------------------
00201 
00202 Survey::Survey()
00203    : base(Params::update_delay(), LOBE_SURVEY, Drawable::Geometry()),
00204      m_slam(0), m_slam_busy(false)
00205 {
00206    m_odometry.set_thresholds(Params::thresholds()) ;
00207    start(LOBE_SURVEY) ;
00208 }
00209 
00210 // The odometry_helper function object is used to signal the survey
00211 // behaviour's thread that it should trigger the SLAM algorithm with the
00212 // latest odometry.
00213 Survey::odometry_helper::odometry_helper(int dist, int ang)
00214    : distance(dist), angle(ang)
00215 {}
00216 
00217 // Before we begin regular action processing for the survey behaviour, we
00218 // should first ensure that all the necessary objects have been properly
00219 // setup. We also need to setup the low-level hook for getting odometry
00220 // updates, take care of initializing the SLAM algorithm and perform any
00221 // other required initialization that cannot/should not be done in the
00222 // constructor.
00223 void Survey::pre_run()
00224 {
00225    const LaserRangeFinder* lrf = App::lrf() ;
00226    if (! lrf)
00227       throw behavior_error(LASER_RANGE_FINDER_MISSING) ;
00228 
00229    Robot* robot = App::robot() ;
00230    if (! robot)
00231       throw behavior_error(MOTOR_SYSTEM_MISSING) ;
00232 
00233    Map* map = App::map() ;
00234    if (! map)
00235       throw behavior_error(MAPPING_DISABLED) ;
00236 
00237    robot->add_hook(Robot::SensorHook(
00238       sensor_hook, reinterpret_cast<unsigned long>(this))) ;
00239 
00240    UpdateLock::begin_read() ;
00241       LRFData scan(lrf) ;
00242    UpdateLock::end_read() ;
00243 
00244    const std::string map_file = SlamParams::map_file() ;
00245    if (map_file.empty()) // SLAM should perform both localization and mapping
00246       m_slam.reset(new FastSLAM(scan)) ;
00247    else { // SLAM will be given a known map and need only perform localization
00248       OccGrid* known_map = new OccGrid(map_file) ;
00249       map->update(*known_map) ;
00250       m_slam.reset(new FastSLAM(scan, boost::shared_ptr<OccGrid>(known_map))) ;
00251    }
00252 
00253    if (visualize(LOBE_SURVEY))
00254       map->add_hook(RenderHook(
00255          render_particles, reinterpret_cast<unsigned long>(this))) ;
00256 }
00257 
00258 //---------------------- THE BEHAVIOUR'S ACTION -------------------------
00259 
00260 // This function implements a custom "main loop" for the survey
00261 // behaviour. Instead of relying on a sleep to decide when to go in for
00262 // the behaviour's next iteration (as is the case with the default main
00263 // loop for behaviours), it uses a condition variable and the signal/wait
00264 // mechanism to go in for its next iteration.
00265 void Survey::run()
00266 {
00267    try
00268    {
00269       App::wait_for_init() ;
00270 
00271       // Main loop
00272       pre_run() ;
00273       while (! Shutdown::signaled())
00274       {
00275          if (m_odometry_cond.wait(threshold_check(), Params::update_delay())) {
00276             action() ;
00277             m_odometry_cond.protect(reset_slam_busy_flag()) ;
00278          }
00279       }
00280       post_run() ;
00281    }
00282    catch (uhoh& e)
00283    {
00284       LERROR("behaviour %s encountered an error: %s", LOBE_SURVEY, e.what()) ;
00285       return ;
00286    }
00287 }
00288 
00289 // When the main thread updates the low-level sensor state, this hook
00290 // function will be triggered. The survey behaviour uses the latest
00291 // odometry data to track the cumulative pose change since the previous
00292 // invocation of this hook. When the cumulative pose change reaches some
00293 // predefined limit, the behaviour will trigger the next update of its
00294 // SLAM algorithm as long as the SLAM module is not busy working on the
00295 // previous update.
00296 //
00297 // DEVNOTE: If the low-level odometry packet smells bad, this function
00298 // will throw an exception. Since this hook is executing in the context
00299 // of the main thread, having been called via Robot::update(), the
00300 // exception will result in the application quitting. This is not an
00301 // inappropriate action. If the low-level is truly berserk and sending
00302 // bogus odometry packets, then the high-level cannot function properly
00303 // and the user should fix the low-level problem (e.g., reboot robot) and
00304 // restart the application. If the low-level is okay, then the configured
00305 // odometry maximums are probably wrong. In that case too, the best thing
00306 // to do is to quit the application and have the user fix the config
00307 // before retrying.
00308 void
00309 Survey::
00310 sensor_hook(const Robot::Sensors& sensors, unsigned long client_data)
00311 {
00312    int distance = sensors.distance() ;
00313    int rotation = sensors.angle() ;
00314    /*
00315    LERROR("raw odometry= [%4d %4d] @ %lld",
00316           distance, rotation, sensors.time_stamp()) ;
00317    // */
00318    const int D = Params::max_distance() ;
00319    const int R = Params::max_rotation() ;
00320    if (abs(distance) > D || abs(rotation) > R) // bad low-level odometry?
00321    {
00322       // When the low-level controller responds to the Roomba's bump or
00323       // cliff sensors and also when it responds to the SPIN command sent
00324       // by the high level, it can result in the robot experiencing quite
00325       // a bit of translation and/or rotation. In these situations, we
00326       // should not expect the configured odometry maximums to apply.
00327       if (sensors.bump() || sensors.cliff() || sensors.spin())
00328          ; // odometry maximums do not apply
00329       else // normal situation: low-level controller did not move robot
00330          throw behavior_error(BOGUS_LOW_LEVEL_ODOMETRY) ; // thresholds apply
00331    }
00332 
00333    (reinterpret_cast<Survey*>(client_data))->accumulate(distance, rotation) ;
00334 }
00335 
00336 // Since low-level odometry updates come in from the main thread, we need
00337 // to "switch thread contexts" by signaling the survey behaviour's thread
00338 // that new odometry is available.
00339 void Survey::accumulate(int distance, int angle)
00340 {
00341    //LERROR("raw odometry = [%4d %4d]", distance, angle) ;
00342    m_odometry_cond.signal(odometry_update(distance, angle)) ;
00343 }
00344 
00345 // This predicate is used in conjunction with the above function's
00346 // signal. It adds the latest low-level odometry packet (distance and
00347 // angle) to the survey behaviour's odometry tracker and checks if the
00348 // SLAM module is busy or not. If not, it returns true to signal the
00349 // survey behaviour that new odometry (viz., control input) is available
00350 // for SLAM.
00351 bool Survey::odometry_helper::operator()(Survey& survey)
00352 {
00353    survey.m_odometry.add(distance, angle) ;
00354    /*
00355    LERROR("acc odometry = [%4d %4d]",
00356           survey.m_odometry.displacement(), survey.m_odometry.rotation()) ;
00357    // */
00358    if (! survey.m_slam_busy && survey.m_odometry.thresholds_crossed()) {
00359       survey.ut = survey.m_odometry ;
00360       survey.m_odometry.reset() ;
00361       return true ;
00362    }
00363    return false ;
00364 }
00365 
00366 // The survey behaviour waits until the low-level odometry has
00367 // accumulated up to some predefined minimum limit before it triggers the
00368 // next SLAM update. This predicate is used in conjunction with
00369 // Survey::m_odometry_cond to check the odometry thresholds. If the
00370 // thresholds have been crossed, it will set the SLAM module's state to
00371 // busy so that odometric updates from the main thread accumulate while
00372 // the SLAM update takes place and then return true to end the survey
00373 // behaviour's thread's waiting and go ahead with the SLAM update.
00374 bool Survey::threshold_helper::operator()(Survey& survey)
00375 {
00376    if (survey.ut.thresholds_crossed()) {
00377       survey.m_slam_busy = true ;
00378       return true ;
00379    }
00380    return false ;
00381 }
00382 
00383 // Once the SLAM update is done, we need to mark the SLAM module as "not
00384 // busy" so that we can proceed with the next update. As long as the SLAM
00385 // module is busy, the main thread's odometric updates will be
00386 // accumulated rather than immediately acted upon. To ensure proper
00387 // synchronization with the main thread, this function is used in
00388 // conjunction with Survey::m_odometry_cond's (internal) mutex.
00389 void Survey::reset_helper::operator()(Survey& survey)
00390 {
00391    survey.m_slam_busy = false ;
00392    survey.ut.reset() ;
00393 }
00394 
00395 // The survey behaviour uses a SLAM algorithm to build a map and record
00396 // the robot's trajectory. This function implements the next SLAM update
00397 // using the latest sensor and control inputs.
00398 void Survey::action()
00399 {
00400    UpdateLock::begin_read() ;
00401       LRFData zt(App::lrf()) ; // measurement at current time step t
00402    UpdateLock::end_read() ;
00403 
00404    viz_lock() ;
00405    try
00406    {
00407       /*
00408       LERROR("    ctl odometry = [%4d %4d]",
00409              ut.displacement(), ut.rotation()) ;
00410       // */
00411       m_slam->update(ut, zt) ; // DEVNOTE: ut is a member variable
00412    }
00413    catch (misc_error&) // ignore LOGIC_ERROR: line rasterizer moved more
00414    {                   // than one pixel in one step; ought not to happen,
00415    }                   // but if it does, visualization will freeze because
00416    viz_unlock() ;      // viz lock will still be held by dead SURVEY thread
00417 
00418    Map* M = App::map() ;
00419    M->update(m_slam->current_pose()) ;
00420    if (SlamParams::slam_mode()) // update map only when doing full SLAM
00421       M->update(m_slam->current_map()) ;
00422 }
00423 
00424 //--------------------------- VISUALIZATION -----------------------------
00425 
00426 // Quick helper function to compare two particle visualization structures
00427 // using the particle weights. Avoids the hassle of boost::bind for use
00428 // with the std::min_element and std::max_element algorithms.
00429 //
00430 // DEVNOTE: Because Particle::Viz is defined in the lobot namespace, this
00431 // comparison function also needs to be in the lobot namespace for the
00432 // compiler to be able to find it. That is, putting it in an anonymous
00433 // namespace here won't work. That's why we use the static keyword to
00434 // make this function invisible outside of this translation unit.
00435 static bool operator<(const Particle::Viz& a, const Particle::Viz& b)
00436 {
00437    return a.weight < b.weight ;
00438 }
00439 
00440 // Given the min and max weights for all the particles, this function
00441 // linearly intepolates a particle's weight to lie in [0,1] and returns a
00442 // new particle with the same pose but the rescaled weight.
00443 //
00444 // NOTE: Since the denominator for the linear scaling formula, (max-min),
00445 // is constant, we require the caller to compute that value and pass it
00446 // in so that it doesn't need to be recomputed over and over in this
00447 // function (which will be called from a loop in an STL algorithm).
00448 static Particle::Viz
00449 rescale_weight(const Particle::Viz& p, float min, float max_minus_min)
00450 {
00451    return Particle::Viz(p.pose, (p.weight - min)/max_minus_min) ;
00452 }
00453 
00454 // This function shows the given particle's estimate of the robot's
00455 // current position within the map as a simple dot. The dot's color is
00456 // set so as to depict the particle's relative importance. Each particle
00457 // is painted in a blue-green combination. Particles with higher weights
00458 // have more green in them whereas those with lower weights are bluer.
00459 static void render_particle(const Particle::Viz& p)
00460 {
00461    glColor3f(0, p.weight, 1 - p.weight) ;
00462    glVertex2f(p.pose.y(), p.pose.x()) ;
00463 }
00464 
00465 // This function shows the particle's estimate of the robot's current
00466 // bearing w.r.t. the map as a fixed-size line. The line's size is fixed
00467 // in screen pixels; appropriate scale factors for converting this fixed
00468 // size to the map's coordinate system are determined in the Params
00469 // initialization. These scale factors should be passed in to this
00470 // function.
00471 static void render_bearing(const Particle::Viz& p, float Sx, float Sy)
00472 {
00473    const float x = p.pose.x() ;
00474    const float y = p.pose.y() ;
00475    const float t = p.pose.t() ;
00476 
00477    glColor3f(0, p.weight, 1 - p.weight) ;
00478    glVertex2f(y, x) ;
00479    glVertex2f(y + Sy * sin(t), x + Sx * cos(t)) ;
00480 }
00481 
00482 // Callback function for drawing the particles on the map
00483 void Survey::render_particles(unsigned long client_data)
00484 {
00485    (reinterpret_cast<Survey*>(client_data))->render_particles() ;
00486 }
00487 
00488 void Survey::render_particles()
00489 {
00490    // First, retrieve particle poses and weights from SLAM algorithm
00491    viz_lock() ;
00492       std::vector<Particle::Viz> particles = m_slam->viz() ;
00493    viz_unlock() ;
00494 
00495    // Then, rescale particle weights to lie within [0,1]
00496    float min = (std::min_element(particles.begin(), particles.end()))->weight ;
00497    float max = (std::max_element(particles.begin(), particles.end()))->weight ;
00498    std::transform(particles.begin(), particles.end(), particles.begin(),
00499                   boost::bind(rescale_weight, _1, min, max - min)) ;
00500 
00501    // Setup 2D "view volume" to match real/physical coordinate system
00502    // except that the whole thing is rotated 90 degrees ccw so that our
00503    // notion of "up" matches that of the robot's.
00504    float L, R, B, T ; // map extents
00505    SlamParams::map_extents(&L, &R, &B, &T) ;
00506    glMatrixMode(GL_PROJECTION) ;
00507    glPushMatrix() ;
00508    glLoadIdentity() ;
00509    gluOrtho2D(T, B, L, R) ;
00510 
00511    glMatrixMode(GL_MODELVIEW) ;
00512    glPushMatrix() ;
00513    glLoadIdentity() ;
00514 
00515    // Now we're ready to draw the particles...
00516    bool show_bearings = Params::show_bearings() ;
00517    glPushAttrib(GL_POINT_BIT) ;
00518    glPointSize(show_bearings ? 5 : 1) ;
00519    glBegin(GL_POINTS) ;
00520       std::for_each(particles.begin(), particles.end(), render_particle) ;
00521    glEnd() ;
00522    if (show_bearings) {
00523       glBegin(GL_LINES) ;
00524          std::for_each(particles.begin(), particles.end(),
00525                        boost::bind(render_bearing, _1,
00526                                    Params::Bx(), Params::By())) ;
00527       glEnd() ;
00528    }
00529    glPopAttrib() ;
00530 
00531    // Reset GL transformations so next drawable won't get screwed
00532    restore_view_volume() ;
00533 }
00534 
00535 //----------------------------- CLEAN-UP --------------------------------
00536 
00537 Survey::~Survey(){}
00538 
00539 //-----------------------------------------------------------------------
00540 
00541 } // end of namespace encapsulating this file's definitions
00542 
00543 /* So things look consistent in everyone's emacs... */
00544 /* Local Variables: */
00545 /* indent-tabs-mode: nil */
00546 /* End: */
Generated on Sun May 8 08:41:23 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3