00001 /** 00002 \file Robots/LoBot/misc/LoFastSLAM.C 00003 \brief This file implements the non-inline member functions of the 00004 lobot::FastSLAM 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/slam/LoFastSLAM.C $ 00039 // $Id: LoFastSLAM.C 13575 2010-06-17 01:42:18Z mviswana $ 00040 // 00041 00042 //------------------------------ HEADERS -------------------------------- 00043 00044 // lobot headers 00045 #include "Robots/LoBot/slam/LoFastSLAM.H" 00046 #include "Robots/LoBot/slam/LoMap.H" 00047 #include "Robots/LoBot/slam/LoSlamParams.H" 00048 00049 #include "Robots/LoBot/config/LoConfigHelpers.H" 00050 #include "Robots/LoBot/util/LoStats.H" 00051 #include "Robots/LoBot/util/LoSTL.H" 00052 #include "Robots/LoBot/util/LoString.H" 00053 #include "Robots/LoBot/util/LoMath.H" 00054 #include "Robots/LoBot/util/range.hh" 00055 #include "Robots/LoBot/util/triple.hh" 00056 #include "Robots/LoBot/misc/singleton.hh" 00057 00058 // INVT utilities 00059 #include "Util/log.H" 00060 00061 // Boost headers 00062 #include <boost/bind.hpp> 00063 00064 // Standard C++ headers 00065 #include <string> 00066 #include <numeric> 00067 #include <algorithm> 00068 #include <functional> 00069 #include <iterator> 00070 #include <utility> 00071 00072 //----------------------------- NAMESPACE ------------------------------- 00073 00074 namespace lobot { 00075 00076 //-------------------------- INITIALIZATION ----------------------------- 00077 00078 // Generator function object to create a new particle 00079 namespace { 00080 00081 class new_particle { 00082 float initial_weight ; 00083 std::vector<int> initial_scan ; 00084 boost::shared_ptr<OccGrid> known_map ; 00085 public: 00086 new_particle(float initial_weight, const LRFData& initial_scan) ; 00087 new_particle(float, const LRFData&, const boost::shared_ptr<OccGrid>&) ; 00088 Particle operator()() const ; 00089 } ; 00090 00091 new_particle::new_particle(float w, const LRFData& lrf) 00092 : initial_weight(w), initial_scan(lrf.distances()) 00093 {} 00094 00095 new_particle:: 00096 new_particle(float w, const LRFData& lrf, const boost::shared_ptr<OccGrid>& m) 00097 : initial_weight(w), initial_scan(lrf.distances()), known_map(m) 00098 {} 00099 00100 Particle new_particle::operator()() const 00101 { 00102 if (known_map) 00103 return Particle(initial_weight, initial_scan, known_map) ; 00104 else 00105 return Particle(initial_weight, initial_scan) ; 00106 } 00107 00108 } // end of local anonymous namespace encapsulating above helper 00109 00110 // Since this implementation of FastSLAM uses laser range scan matching 00111 // to reduce the number of particles required by the algorithm, it needs 00112 // to be passed an initial scan of range readings during initialization. 00113 FastSLAM::FastSLAM(const LRFData& lrf) 00114 : m_w_slow(0), m_w_fast(0) 00115 { 00116 const int N = SlamParams::num_particles() ; 00117 m_particles.reserve(N) ; 00118 std::generate_n(std::back_inserter(m_particles), N, 00119 new_particle(1.0f/N, lrf)) ; 00120 } 00121 00122 FastSLAM:: 00123 FastSLAM(const LRFData& lrf, const boost::shared_ptr<OccGrid>& known_map) 00124 : m_w_slow(0), m_w_fast(0) 00125 { 00126 const int N = SlamParams::num_particles() ; 00127 m_particles.reserve(N) ; 00128 std::generate_n(std::back_inserter(m_particles), N, 00129 new_particle(1.0f/N, lrf, known_map)) ; 00130 } 00131 00132 //------------------------ FASTSLAM ALGORITHM --------------------------- 00133 00134 namespace { 00135 00136 // Helper function object to update a particle's state using the current 00137 // control and data inputs. 00138 class update_particle { 00139 std::vector<int> lrf; 00140 const Odometry& ut ; // control input at (current) time step t 00141 std::vector<int> zt ; // sensor data at (current) time step t 00142 mutable int n ; // particle number (for debugging) 00143 const bool slam_mode ;// are we doing MCL only or full SLAM? 00144 public: 00145 update_particle(const Odometry&, const LRFData&) ; 00146 void operator()(Particle&) const ; 00147 } ; 00148 00149 update_particle::update_particle(const Odometry& odometry, const LRFData& L) 00150 : lrf(L.distances()), ut(odometry), n(0), slam_mode(SlamParams::slam_mode()) 00151 { 00152 const int begin = SlamParams::beam_start() ; 00153 const int end = SlamParams::beam_end() ; 00154 const int step = SlamParams::beam_step() ; 00155 00156 const int N = (end - begin)/step + 1 ; 00157 zt.reserve(N) ; 00158 for (int angle = begin; angle <= end; angle += step) 00159 zt.push_back(L[angle]) ; 00160 } 00161 00162 void update_particle::operator()(Particle& P) const 00163 { 00164 //LERROR("--------------- Particle #%-4d ---------------", n++) ; 00165 P.apply_motion_model(ut, lrf) ; // full LRF scan for scan matching 00166 P.apply_sensor_model(zt) ; // limited scan for filter's 00167 if (slam_mode) // sensor-based correction steps 00168 P.update_map(zt) ; 00169 } 00170 00171 // Divide each particle's weight by the sum of all their weights 00172 void normalize_weights(std::vector<Particle>& particles) 00173 { 00174 accumulator<float> sum = 00175 std::transform(particles.begin(), particles.end(), accumulator<float>(0), 00176 std::mem_fun_ref(&Particle::weight)) ; 00177 std::for_each(particles.begin(), particles.end(), 00178 std::bind2nd(std::mem_fun_ref(&Particle::normalize), 00179 1/sum.value())) ; 00180 00181 //LERROR("w_sum = %12.8f (%14.8e)", sum.value(), sum.value()) ; 00182 /* 00183 const int N = particles.size() ; 00184 for (int i = 0; i < N; ++i) { 00185 float w = particles[i].weight() ; 00186 LERROR("particle[%4d].weight = %12.8f (%14.8e)", i, w, w) ; 00187 } 00188 // */ 00189 } 00190 00191 // This function computes the effective sample size of the particle 00192 // population by taking the reciprocal of the sum of the squares of the 00193 // normalized individual particle weights. 00194 int effective_sample_size(const std::vector<Particle>& particles) 00195 { 00196 float sum = 00197 std::accumulate(particles.begin(), particles.end(), 0.0f, 00198 boost::bind(sum_of_squares<float>(), _1, 00199 boost::bind(&Particle::weight, _2))) ; 00200 int N = round(1/sum) ; 00201 //LERROR("w_sum_sqr = %12.8f, Neff = %d", sum, N) ; 00202 return N ; 00203 } 00204 00205 } // end of local anonymous namespace encapsulating above helpers 00206 00207 // This function implements the FastSLAM algorithm's particle update and 00208 // resampling steps. Resampling is carried out only when the effective 00209 // number of particles falls below a certain threshold. This strategy is 00210 // described in many different papers on particle filters. See, for 00211 // example, "Improving Grid-based SLAM with Rao-Blackwellized Particle 00212 // Filters by Adaptive Proposals and Selective Resampling" by Grisetti, 00213 // Stachniss and Burgard, ICRA 2005. 00214 // 00215 // NOTE: The effective sample size check can be turned off by configuring 00216 // the ESS threshold to be a negative number or to a number greater than 00217 // the number of particles. If the user has switched off the ESS check, 00218 // then resampling will occur after each and every FastSLAM update. 00219 void FastSLAM::update(const Odometry& ut, const LRFData& zt) 00220 { 00221 // First, update all particles using latest control and sensor inputs 00222 std::for_each(m_particles.begin(), m_particles.end(), 00223 update_particle(ut, zt)) ; 00224 00225 // Then, normalize the particle weights and resample particle 00226 // population based on their weights. 00227 //LERROR("=============== Resampling... ===============") ; 00228 normalize_weights(m_particles) ; 00229 update_averages() ; 00230 00231 const int N = m_particles.size() ; 00232 const int T = SlamParams::ess_threshold() ; 00233 if (T <= 0 || T > N || effective_sample_size(m_particles) < T) 00234 { 00235 // Low-variance resampling; see "Probabilistic Robotics" by Thrun, 00236 // Burgard and Fox (pg. 110). 00237 Particles particles ; 00238 particles.reserve(N) ; 00239 00240 float n = 1.0f/N ; 00241 float r = randomf(0, n) ; 00242 float c = m_particles[0].weight() ; 00243 float w = std::max(0.0f, 1 - m_w_fast/m_w_slow); 00244 int i = 0 ; 00245 //LERROR("n = %12.8f, r = %12.8f, w = %12.8f", n, r, w) ; 00246 00247 float U = r ; 00248 for (int j = 0; j < N; ++j, U += n) 00249 { 00250 if (randomf(0, 1) < w) 00251 { 00252 //LERROR("randomizing particle #%-4d", i) ; 00253 Particle tmp = m_particles[i] ; 00254 tmp.randomize() ; 00255 particles.push_back(tmp) ; 00256 } 00257 else 00258 { 00259 //LERROR("U = %12.8f, c = %12.8f", U, c) ; 00260 while (U > c) { 00261 ++i ; 00262 c += m_particles[i].weight() ; 00263 //LERROR("c = %12.8f, i = %4d", c, i) ; 00264 } 00265 //LERROR("U = %12.8f, c = %12.8f, i = %4d", U, c, i) ; 00266 /* 00267 LERROR("particle[%4d] = %-4d (w: %12.8f)", 00268 j, i, m_particles[i].weight()) ; 00269 // */ 00270 particles.push_back(m_particles[i]) ; 00271 } 00272 } 00273 00274 m_particles = particles ; 00275 } 00276 } 00277 00278 // This method updates the short and long-term particle weight averages, 00279 // which are used in the resampling step to determine when to insert 00280 // random particles to help deal with mislocalizations and also the 00281 // overall number of random particles that will be inserted. 00282 void FastSLAM::update_averages() 00283 { 00284 float w_avg = mean<float>(m_particles.begin(), m_particles.end(), 00285 std::mem_fun_ref(& Particle::weight)) ; 00286 m_w_slow += SlamParams::alpha_slow() * (w_avg - m_w_slow) ; 00287 m_w_fast += SlamParams::alpha_fast() * (w_avg - m_w_fast) ; 00288 } 00289 00290 //-------------------- PARTICLE DENSITY EXTRACTION ---------------------- 00291 00292 namespace { 00293 00294 // This helper class implements a function object that can be used to 00295 // compute the overall error in the relevant state variables between the 00296 // particle with maximum weight and any other particle. 00297 // 00298 // The type parameters for this template are: 00299 // 00300 // 1. T -- the state variable for which we are interested in the error 00301 // 2. C -- a "converter" that returns a T given a lobot::Particle 00302 // 3. E -- a callable that makes the actual error determination 00303 // 00304 // Since this class is used in conjunction with Robolocust's 00305 // implementation of FastSLAM, the relevant state variables we're 00306 // interested in are either lobot::OccGrid or lobot::Pose. Thus, T would 00307 // be one of these two classes. 00308 // 00309 // This function object is intended to be used in conjunction with an STL 00310 // algorithm that iterates over a container of Particles. For each 00311 // particle, it will return an estimate of the error in the state of that 00312 // particle against the state of the particle with maximum weight. When 00313 // this function object is created, it will be passed the state 00314 // corresponding to the Particle with maximum weight. In order to 00315 // retrieve the state for each Particle in the sequence being iterated 00316 // over, the object will use the Particle-to-T converter (type C). 00317 // 00318 // Since the lobot::Particle class provides member functions to retrieve 00319 // that Particle's Pose and OccGrid map, type C will usually be an 00320 // adapter function object that calls the relevant member function of the 00321 // Particle class to "convert" the Particle to the state type T. An 00322 // example of such an adapter would be a function object returned by 00323 // std::mem_fun_ref. 00324 // 00325 // The E function will be passed two state variables: the first 00326 // corresponding to the Particle with maximum weight and the second 00327 // corresponding to the "current" Particle in the sequence being iterated 00328 // over. This function is expected to return a floating point number that 00329 // somehow measures the error between the current Particle's state 00330 // variable and the state of the Particle with maximum weight. 00331 // 00332 // As mentioned above, this function object is intended to be used in 00333 // conjunction with an STL algorithm that iterates over a container of 00334 // Particles. For each Particle in the sequence, it will return an 00335 // estimate of the error in the state of that Particle against the state 00336 // of the Particle with maximum weight. In addition to this error value, 00337 // it will also return the index of the Particle for which it just 00338 // performed the error evaluation. These two items are returned via an 00339 // STL pair, error value first and particle index second. 00340 template<typename T, typename C, typename E = float (*)(const T&, const T&)> 00341 class compute_error { 00342 const T& max ; // state corresponding to Particle with max weight 00343 const C& p2t ; // Particle-to-T "converter" 00344 const E& err ; // function to evaluate error between max & other Particles 00345 mutable int i; // index of current Particle in the Particle list 00346 public: 00347 compute_error(const T&, const C&, const E&) ; 00348 std::pair<float, int> operator()(const Particle&) const ; 00349 } ; 00350 00351 // When the compute_error function object is created, it should be passed 00352 // the state of the particle with maximum weight, a function or function 00353 // object that retrieves/converts a Particle to a state variable (type T) 00354 // and a callable that can be used to estimate the error for this state 00355 // variable. 00356 template<typename T, typename C, typename E> 00357 compute_error<T,C,E>::compute_error(const T& t, const C& c, const E& e) 00358 : max(t), p2t(c), err(e), i(0) 00359 {} 00360 00361 // As mentioned earlier, the compute_error function object is meant to be 00362 // used with a container of Particles. Each time this function object is 00363 // called, it will evaluate the error in the state of the "current" 00364 // Particle against that of the Particle with maximum weight using the 00365 // error function it was given when it was created. 00366 template<typename T, typename C, typename E> 00367 std::pair<float, int> 00368 compute_error<T,C,E>:: 00369 operator()(const Particle& P) const 00370 { 00371 return std::make_pair(err(max, p2t(P)), i++) ; 00372 } 00373 00374 // This function evaluates the error in the occupancy grids of two 00375 // particles. To measure the amount of error between two occupancy grids, 00376 // we perform a pixel/cell-wise comparison and return the overall sum of 00377 // the squares of the differences between the individual cells. 00378 float map_error(const OccGrid& m1, const OccGrid& m2) 00379 { 00380 if (&m1 == &m2) // same map ==> error will be zero 00381 return 0 ; // so don't waste time computing the obvious 00382 00383 using boost::bind ; 00384 accumulator<float> acc = 00385 std::transform(m1.begin(), m1.end(), m2.begin(), accumulator<float>(0), 00386 bind(sqr<float>, bind(std::minus<float>(), _1, _2))) ; 00387 return acc.value() ; // sum of square errors 00388 } 00389 00390 // This function evaluates the error in the poses of two Particles. The 00391 // total error in the pose of two particles is evaluated as simply the 00392 // sum of the squares of the differences between the individual pose 00393 // variables (x, y, theta). 00394 float pose_error(const Pose& p1, const Pose& p2) 00395 { 00396 // NOTE: Cannot apply (&p1 == &p2) check here to bypass useless zero 00397 // computation (as in map_error above) because Particle::pose() 00398 // returns a copy of its Pose object whereas Particle::map() returns a 00399 // reference to its OccGrid object. Therefore, the addresses of this 00400 // function's two parameters will always be different. 00401 // 00402 // DEVNOTE: We could change Particle::pose() so that it too returns a 00403 // reference to its Pose object. However, the error computation for 00404 // Pose objects is much less strenuous than the error computation for 00405 // OccGrid objects. So the object address check for bypassing the 00406 // error computation here is not as imperative as for the map case. 00407 return sqr(p2.x() - p1.x()) 00408 + sqr(p2.y() - p1.y()) 00409 + sqr(p2.t() - p1.t()) ; 00410 } 00411 00412 // This helper function returns a function object for computing the map 00413 // error between the given map and the map associated with any other 00414 // Particle. It is meant to be used for the computation of the particle 00415 // filter's current best hypothesis regarding the occupancy grid map of 00416 // the robot's surroundings. Therefore, the map object passed to this 00417 // function should be the map of the Particle with maximum weight. 00418 // 00419 // Additionally, this function should be passed a "converter" (type C) 00420 // that can return a lobot::OccGrid given a lobot::Particle. Usually, 00421 // this would be a member function adapter such as the thing returned by 00422 // std::mem_fun_ref or boost::bind. 00423 // 00424 // With these two parameters in hand, this function will instantiate the 00425 // compute_error function object with the appropriate types and arguments 00426 // and return this object properly setup to perform the map error 00427 // computations. 00428 template<typename C> 00429 compute_error<OccGrid, C> 00430 error_function(const OccGrid& max_map, const C& particle_to_occgrid) 00431 { 00432 return compute_error<OccGrid, C>(max_map, particle_to_occgrid, map_error) ; 00433 } 00434 00435 // This helper function returns a function object for computing the pose 00436 // error between the given Pose and the Pose associated with any other 00437 // Particle. It is meant to be used for the computation of the particle 00438 // filter's current best hypothesis regarding the robot's pose. 00439 // Therefore, the Pose object passed to this function should be the Pose 00440 // of the Particle with maximum weight. 00441 // 00442 // Additionally, this function should be passed a "converter" (type C) 00443 // that can return a lobot::Pose given a lobot::Particle. Usually, this 00444 // would be a member function adapter such as the thing returned by 00445 // std::mem_fun_ref or boost::bind. 00446 // 00447 // With these two parameters in hand, this function will instantiate the 00448 // compute_error function object with the appropriate types and arguments 00449 // and return this object properly setup to perform the pose error 00450 // computations. 00451 template<typename C> 00452 compute_error<Pose, C> 00453 error_function(const Pose& max_pose, const C& particle_to_pose) 00454 { 00455 return compute_error<Pose, C>(max_pose, particle_to_pose, pose_error) ; 00456 } 00457 00458 // To construct the particle filter's current best hypothesis regarding 00459 // the robot's state (either the occupancy map or the robot pose), we use 00460 // a robust mean computed thusly: 00461 // 00462 // 1. Find particle with max weight 00463 // 2. Pick K particles whose maps/poses most closely match above particle 00464 // 3. Return average of above K maps/poses 00465 // 00466 // Since the procedure for both occupancy maps and poses is the same, we 00467 // use a template function to implement it. This function is parametrized 00468 // on the following types: 00469 // 00470 // 1. T -- the type of state extracted from the particle population 00471 // 2. C -- a "converter" function for returning a T from a Particle 00472 // 00473 // Since this function is used in conjunction with FastSLAM, the state 00474 // we're concerned with is either the occupancy grid map of the robot's 00475 // surroundings or the robot's pose. Thus, T will be either 00476 // lobot::OccGrid or lobot::Pose. 00477 // 00478 // The type C will be a function or function object that takes a Particle 00479 // and returns a T (OccGrid or Pose) from that Particle. Since the 00480 // lobot::Particle class provides member functions for returning that 00481 // particle's hypothesis regarding the map and pose, type C will usually 00482 // be a member function adapter (e.g., returned by std::mem_fun_ref). 00483 // 00484 // The parameters to this function are: 00485 // 00486 // 1. particles -- the particle filter's current list of particles 00487 // 2. p2t -- a function/object that takes a Particle and returns a T 00488 // 00489 // DEVNOTE: Since this function's argument list does not contain a T, 00490 // which only figures in its return type, automatic type deduction will 00491 // not work (as return types don't count when determining a function's 00492 // signature). 00493 template<typename T, typename C> 00494 T current_best_hypothesis(const std::vector<Particle>& particles, C p2t) 00495 { 00496 // Find particle with max weight 00497 std::vector<Particle>::const_iterator max = 00498 std::max_element(particles.begin(), particles.end(), 00499 boost::bind(std::less<float>(), 00500 boost::bind(&Particle::weight, _1), 00501 boost::bind(&Particle::weight, _2))) ; 00502 00503 // Special case: don't bother with finding top K matches to above 00504 // particle when K is one. If K is one, then the particle with max 00505 // weight is the thing we're interested in. 00506 const int K = SlamParams::num_matches() ; 00507 if (K == 1) 00508 return p2t(*max) ; 00509 00510 // Compute overall errors between the relevant state variable (T) of 00511 // all other particles and the corresponding state variable of the 00512 // particle with max weight. These errors are stored along with their 00513 // corresponding particle indices. 00514 typedef std::pair<float, int> ErrIndPair ; 00515 std::vector<ErrIndPair> E ; 00516 E.reserve(particles.size()) ; 00517 std::transform(particles.begin(), particles.end(), 00518 std::back_inserter(E), error_function(p2t(*max), p2t)) ; 00519 00520 // Partially sort the errors so that the top K particles whose states 00521 // are closest to the state of the particle with max weight are at the 00522 // front of the error-index pair vector. 00523 std::partial_sort(E.begin(), E.begin() + K, E.end(), 00524 boost::bind(std::less<float>(), 00525 boost::bind(get_first<ErrIndPair>, _1), 00526 boost::bind(get_first<ErrIndPair>, _2))) ; 00527 00528 // Compute final state as average of states of top K particles closest 00529 // to particle with max weight. 00530 T t ; 00531 for (int i = 0; i < K; ++i) 00532 t += p2t(particles[E[i].second]) ; 00533 return t * (1.0f/K) ; 00534 } 00535 00536 } // end of local anonymous namespace encapsulating above helpers 00537 00538 // This method computes the particle filter's current best hypothesis 00539 // regarding the occupancy grid map of the robot's surroundings using a 00540 // robust mean, i.e., a mean of the maps of the K particles whose maps 00541 // most closesly resemble the map built by the particle with maximum 00542 // weight. 00543 OccGrid FastSLAM::current_map() const 00544 { 00545 return current_best_hypothesis<OccGrid>(m_particles, 00546 std::mem_fun_ref(&Particle::map)) ; 00547 } 00548 00549 // This method computes the particle filter's current best hypothesis 00550 // regarding the robot's pose using a robust mean, i.e., a mean of the 00551 // poses of the K particles whose poses most closesly match the pose 00552 // estimated by the particle with maximum weight. 00553 Pose FastSLAM::current_pose() const 00554 { 00555 return current_best_hypothesis<Pose>(m_particles, 00556 std::mem_fun_ref(&Particle::pose)) ; 00557 } 00558 00559 //----------------------- VISUALIZATION SUPPORT ------------------------- 00560 00561 std::vector<Particle::Viz> FastSLAM::viz() const 00562 { 00563 std::vector<Particle::Viz> v ; 00564 v.reserve(m_particles.size()) ; 00565 std::transform(m_particles.begin(), m_particles.end(), 00566 std::back_inserter(v), std::mem_fun_ref(&Particle::viz)) ; 00567 return v ; 00568 } 00569 00570 //----------------------------- CLEAN-UP -------------------------------- 00571 00572 FastSLAM::~FastSLAM(){} 00573 00574 //----------------------------------------------------------------------- 00575 00576 } // end of namespace encapsulating this file's definitions 00577 00578 /* So things look consistent in everyone's emacs... */ 00579 /* Local Variables: */ 00580 /* indent-tabs-mode: nil */ 00581 /* End: */