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: */