00001 /** 00002 \file Robots/LoBot/LobayMain.C 00003 \brief Robolocust metrics log analyzer for Bayesian TTI predictions. 00004 00005 This file defines the main function for a multithreaded analysis 00006 program that loads all the Robolocust metrics logs associated with the 00007 Bayesian time-to-impact prediction experiments and processes these 00008 logs to produce the desired results files for each set of related 00009 experiments. 00010 00011 Here's the background for this program: the Robolocust project aims to 00012 use locusts for robot navigation. Specifically, the locust sports a 00013 visual interneuron known as the Lobula Giant Movement Detector (LGMD) 00014 that spikes preferentially in response to objects moving toward the 00015 animal on collisional trajectories. Robolocust's goal is to use an 00016 array of locusts, each looking in a different direction. As the robot 00017 moves, we expect to receive greater spiking activity from the locusts 00018 looking in the direction in which obstacles are approaching (or being 00019 approached) and use this information to veer the robot away. 00020 00021 Before we mount actual locusts on a robot, we would like to first 00022 simulate this LGMD-based navigation. Toward that end, we use a laser 00023 range finder (LRF) mounted on an iRobot Create driven by a quad-core 00024 mini-ITX computer. A computational model of the LGMD developed by 00025 Gabbiani, et al. takes the LRF distance readings and the Create's 00026 odometry as input and produces artificial LGMD spikes based on the 00027 time-to-impact of approaching objects. We simulate multiple virtual 00028 locusts by using different angular portions of the LRF's field of 00029 view. To simulate reality a little better, we inject Gaussian noise 00030 into the artificial spikes. 00031 00032 We have devised three different LGMD-based obstacle avoidance 00033 algorithms: 00034 00035 1. EMD: pairs of adjacent LGMD's are fed into Reichardt motion 00036 detectors to determine the dominant direction of spiking 00037 activity and steer the robot away from that direction; 00038 00039 2. VFF: a spike rate threshold is used to "convert" each virtual 00040 locust's spike into an attractive or repulsive virtual 00041 force; all the force vectors are combined to produce the 00042 final steering vector; 00043 00044 3. TTI: each locust's spike rate is fed into a Bayesian state 00045 estimator that computes the time-to-impact given a spike 00046 rate; these TTI estimates are then used to determine 00047 distances to approaching objects, thereby effecting the 00048 LGMD array's use as a kind of range sensor; the distances 00049 are compared against a threshold to produce attractive and 00050 repulsive forces, with the sum of the force field vectors 00051 determining the final steering direction. 00052 00053 As mentioned above, the TTI algorithm uses a Bayesian state estimator 00054 that predicts the time-to-impact given an LGMD spike rate. The 00055 Bayesian time-to-impact prediction experiments are designed to 00056 evaluate this TTI prediction model. Here's how these experiments were 00057 conducted: 00058 00059 The robot was driven straight ahead towards a wall starting at a point 00060 2.5 meters away. A single virtual locust looking straight ahead was 00061 used to generate LGMD spikes. The robot was configured to stop just 00062 short of hitting the wall. 00063 00064 As it moved toward the wall, the robot's controller would record the 00065 current speed, LGMD spike rate, actual time-to-impact, predicted TTI, 00066 actual distance to wall, predicted distance and the prediction's 00067 confidence level to a log file. 00068 00069 We varied the robot's speed, the amount of noise in the artificially 00070 generated LGMD spikes and the delta value for the spike generation 00071 model. The delta value is a parameter that controls when the peak 00072 spike rate is achieved w.r.t. the point at which a collision takes 00073 place, i.e., when time-to-impact is zero. To illustrate, let us say we 00074 use a delta of 1.0 seconds; this means that the spike generation model 00075 will produce a peak when the time-to-impact is 1.0 seconds, i.e., when 00076 the approaching wall is 1 second away from the robot. Similarly, when 00077 delta is 0.5, the LGMD peak will be achieved when the robot is half a 00078 second away from colliding with the wall; at delta = 2, the peak will 00079 be at 2 seconds from collision; so on and so forth. 00080 00081 The TTI prediction experiments were run with the following parameters: 00082 00083 - noise: 0Hz, 25Hz, 50Hz, 100Hz 00084 - speed: 0.1m/s, 0.2m/s, 0.3m/s, 0.4m/s 00085 - delta: 0.25s, 0.50s, 0.75s, 1.00s, 1.25s, 1.50s, 1.75s, 2.00s 00086 00087 For each noise level, robot speed and delta value, we ran the robot 10 00088 times. Thus, if we consider each set of 10 such individual runs to be 00089 one dataset, we have a total of 4 noise levels times 4 speeds times 8 00090 delta values = 128 datasets. 00091 00092 If "bay" is the root data directory for the Bayesian TTI experiments, 00093 then each dataset's log files are stored in a subdirectory of "bay" in 00094 a hierarchy as depicted below: 00095 00096 bay 00097 | 00098 +-- 000 00099 | | 00100 | +-- 0.1 00101 | | | 00102 | | +-- 0.25 00103 | | +-- 0.50 00104 | | +-- 0.75 00105 | | +-- 1.00 00106 | | +-- 1.25 00107 | | +-- 1.50 00108 | | +-- 1.75 00109 | | +-- 2.00 00110 | | 00111 | +-- 0.2 00112 | | : 00113 | | : 00114 | | 00115 | +-- 0.3 00116 | | : 00117 | | 00118 | +-- 0.4 00119 | : 00120 | : 00121 | 00122 +-- 025 00123 | : 00124 | : 00125 | 00126 +-- 050 00127 | : 00128 | : 00129 | 00130 +-- 100 00131 : 00132 : 00133 00134 NOTE: The above directory hierarchy is not fixed by the lobot 00135 controller. It is simply how we configured the controller to work 00136 while running these experiments and collecting the data. 00137 00138 The objective of this program is to load an entire dataset from each 00139 of the above directories and then write out a results file whose 00140 format is shown below: 00141 00142 TTI LGMD Spike Rate Predicted TTI Confidence Level 00143 --- --------------- ------------- ---------------- 00144 mean stdev mean stdev mean stdev 00145 00146 Since each dataset's results can be computed independently of every 00147 other dataset, we use multiple threads to process several datasets in 00148 parallel. Given the Bayesian TTI prediction experiments' data root 00149 directory, this program finds all the subdirectories containing log 00150 files and then launches as many threads as CPU's available to walk 00151 through this directory list and perform the necessary log file 00152 parsing. 00153 */ 00154 00155 // //////////////////////////////////////////////////////////////////// // 00156 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00157 // by the University of Southern California (USC) and the iLab at USC. // 00158 // See http://iLab.usc.edu for information about this project. // 00159 // //////////////////////////////////////////////////////////////////// // 00160 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00161 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00162 // in Visual Environments, and Applications'' by Christof Koch and // 00163 // Laurent Itti, California Institute of Technology, 2001 (patent // 00164 // pending; application number 09/912,225 filed July 23, 2001; see // 00165 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00166 // //////////////////////////////////////////////////////////////////// // 00167 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00168 // // 00169 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00170 // redistribute it and/or modify it under the terms of the GNU General // 00171 // Public License as published by the Free Software Foundation; either // 00172 // version 2 of the License, or (at your option) any later version. // 00173 // // 00174 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00175 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00176 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00177 // PURPOSE. See the GNU General Public License for more details. // 00178 // // 00179 // You should have received a copy of the GNU General Public License // 00180 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00181 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00182 // Boston, MA 02111-1307 USA. // 00183 // //////////////////////////////////////////////////////////////////// // 00184 // 00185 // Primary maintainer for this file: mviswana usc edu 00186 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/LobayMain.C $ 00187 // $Id: LobayMain.C 14083 2010-09-30 13:59:37Z mviswana $ 00188 // 00189 00190 //--------------------------- LIBRARY CHECKS ---------------------------- 00191 00192 #if !defined(INVT_HAVE_BOOST_PROGRAM_OPTIONS) 00193 00194 #include <iostream> 00195 00196 int main() 00197 { 00198 std::cerr << "Sorry, this program requires the following libraries:\n" 00199 << "\tlibboost_program_options\n\n" ; 00200 std::cerr << "Please ensure development packages for above libraries " 00201 << "are installed\n" 00202 << "and then rebuild this program to get it to work.\n" ; 00203 return 255 ; 00204 } 00205 00206 #else // various required libraries available 00207 00208 //------------------------------ HEADERS -------------------------------- 00209 00210 // lobot headers 00211 #include "Robots/LoBot/baylog/LoBaylogAnalyzer.H" 00212 #include "Robots/LoBot/baylog/LoDirList.H" 00213 00214 #include "Robots/LoBot/thread/LoThread.H" 00215 #include "Robots/LoBot/config/LoConfigHelpers.H" 00216 00217 #include "Robots/LoBot/util/LoFile.H" 00218 #include "Robots/LoBot/util/LoSTL.H" 00219 #include "Robots/LoBot/util/LoSysConf.H" 00220 #include "Robots/LoBot/misc/LoExcept.H" 00221 #include "Robots/LoBot/misc/singleton.hh" 00222 00223 // Boost headers 00224 #include <boost/program_options.hpp> 00225 00226 // Standard C++ headers 00227 #include <iostream> 00228 #include <algorithm> 00229 #include <string> 00230 #include <vector> 00231 #include <stdexcept> 00232 00233 // Standard C headers 00234 #include <stdlib.h> 00235 00236 // Standard Unix headers 00237 #include <unistd.h> 00238 00239 //-------------------------- KNOB TWIDDLING ----------------------------- 00240 00241 namespace { 00242 00243 // Retrieve settings from global section of config file 00244 template<typename T> 00245 inline T conf(const std::string& key, const T& default_value) 00246 { 00247 return lobot::global_conf<T>(key, default_value) ; 00248 } 00249 00250 /// This inner class encapsulates various parameters that can be used to 00251 /// tweak different aspects of the Bayesian TTI prediction analysis. 00252 class MainParams : public lobot::singleton<MainParams> { 00253 /// The log files for the Bayesian time-to-impact prediction 00254 /// experiments are stored under a directory hierarchy that is 00255 /// arranged to match the different LGMD spike rate noise levels, 00256 /// robot speeds and delta values used to gather the data. 00257 /// 00258 /// This setting specifies the root directory under which the 00259 /// above-mentioned hierarchy of subdirectories appears. 00260 std::string m_root ; 00261 00262 /// The lobay program processes multiple datasets in parallel by first 00263 /// reading in the entire list of directories containing log files and 00264 /// then launching threads to process each dataset. This setting 00265 /// specifies a regular expression that will be used to match dataset 00266 /// directory names. 00267 std::string m_dataset ; 00268 00269 /// Private constructor because this is a singleton. 00270 MainParams() ; 00271 00272 // Boilerplate code to make generic singleton design pattern work 00273 friend class lobot::singleton<MainParams> ; 00274 00275 public: 00276 /// Accessing the various parameters. 00277 //@{ 00278 static const std::string& root() {return instance().m_root ;} 00279 static const std::string& dataset() {return instance().m_dataset ;} 00280 //@} 00281 } ; 00282 00283 // Parameters initialization 00284 MainParams::MainParams() 00285 : m_root(conf<std::string>("root_dir", "/tmp/lobay")), 00286 m_dataset(conf<std::string>("dataset_dir_name", 00287 "[01][025][05]/0\\.[1-4]/[0-2]\\.[0257][05]")) 00288 {} 00289 00290 // Shortcut 00291 typedef MainParams Params ; 00292 00293 } // end of local anonymous namespace encapsulating above helpers 00294 00295 //-------------------------- PROGRAM OPTIONS ---------------------------- 00296 00297 namespace { 00298 00299 // If the user does not supply the -c option on the command line, we will 00300 // fall back to the default config file name returned by this function. 00301 std::string default_config_file() 00302 { 00303 return std::string(getenv("HOME")) + "/.lobayrc" ; 00304 } 00305 00306 // Helper function to take care of the annoying details of using 00307 // Boost.program_options to get at the command line arguments. 00308 // 00309 // DEVNOTE: This code is lifted almost verbatim from the 00310 // Boost.program_options tutorial and examples that comes with the Boost 00311 // documentation. There may be better (i.e., neater, more effective, 00312 // clearer, more efficient, whatever) ways to use the library. 00313 std::string parse(int argc, char* argv[]) 00314 { 00315 std::string config_file_name ; 00316 00317 // Specify the command line options 00318 namespace po = boost::program_options ; 00319 po::options_description options("Command line options") ; 00320 options.add_options() 00321 // the -c option for specifying the config file 00322 ("config-file,c", 00323 po::value<std::string>(&config_file_name)-> 00324 default_value(default_config_file()), 00325 "specify configuration settings file") ; 00326 00327 // Setup done: now parse argc and argv... 00328 po::variables_map varmap ; 00329 po::store(po::parse_command_line(argc, argv, options), varmap) ; 00330 po::notify(varmap) ; 00331 00332 return config_file_name ; 00333 } 00334 00335 // Helper function to read the lobay program's config file. If the 00336 // specified file doesn't exist, the program will rely on default 00337 // settings. If the config file contains problematic constructs, an error 00338 // will be reported but the program will continue on, simply ignoring the 00339 // bad settings. 00340 void load_config_file(const std::string& file_name) 00341 { 00342 using namespace lobot ; 00343 try 00344 { 00345 Configuration::load(file_name) ; 00346 //Configuration::dump() ; 00347 } 00348 catch (customization_error& e) 00349 { 00350 if (e.code() != NO_SUCH_CONFIG_FILE) 00351 std::cerr << e.what() << '\n' ; 00352 } 00353 } 00354 00355 } // end of local anonymous namespace encapsulating above helpers 00356 00357 //------------------------- METRICS ANALYSIS ---------------------------- 00358 00359 namespace { 00360 00361 // This function reads all the Robolocust metrics logs in the specified 00362 // directory and combines them to produce the desired average trajectory 00363 // and other pertinent info. 00364 void process_datasets(const std::vector<std::string>& dirs) 00365 { 00366 using namespace lobot ; 00367 00368 // Analyze datasets in parallel 00369 DirList dir_list(dirs) ; 00370 const int T = std::min(dir_list.size(), num_cpu()) ; 00371 std::vector<BaylogAnalyzer*> analyzer_threads ; 00372 analyzer_threads.reserve(T) ; 00373 for (int i = 0; i < T; ++i) 00374 analyzer_threads.push_back(BaylogAnalyzer::create(dir_list)) ; 00375 00376 // Now we wait for the analyzer threads to do their thing... 00377 // 00378 // DEVNOTE: If we don't pause this main thread briefly before invoking 00379 // the wait API, we could be in big trouble because the scheduler 00380 // might decide to go right on executing this thread before it begins 00381 // any of the analyzer threads, in which case the wait will fail as 00382 // none of the other threads would have started up just yet, i.e., the 00383 // thread count would be zero, causing this thread to mistakenly 00384 // conclude that all the analyzers are done and that it is safe to 00385 // delete them. When those threads then start executing, they will try 00386 // to reference their associated analyzer objects, which, because they 00387 // no longer exist, will end up taking us on a scenic, albeit short 00388 // and tragic, bus ride to Segfault City. 00389 // 00390 // DEVNOTE 2: A better way to do this is to use another condition 00391 // variable rather than the one used implicitly by Thread::wait_all(), 00392 // over which we have no control. For example, we could wait on a 00393 // condition variable that tests a counter going all the way up to T, 00394 // the number of analyzer threads created. When each analyzer is done, 00395 // it will increment the counter. Since we would have explicit control 00396 // over this variable over here, we can be assured that this thread 00397 // won't mistakenly assume all the analyzers are done. 00398 // 00399 // Of course, the Right Thing is good and all. But why bother? A short 00400 // sleeps works just as well for most practical purposes... 00401 sleep(1) ; // HACK! to prevent segfault; see comment above 00402 Thread::wait_all() ; 00403 purge_container(analyzer_threads) ; 00404 } 00405 00406 } // end of local anonymous namespace encapsulating above helpers 00407 00408 //------------------------------- MAIN ---------------------------------- 00409 00410 int main(int argc, char* argv[]) 00411 { 00412 int ret = 0 ; 00413 try 00414 { 00415 load_config_file(parse(argc, argv)) ; 00416 process_datasets(lobot::find_dir(Params::root(), Params::dataset())) ; 00417 } 00418 catch (lobot::uhoh& e) 00419 { 00420 std::cerr << e.what() << '\n' ; 00421 ret = e.code() ; 00422 } 00423 catch (std::exception& e) 00424 { 00425 std::cerr << e.what() << '\n' ; 00426 ret = 127 ; 00427 } 00428 catch(...) 00429 { 00430 std::cerr << "unknown exception\n" ; 00431 ret = 255 ; 00432 } 00433 return ret ; 00434 } 00435 00436 //----------------------------------------------------------------------- 00437 00438 #endif // library checks 00439 00440 /* So things look consistent in everyone's emacs... */ 00441 /* Local Variables: */ 00442 /* indent-tabs-mode: nil */ 00443 /* End: */