HawkSlammer.C

00001 // File: HawkSlammer.C
00002 // Author: Josh Villbrandt <josh.villbrandt@usc.edu>
00003 // Date: April 2010
00004 
00005 #include "Robots/BeoHawk/computer/HawkSlammer.H"
00006 
00007 // ######################################################################
00008 HawkSlammer::HawkSlammer(std::string myName, int argc, char* argv[])
00009 : HawkAgent(myName, argc, argv),
00010 window(Dims(320, 240)) {
00011         // Help section
00012         helpTitle = "HawkSlammer";
00013         helpDescription = "Runs an implementation of DP-SLAM for the BeoHawk.";
00014         helpOptions.push_back("\t--pause\t\t(false) Wait for keyboard input after each iteration.");
00015         helpOptions.push_back("\t--mode\t\t(simulation) Try realTimeDrop, realTimeCombine, or simulation.");
00016         helpOptions.push_back("\t--particles\t(100) Number of particles in the simulation.");
00017         helpOptions.push_back("\t--resolution\t(30) Size of each grid cell in mm.");
00018         helpOptions.push_back("\t--maxLaserDistance\t(6000) Largest distance that the laser scanner can detect.");
00019         
00020         // Parameters
00021         pauseEachStep = loadBoolParameter("pause", false);
00022         numberOfParticles = loadIntParameter("particles", 100);
00023         mapResolution = loadIntParameter("resolution", 30); // mm per grid cell
00024         maxLaserDistance = loadIntParameter("maxLaserDistance", 6000);
00025         robotXStdDev = loadDoubleParameter("robotXStdDev", 50); // 50 mm
00026         robotYStdDev = loadDoubleParameter("robotYStdDev", 50); // 50 mm
00027         robotThetaStdDev = loadDoubleParameter("robotThetaStdDev", 0.1); // ~6 deg
00028         scannerVariance = loadDoubleParameter("scannerVariance", 20); // 20 mm
00029         std::string modeIn = loadStringParameter("mode", "simulation");
00030         if(modeIn == "realTimeDrop") mode = REALTIME_DROP;
00031         else if(modeIn == "realTimeCombine") mode = REALTIME_COMBINE;
00032         else mode = SIMULATION;
00033         
00034         // Initialize SLAM
00035         initializeSlam();
00036 }
00037 
00038 // ######################################################################
00039 void HawkSlammer::registerTopics() {
00040         registerPublisher("SlamDataMessage");
00041         registerSubscription("SensorDataMessage");
00042 }
00043 
00044 // ######################################################################
00045 bool HawkSlammer::scheduler() {
00046         if(sensorDataMessages.size() > 0) {
00047                 slam();
00048                 return true;
00049         }
00050         
00051         return false;
00052 }
00053 
00054 // ######################################################################
00055 void HawkSlammer::catchMessage(const HawkMessages::MessagePtr& hawkMessage, const Ice::Current&) {
00056         if(hawkMessage->ice_isA("::HawkMessages::SensorDataMessage"))
00057         {
00058                 // Save message to storage queue
00059                 HawkMessages::SensorDataMessagePtr msg = HawkMessages::SensorDataMessagePtr::dynamicCast(hawkMessage);
00060                 sensorDataMessages.push_back(msg);
00061                 wakeUp();
00062         }
00063 }
00064 
00065 // ######################################################################
00066 void HawkSlammer::initializeSlam() {
00067         // Initialize grid
00068         int initialPixels = (int)(2 * maxLaserDistance / mapResolution);
00069         occupancyGrid = occupancyMap_t(initialPixels, initialPixels, NO_INIT);
00070         
00071         // Initialize tree head
00072         boost::shared_ptr<Particle> head(new Particle());
00073         head->parent.reset();
00074         head->robotPose.x = initialPixels/2;
00075         head->robotPose.y = initialPixels/2;
00076         head->robotPose.z = 0;
00077         head->robotPose.theta = 0;
00078         head->probability = 1;
00079         ancestryTree.head = head;
00080         
00081         // Initialize tree leaves
00082         ancestryTree.leaves.push_back(head);
00083 }
00084 
00085 // ######################################################################
00086 void HawkSlammer::slam() {
00087         std::cout << "slam(): " << sensorDataMessages.size() << " messages in queue" << std::endl;
00088         
00089         // Select correct message
00090         SensorDataMessagePtr msg;
00091         if(mode == REALTIME_COMBINE) {
00092                 // create a pseudo message by adding all attemptedMoves
00093                 msg = sensorDataMessages.back();
00094                 for(size_t i = 0; i < (sensorDataMessages.size()-1); i++) {
00095                         // fill this in
00096                         // is not simply x += x if non-zero rotation
00097                 }
00098                 sensorDataMessages.clear();
00099         }
00100         else if(mode == REALTIME_DROP) {
00101                 // just take the last message received and drop the rest
00102                 msg = sensorDataMessages.front();
00103                 sensorDataMessages.clear();
00104         }
00105         else {
00106                 // just take the last message received and save the rest
00107                 msg = sensorDataMessages.front();
00108                 sensorDataMessages.erase(sensorDataMessages.begin());
00109         }
00110         
00111         // Algorithm core (SLAM)
00112         updateParticles(msg);
00113     sendSlamDataMessage(); // we can send this immediately and then worry about cleaning up
00114     resampleParticles();
00115         std::cout << "active particles: " << 1+countDescendants(ancestryTree.head) << std::endl;
00116         if(pauseEachStep) {
00117                 std::cout << "Please press enter to continue...";
00118                 std::cin.get();
00119                 std::cout << std::endl;
00120         }
00121     pruneParticles();
00122         
00123         // Debug items
00124         std::cout << "active particles: " << 1+countDescendants(ancestryTree.head) << std::endl;
00125         if(pauseEachStep) {
00126                 std::cout << "Please press enter to continue...";
00127                 std::cin.get();
00128                 std::cout << std::endl;
00129         }
00130 }
00131 
00132 // ######################################################################
00133 void HawkSlammer::updateParticles(SensorDataMessagePtr msg) {
00134         std::cout << "updateParticles()" << std::endl;
00135         
00136         // Prep
00137         ancestryTree.mostLikely.reset();
00138         double totalProbability = 0;
00139         
00140         // Apply new laser scan to every particle
00141         for(size_t i = 0; i < ancestryTree.leaves.size(); i++) {
00142                 std::cout << "Working with leaf #" << i << ". ";
00143                 // Add gaussian noise to attempted move to make our particle's move
00144                 Pose move = msg->attemptedMove;
00145                 move.x = (move.x + gaussian(robotXStdDev)) / mapResolution;
00146                 move.y = (move.y + gaussian(robotYStdDev)) / mapResolution;
00147                 move.theta += gaussian(robotThetaStdDev);
00148                 
00149                 // Add movement to absolute position while converting from robot frame of reference to world FoR
00150                 double worldTheta = cleanAngle(ancestryTree.leaves.at(i)->robotPose.theta + move.theta);
00151                 ancestryTree.leaves.at(i)->robotPose.theta = worldTheta; // theta = 0 along x axis
00152                 ancestryTree.leaves.at(i)->robotPose.x += move.x*cos(worldTheta) + move.y*sin(worldTheta);
00153                 ancestryTree.leaves.at(i)->robotPose.y += move.y*cos(worldTheta) - move.x*sin(worldTheta);
00154                 
00155                 // Apply each laser cast of the laser scan set
00156                 int offset = msg->scannerData.size()/2;
00157                 std::cout << "Applying laser scan... ";
00158                 for(int j = 0; j < (int)msg->scannerData.size(); j++) {
00159                         addLaserCast(ancestryTree.leaves.at(i), (j-offset)*msg->angularResolution, msg->scannerData.at(j) / mapResolution);
00160                 }
00161                 
00162                 // Quick score (only look around the endpoint of the scan)
00163                 
00164                 // Compute probability of entire laser scan (unnormalized)
00165                 double probabilityOfLaserScan = 0;
00166                 std::cout << "Scoring laser scan... ";
00167                 for(int j = 0; j < (int)msg->scannerData.size(); j++) {
00168                         probabilityOfLaserScan += scoreLaserCast(ancestryTree.leaves.at(i), (j-offset)*msg->angularResolution, msg->scannerData.at(j) / mapResolution);
00169                 }
00170                 
00171                 // Affect this particle's probability
00172                 std::cout << probabilityOfLaserScan << std::endl;
00173                 ancestryTree.leaves.at(i)->probability *= probabilityOfLaserScan; // DP-SLAM uses max(, th) here to provide a min-prob threshold
00174                 totalProbability += ancestryTree.leaves.at(i)->probability;
00175                 
00176                 // Search for most likely particle
00177                 if(!ancestryTree.mostLikely || ancestryTree.leaves.at(i)->probability > ancestryTree.mostLikely->probability) {
00178                         ancestryTree.mostLikely = ancestryTree.leaves.at(i);
00179                 }
00180         }
00181         
00182         // Normalize particles
00183         for(size_t i = 0; i < ancestryTree.leaves.size(); i++) {
00184                 if(totalProbability == 0) ancestryTree.leaves.at(i)->probability = 1 / ancestryTree.leaves.size();
00185                 else ancestryTree.leaves.at(i)->probability = ancestryTree.leaves.at(i)->probability / totalProbability;
00186         }
00187 }
00188 
00189 // ######################################################################
00190 double HawkSlammer::randomDouble() {
00191         return double(rand()) / (double(RAND_MAX) + 1.0);
00192 }
00193 
00194 // ######################################################################
00195 double HawkSlammer::gaussian(double stdDev) {
00196         double sum = 0;
00197         for(int i = 0; i < 12; i++) {
00198                 sum += randomDouble() * 2 * stdDev - stdDev;
00199         }
00200         return sum / 2;
00201 }
00202 
00203 // ######################################################################
00204 double HawkSlammer::cleanAngle(double angle) {
00205         while(angle > M_PI) angle -= 2*M_PI;
00206         while(angle < -M_PI) angle += 2*M_PI;
00207         return angle;
00208 }
00209 
00210 // ######################################################################
00211 void HawkSlammer::addLaserCast(boost::shared_ptr<Particle> treeLeaf, double scanAngle, double scanDistance) {
00212         if(scanDistance == 0) return;
00213         
00214         double startx = treeLeaf->robotPose.x;
00215         double starty = treeLeaf->robotPose.y;
00216         double theta = cleanAngle(treeLeaf->robotPose.theta + scanAngle);
00217 
00218         double overflow, slope; // Used for actually tracing the line
00219         int x, y, incX, incY, endx, endy;
00220         int xedge, yedge; // Used in computing the midpoint. Recompensates for which edge of the square the line entered from
00221         double dx, dy;
00222         double distance = scanDistance, error;
00223         
00224         // Precompute these for speed
00225         double secant = 1.0/fabs(cos(theta));
00226         double cosecant = 1.0/fabs(sin(theta));
00227         
00228         // Mark the final endpoint of the line trace, so that we know when to stop.
00229         // We keep the endpoint as both float and int.
00230         dx = (startx + (cos(theta) * distance));
00231         dy = (starty + (sin(theta) * distance));
00232         endx = (int) (dx);
00233         endy = (int) (dy);
00234 
00235         // Decide which x and y directions the line is travelling.
00236         // inc tells us which way to increment x and y. edge indicates whether we are computing
00237         // distance from the near or far edge.
00238         if (startx > dx) {
00239                 incX = -1;
00240                 xedge = 1;
00241         }
00242         else {
00243                 incX = 1;
00244                 xedge = 0;
00245         }
00246 
00247         if (starty > dy) {
00248                 incY = -1;
00249                 yedge = 1;
00250         }
00251         else {
00252                 incY = 1;
00253                 yedge = 0;
00254         }
00255 
00256         // Figure out whether primary motion is in the x or y direction. 
00257         // The two sections of code look nearly identical, with x and y reversed.
00258         if (fabs(startx - dx) > fabs(starty - dy)) {
00259                 // The given starting point is non-integer. The line therefore starts at some point partially set in to the starting
00260                 // square. Overflow starts at this offcenter amount, in order to make steps in the y direction at the right places.
00261                 y = (int)(starty);
00262                 overflow = starty - y;
00263                 
00264                 // We always use overflow as a decreasing number- therefore positive y motion needs to 
00265                 // adjust the overflow value accordingly.
00266                 if(incY == 1) overflow = 1.0 - overflow;
00267                 
00268                 // Compute the effective slope of the line.
00269                 slope = fabs(tan(theta));
00270 
00271                 // The first square is a delicate thing, as we aren't doing a full square traversal in
00272                 // either direction. So we figure out this strange portion of a step so that we can then
00273                 // work off of the axes later. 
00274                 // NOTE: we aren't computing the probability of this first step. Its a technical issue for
00275                 // simplicity, and the odds of the sensor sitting on top of a solid object are sufficiently 
00276                 // close to zero to ignore this tiny portion of a step. 
00277                 error = fabs(((int)(startx)+incX+xedge)-startx);
00278                 overflow = overflow - (slope*error);
00279                 // The first step is actually in the y direction, due to the proximity of starty to the y axis. 
00280                 if (overflow < 0.0) {
00281                         y = y + incY;
00282                         overflow = overflow + 1.0;
00283                 }
00284 
00285                 // Now we can start the actual line trace.
00286                 for(x = (int)(startx) + incX; x != endx; x = x + incX) {
00287                         overflow = overflow - slope;
00288 
00289                         // Compute the distance travelled in this square
00290                         if(overflow < 0.0) distance = (overflow+slope)*cosecant;
00291                         else distance = fabs(slope)*cosecant;
00292                         
00293                         // Update every grid square we cross as empty...
00294                         addObservation(treeLeaf, x, y, distance, 0);
00295 
00296                         // ...including the overlap in the minor direction
00297                         if(overflow < 0) {
00298                                 y = y + incY;
00299                                 distance = -overflow*cosecant;
00300                                 overflow = overflow + 1.0;
00301                                 addObservation(treeLeaf, x, y, distance, 0);
00302                         }
00303                 }
00304 
00305                 // Update the last grid square seen as having a hit.
00306                 // make sure the laser just didn't go the full distance and not hit something...
00307                 //if(addEnd) {
00308                         if(incX < 0) distance = fabs((x+1) - dx)*secant;
00309                         else distance = fabs(dx - x)*secant;
00310                         addObservation(treeLeaf, endx, endy, distance, 1);
00311                 //}
00312         }
00313         // This is the same as the previous block of code, with x and y reversed.
00314         else {
00315                 x = (int)(startx);
00316                 overflow = startx - x;
00317                 if(incX == 1) overflow = 1.0 - overflow;
00318                 slope = 1.0/fabs(tan(theta));
00319 
00320                 error = fabs(((int)(starty)+incY+yedge)-starty);
00321                 overflow = overflow - (error*slope);
00322                 if(overflow < 0.0) {
00323                         x = x + incX;
00324                         overflow = overflow + 1.0;
00325                 }
00326 
00327                 for(y = (int)(starty) + incY; y != endy; y = y + incY) {
00328                         overflow = overflow - slope;
00329                         if (overflow < 0) distance = (overflow+slope)*secant;
00330                         else distance = fabs(slope)*secant;
00331 
00332                         addObservation(treeLeaf, x, y, distance, 0);
00333 
00334                         if(overflow < 0.0) {
00335                                 x = x + incX;
00336                                 distance = -overflow*secant;
00337                                 overflow = overflow + 1.0;
00338                                 addObservation(treeLeaf, x, y, distance, 0);
00339                         }
00340                 }
00341 
00342                 //if(addEnd) {
00343                         if(incY < 0) distance = fabs(((y+1) - dy)/sin(theta));
00344                         else distance = fabs((dy - y)/sin(theta));
00345                         
00346                         addObservation(treeLeaf, endx, endy, distance, 1);
00347                 //}
00348         }
00349 }
00350 
00351 // ######################################################################
00352 void HawkSlammer::addObservation(boost::shared_ptr<Particle> treeLeaf, int x, int y, double distanceTraveled, int hit) {
00353         // Make new observation
00354         boost::shared_ptr<Observation> o(new Observation());
00355         o->source = treeLeaf;
00356         o->x = x;
00357         o->y = y;
00358         
00359         // Add empty map if needed
00360         occupancyMapPix_t treeMap;
00361         if(!occupancyGrid.getVal(x, y)) {
00362                 treeMap = occupancyMapPix_t(
00363                         new std::map<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >);
00364                 occupancyGrid.setVal(x, y, treeMap);
00365         }
00366         else treeMap = occupancyGrid.getVal(x, y);
00367         
00368         // Search for an ancestor observation
00369         boost::shared_ptr<Particle> ancestor = treeLeaf->parent;
00370         bool ancestorObservation = false;
00371         std::map< boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::const_iterator it;
00372         while(!ancestorObservation && ancestor) {
00373                 it = treeMap->find(ancestor);
00374                 if(it != treeMap->end()) ancestorObservation = true;
00375                 else ancestor = ancestor->parent;
00376         }
00377         
00378         // Set new observation's parameters accordingly
00379         if(ancestorObservation) {
00380                 o->laserTravel = it->second->laserTravel + distanceTraveled;
00381                 o->laserTerminations = it->second->laserTerminations + hit;
00382                 o->ancestor = it->second;
00383         }
00384         else {
00385                 o->laserTravel = distanceTraveled;
00386                 o->laserTerminations = hit;
00387                 o->ancestor.reset();
00388         }
00389         
00390         // Add new observation to treeLeaf's observation list and the occupancyGrid
00391         treeLeaf->observations.push_back(o);
00392         treeMap->insert( std::pair<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >(treeLeaf, o) );
00393 }
00394 
00395 // ######################################################################
00396 double HawkSlammer::scoreLaserCast(boost::shared_ptr<Particle> treeLeaf, double scanAngle, double scanDistance) {
00397         if(scanDistance == 0) return scoreObservation(treeLeaf, treeLeaf->robotPose.x, treeLeaf->robotPose.y, scanDistance);
00398         
00399         double startx = treeLeaf->robotPose.x;
00400         double starty = treeLeaf->robotPose.y;
00401         double theta = cleanAngle(treeLeaf->robotPose.theta + scanAngle);
00402         double MeasuredDist = scanDistance;
00403         
00404         double overflow, slope; // Used for actually tracing the line
00405         int x, y, incX, incY, endx, endy;
00406         double dx, dy;
00407         double totalProb; // Total probability that the line trace should have stopped before this step in the trace
00408         double eval;      // Total raw probability for the observation given this line trace through the map
00409         double prob, distance, error;
00410         double secant, cosecant;   // precomputed for speed
00411         double xblock, yblock;
00412         double xMotion, yMotion;
00413         double standardDist;
00414 
00415         // eval is the total probability for this line trace. Since this is a summation, eval starts at 0
00416         eval = 0.0;
00417         // totalProb is the total probability that the laser scan could travel this far through the occupancyGrid
00418         // This starts at 1, and decreases as possible objects are passed.
00419         totalProb = 1.0;
00420         // a couple of variables are precomuted for speed.
00421         secant = 1.0/fabs(cos(theta));
00422         cosecant = 1.0/fabs(sin(theta));
00423 
00424         // If you look at Localize funtion in low.c, you can see that there are two different line traces
00425         // performed. The second trace is the full evaluation of the scan. The first trace is a kind of
00426         // approximate scan, only covering a small section near the percieved endpoint of the scan. The 
00427         // first trace is used as a way of culling out obviously bad particles without having to do too
00428         // much on them. For this "culling" trace, we specify directly how much further past the endpoint
00429         // we want to trace. When that culling number is set to zero, we know that it is the full trace
00430         // we are looking at, and we can as far out as 20 grid squares beyond the endpoint (anything further
00431         // has essentially zero probability to add to the scan.
00432         /*if (culling)
00433         distance = MeasuredDist+culling;
00434         else
00435         distance = MIN(MeasuredDist+20.0, MAX_SENSE_RANGE);*/
00436         distance = MeasuredDist;
00437 
00438         // The endpoint of the scan, in both float and int.
00439         dx = (startx + (cos(theta) * distance));
00440         dy = (starty + (sin(theta) * distance));
00441         endx = (int) (dx);
00442         endy = (int) (dy);
00443 
00444         // Decide which x and y directions the line is travelling.
00445         if(startx > dx) {
00446                 incX = -1;
00447                 xblock = -startx;
00448         }
00449         else {
00450                 incX = 1;
00451                 xblock = 1.0-startx;
00452         }
00453 
00454         if(starty > dy) {
00455                 incY = -1;
00456                 yblock = -starty;
00457         }
00458         else {
00459                 incY = 1;
00460                 yblock = 1.0-starty;
00461         }
00462 
00463         // Two copies of the same basic code, swapping the roles of x and y, depending on which one is the primary 
00464         // direction of motion in the line trace.
00465         if(fabs(startx - dx) > fabs(starty - dy)) {
00466                 y = (int) (starty);
00467 
00468                 // The given starting point is non-integer. The line therefore starts at some point partially set in to the starting
00469                 // square. Overflow starts at this off-center amount, in order to make steps in the y direction at the right places.
00470                 overflow = starty - y;
00471                 // Code is simpler if overflow is always decreasing towards zero. Note that slope is forced to be postive
00472                 if(incY == 1) overflow = 1.0 - overflow;
00473 
00474                 slope = fabs(tan(theta));
00475                 if(slope > 1.0) slope = fabs((starty - dy) / (startx - dx));
00476 
00477                 // The first square is a delicate thing, as we aren't doing a full square traversal in
00478                 // either direction. So we figure out this strange portion of a step so that we can then
00479                 // work off of the axes later. 
00480                 // NOTE: we aren't computing the probability of this first step. Its a technical issue for
00481                 // simplicity, and the odds of the sensor sitting on top of a solid object are sufficiently 
00482                 // close to zero to ignore this tiny portion of a step. 
00483                 dx = fabs((int)(startx)+xblock);
00484                 dy = fabs(tan(theta)*dx);
00485                 // The first step is actually in the y direction, due to the proximity of starty 
00486                 // to the y axis. 
00487                 if (overflow - dy < 0.0) {
00488                         y = y + incY;
00489                         overflow = overflow - dy + 1.0;
00490                 }
00491                 // Our first step is in fact in the x direction in this case. Set up for the overflow to 
00492                 // be our starting offset plus this little extra we travel in the y direction.
00493                 else overflow = overflow - dy;
00494 
00495                 // Most of the scans will be the same length across a grid square, entering and exiting on opposite
00496                 // sides. Precompute this amount to save a little time.
00497                 standardDist = slope*cosecant;
00498 
00499                 // These two numbers help determine just how far away the endpoint of the scan is from the current
00500                 // position in the scan. xMotion keeps track of the distance from the endpoint of the scan to next 
00501                 // point where the line trace will cross the x-axis. Since each step in this loop moves us across 
00502                 // exactly one grid square, this number will change by 1/cosine(theta) (ie secant) each iteration.
00503                 // yMotion obviously does the same for the y axis.
00504                 xMotion = -fabs(fabs(( ((int) (startx)) +xblock) * secant) - MeasuredDist);
00505                 yMotion = -fabs(fabs((y+yblock) * cosecant) - MeasuredDist);
00506 
00507                 for(x = (int) (startx) + incX; x != endx; x = x + incX) {
00508                         // Update our two running counts.
00509                         xMotion = xMotion + secant;
00510                         overflow = overflow - slope;
00511 
00512                         // Establish the distance travelled by the laser through this square. Note that this amount is
00513                         // less than normal if the slope has overflowed, implying that the y-axis has been crossed.
00514                         if (overflow < 0.0) distance = (overflow+slope)*cosecant;
00515                         else distance = standardDist;
00516 
00517                         // Compute the probability of the laser stopping in the square, given the particle's unique map.
00518                         // Keep in mind that the probability of even getting this far in the trace is likely less than 1.
00519                         prob = totalProb * scoreObservation(treeLeaf, x, y, distance);
00520                         if(prob > 0) {
00521                                 // If the scan had actually been stopped by an object in the map at this square,
00522                                 // how much error would there be in the laser? Determine which axis will be crossed 
00523                                 // next, and compute from there. (This value is actually kept as a running total now).
00524                                 if(overflow < 0.0) error = fabs(yMotion);
00525                                 else error = fabs(xMotion);
00526 
00527                                 // Increase the probability of the scan by the probability of stopping here, multiplied by the 
00528                                 // probability that a scan which stopped here could produce the error observed.
00529                                 // If the error is too large, the net effect on probability of the scan is essentially zero.
00530                                 // We can save some time by not computing this exponential.
00531                                 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance)));
00532 
00533                                 // Correspondingly decrease the probability that laser has continued.
00534                                 totalProb = totalProb - prob;
00535                         }
00536 
00537                         // If the overflow has dipped below zero, then the trace has crossed the y-axis, and we need to compute
00538                         // everything for a single step in the y direction. 
00539                         if(overflow < 0.0) {
00540                                 y += incY;
00541                                 yMotion = yMotion + cosecant;
00542 
00543                                 distance = -overflow*cosecant;
00544                                 overflow = overflow + 1.0;
00545 
00546                                 prob = totalProb * scoreObservation(treeLeaf, x, y, distance);
00547                                 if(prob > 0) {
00548                                         // There is no question about which axis will be the next crossed, since we just crossed the y-axis, 
00549                                         // and x motion is dominant
00550                                         error = fabs(xMotion);
00551                                         if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance)));
00552                                 }
00553                                 totalProb = totalProb - prob;
00554                         }
00555 
00556                 }
00557         }
00558 
00559         // ...second verse, same as the first...
00560         // Pretty much a direct copy of the previous block of code, with x and y reversed.
00561         else {
00562                 x = (int) (startx);
00563                 overflow = startx - x;
00564                 if(incX == 1) overflow = 1.0 - overflow;
00565                 slope = 1.0/fabs(tan(theta));
00566 
00567                 // (See corresponding comments in the previous half of this function)
00568                 dy = fabs((int)(starty)+yblock);
00569                 dx = fabs(dy/tan(theta));
00570                 if(overflow - dx < 0) {
00571                         x = x + incX;
00572                         overflow = overflow - dx + 1.0;
00573                 }
00574                 else overflow = overflow - dx;
00575 
00576                 standardDist = slope*secant;
00577                 xMotion = -fabs(fabs((x+xblock) * secant) - MeasuredDist);
00578                 yMotion = -fabs(fabs(( ((int) (starty)) +yblock) * cosecant) - MeasuredDist);
00579 
00580                 for(y = (int) (starty) + incY; y != endy; y = y + incY) {
00581                         yMotion = yMotion + cosecant;
00582                         overflow = overflow - slope;
00583 
00584                         if(overflow < 0.0) distance = (overflow+slope)*secant;
00585                         else distance = standardDist;
00586 
00587                         prob = totalProb * scoreObservation(treeLeaf, x, y, distance);
00588                         if (prob > 0) {
00589                                 if(overflow < 0.0) error = fabs(xMotion);
00590                                 else error = fabs(yMotion);
00591                                 if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance)));
00592                         }
00593                         totalProb = totalProb - prob;
00594 
00595                         if(overflow < 0.0) {
00596                                 x += incX;
00597                                 xMotion = xMotion + secant;
00598 
00599                                 distance = -overflow*secant;
00600                                 overflow = overflow + 1.0;
00601 
00602                                 prob = totalProb * scoreObservation(treeLeaf, x, y, distance);
00603                                 if(prob > 0) {
00604                                         error = fabs(yMotion);
00605                                         if(error < 20.0) eval = eval + (prob * exp(-(error*error)/(2*scannerVariance)));
00606                                 }
00607                                 totalProb = totalProb - prob;
00608                         }
00609 
00610                 }
00611         }
00612         
00613         // If the laser reported a range beyond the maximum range allowed, any left-over probability that
00614         // the laser has not yet been stopped all has a probability of 1 to get the measured reading. We
00615         // therefore just add this remaining probability to the evaluation.
00616         if(MeasuredDist >= maxLaserDistance) return (eval + totalProb);
00617 
00618         // Otherwise, we know that the total probability of the laser being stopped at some point during 
00619         // the scan is 1. Normalize the evaluation to enforce this. 
00620         if(totalProb == 1) return 0;
00621         return (eval / (1.0 - totalProb));
00622 }
00623 
00624 // ######################################################################
00625 double HawkSlammer::scoreObservation(boost::shared_ptr<Particle> treeLeaf, int x, int y, double distanceTraveled) {
00626         // This is easy, no particle has observed this, return unkown probability
00627         if(occupancyGrid.getVal(x, y)) {
00628                 std::map< boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::const_iterator it = occupancyGrid.getVal(x, y)->find(treeLeaf);
00629                 if(it == occupancyGrid.getVal(x, y)->end())
00630                         return (1.0 - exp(-1.0/(8000 * mapResolution) * distanceTraveled));
00631                 else return (1.0 - exp(-(it->second->laserTerminations / it->second->laserTravel) * distanceTraveled));
00632         }
00633         else return (1.0 - exp(-1.0/(8000 * mapResolution) * distanceTraveled));
00634 }
00635 
00636 // ######################################################################
00637 void HawkSlammer::sendSlamDataMessage() {
00638         std::cout << "sendSlamDataMessage()" << std::endl;
00639     
00640         // Create Message
00641         HawkMessages::SlamDataMessagePtr msg = new HawkMessages::SlamDataMessage;
00642     msg->hawkPose = ancestryTree.mostLikely->robotPose;
00643     Image< PixRGB<byte> > mapImage = makeMapImage(ancestryTree.mostLikely);
00644     if(ancestryTree.leaves.size() >= 3) {
00645                 Image< PixRGB<byte> > topImage = concatX(mapImage, makeMapImage(ancestryTree.leaves.at(0)));
00646                 Image< PixRGB<byte> > bottomImage = concatX(makeMapImage(ancestryTree.leaves.at(1)), makeMapImage(ancestryTree.leaves.at(2)));
00647                 mapImage = concatY(topImage, bottomImage);
00648     }
00649     
00650     // Debug print to screen
00651     std::cout << "\trobotPose: (" << msg->hawkPose.x << ", " << msg->hawkPose.y << ", " << msg->hawkPose.z << ", " << msg->hawkPose.theta << ")" << std::endl;
00652     window.drawImage(mapImage, 0, 0, true);
00653     
00654     // Send Message
00655         publish("SlamDataMessage", msg);
00656 }
00657 
00658 // ######################################################################
00659 void HawkSlammer::resampleParticles() {
00660         std::cout << "resampleParticles()" << std::endl;
00661         
00662     // Calculate a Cumulative Distribution Function for our particle weights
00663     std::vector<float> CDF;
00664     CDF.resize(ancestryTree.leaves.size());
00665     CDF.at(0) = ancestryTree.leaves.at(0)->probability;
00666     for(int i = 1; i < (int)CDF.size(); i++) {
00667         CDF.at(i) = CDF.at(i-1) + ancestryTree.leaves.at(i)->probability;
00668     }
00669     
00670     // Roulette wheel through particles
00671     std::vector<boost::shared_ptr<Particle> > lonelyLeaves = ancestryTree.leaves;
00672     std::vector<boost::shared_ptr<Particle> > newLeaves;
00673     int i = 0;
00674     float newProb = 1.0/float(numberOfParticles);
00675     float u = randomDouble()* newProb;
00676     for(int j = 0; j < numberOfParticles; j++) {
00677         // Keep the wheel moving
00678         while(i < (int)CDF.size() && u > CDF.at(i)) i++;
00679         u += newProb;
00680         
00681         // Make new particle
00682         boost::shared_ptr<Particle> p(new Particle());
00683         p->parent = ancestryTree.leaves.at(i);
00684         p->robotPose = ancestryTree.leaves.at(i)->robotPose;
00685         p->probability = newProb;
00686         
00687         // Tell everyone!
00688         ancestryTree.leaves.at(i)->children.push_back(p);
00689         newLeaves.push_back(p);
00690         
00691         // Remove from lonelyLeaves vector
00692                 std::vector<boost::shared_ptr<Particle> >::iterator it;
00693                 it = std::find(lonelyLeaves.begin(), lonelyLeaves.end(), ancestryTree.leaves.at(i));
00694                 if(it != lonelyLeaves.end()) lonelyLeaves.erase(it);
00695     }
00696     
00697     // Set new particles as tree leaves
00698     ancestryTree.leaves = newLeaves;
00699     
00700     // Delete Un-resampled leaves
00701         std::cout << "before resampling, active particles: " << 1+countDescendants(ancestryTree.head) << std::endl;
00702         std::cout << "we have " << lonelyLeaves.size() << " lonely leaves to be deleted" << std::endl;
00703     for(size_t i = 0; i < lonelyLeaves.size(); i++) {
00704                 std::vector<boost::shared_ptr<Particle> >::iterator it;
00705                 it = find(lonelyLeaves.at(i)->parent->children.begin(), lonelyLeaves.at(i)->parent->children.end(), lonelyLeaves.at(i));
00706         lonelyLeaves.at(i)->parent->children.erase(it);
00707         // with no references left, particle should now get deleted by boost
00708     }
00709         std::cout << "after resampling, active particles: " << 1+countDescendants(ancestryTree.head) << std::endl;
00710 }
00711 
00712 // ######################################################################
00713 void HawkSlammer::pruneParticles() {
00714         std::cout << "pruneParticles()" << std::endl;
00715         
00716         // Start a recursive call through the tree
00717         collapseParticle(ancestryTree.head);
00718 }
00719 
00720 // ######################################################################
00721 void HawkSlammer::collapseParticle(boost::shared_ptr<Particle> particle) {
00722         // Check to see if we have an only child
00723         if(particle->children.size() == 1) {
00724                 std::cout << "fouund a particle to squash!" << std::endl;
00725                 // Particle's only child
00726                 boost::shared_ptr<Particle> child = particle->children.at(0);
00727                 
00728                 std::cout << "Child has " << child->observations.size() << " observations" << std::endl;
00729                 // Delete particle's observations that that have been replaced by the child
00730                 for(size_t j = 0; j < child->observations.size(); j++) {
00731                         // Does child have observation in the same place as particle?
00732                         if(child->observations.at(j)->ancestor && child->observations.at(j)->ancestor->source == particle) {
00733                                 // He does! So copy over the particle's observation ancestor
00734                                 child->observations.at(j)->ancestor = child->observations.at(j)->ancestor->ancestor;
00735                                 
00736                                 // And remove reference of particle's observation in occupancyGrid
00737                                 std::cout << "Erasing observation with " << (*occupancyGrid.getVal(child->observations.at(j)->x, child->observations.at(j)->y))[particle].use_count() << " references" << std::endl;
00738                                 occupancyGrid.getVal(child->observations.at(j)->x, child->observations.at(j)->y)->erase(particle);
00739                         }
00740                 }
00741                 std::cout << "Child now has " << child->observations.size() << " observations" << std::endl;
00742                 std::cin.get();
00743 
00744                 // Particle's remaining observations should become the child's
00745                 for(int j = 0; j < (int)particle->observations.size(); j++) {
00746                         particle->observations.at(j)->source = child;
00747                         child->observations.push_back(particle->observations.at(j));
00748                 }
00749                 
00750                 // Update child's parent reference
00751                 child->parent = particle->parent;
00752                 
00753                 // Update parent's child reference
00754                 if(!particle->parent) {
00755                         // This is the head of the tree, replace the head
00756                         ancestryTree.head = child;
00757                 }
00758                 else {
00759                         // This is a normal node, replace the pointer in the children vector
00760                         std::vector<boost::shared_ptr<Particle> >::iterator it;
00761                         it = find(particle->parent->children.begin(), particle->parent->children.end(), particle);
00762                         *it = child;
00763                 }
00764         
00765                 // Delete particle (automatic with boost)
00766                 particle->parent.reset();
00767                 particle->children.clear();
00768                 particle->observations.clear();
00769 
00770                 // Now re-run the algorithm on the child
00771                 collapseParticle(child);
00772         }
00773         else {
00774                 // Run this algorithm recursively for each child
00775                 for(size_t i = 0; i < particle->children.size(); i++) {
00776                         collapseParticle(particle->children.at(i));
00777                 }
00778         }
00779 }
00780 
00781 // ######################################################################
00782 int HawkSlammer::countDescendants(boost::shared_ptr<Particle> particle) {
00783         int particles = particle->children.size();
00784         
00785         for(size_t i = 0; i < particle->children.size(); i++) {
00786                 particles += countDescendants(particle->children.at(i));
00787         }
00788         
00789         return particles;
00790 }
00791 
00792 // ######################################################################
00793 Image< PixRGB<byte> > HawkSlammer::makeMapImage(boost::shared_ptr<Particle> primaryParticle) {
00794         Image< PixRGB<byte> > mapImage(occupancyGrid.getWidth(), occupancyGrid.getHeight(), NO_INIT);
00795     PixRGB<byte> hitDot, nohitDot, notscannedDot;
00796     hitDot.set(0, 0, 0); //black
00797     nohitDot.set(195, 195, 195); //light grey
00798     notscannedDot.set(255, 255, 255); //white
00799         
00800         // Make the image
00801         for(int i = 0; i < mapImage.getWidth(); i++) {
00802                 for(int j = 0; j < mapImage.getHeight(); j++) {
00803                         // Find an observation from the particle or his ancestors
00804                         boost::shared_ptr<Particle> particle = primaryParticle;
00805                         boost::shared_ptr<Observation> observation;
00806                         std::map<boost::shared_ptr<Particle>, boost::shared_ptr<Observation> >::iterator it;
00807                         while(!observation && particle) {
00808                                 if(occupancyGrid.getVal(i, j)) {
00809                                         it = occupancyGrid.getVal(i, j)->find(particle);
00810                                         if(it != occupancyGrid.getVal(i, j)->end()) observation = it->second;
00811                                         else particle = particle->parent;
00812                                 }
00813                                 else particle = particle->parent;
00814                         }
00815                         
00816                         // Color map accordingly
00817                         if(observation) {
00818                                 if(observation->laserTerminations / observation->laserTravel > 0.5)
00819                                         mapImage.setVal(i, j, hitDot);
00820                                 else mapImage.setVal(i, j, nohitDot);
00821                         }
00822                         else mapImage.setVal(i, j, notscannedDot);
00823                 }
00824         }
00825         
00826         return mapImage;
00827 }
00828 
00829 // ######################################################################
00830 Image< PixRGB<byte> > HawkSlammer::makeScannerImage(SensorDataMessagePtr msg) {
00831         double angularResolution = msg->angularResolution;
00832         LongSeq scannerData = msg->scannerData;
00833         Image< PixRGB<byte> > scannerImage(2*HALF_WINDOW_SIZE,2*HALF_WINDOW_SIZE,ZEROS);
00834         
00835         // Plot scanner points
00836         long min = maxLaserDistance, max = 0;
00837         int offset = scannerData.size()/2;
00838         for(size_t i = 0; i < scannerData.size(); i++) {
00839                 if(scannerData.at(i) > max) max = scannerData.at(i);
00840                 if(scannerData.at(i) < min) min = scannerData.at(i);
00841                 // Calculate distance
00842                 float distance = scannerData.at(i); 
00843                 float angle = (i-offset)*angularResolution;
00844                 distance = distance/maxLaserDistance*HALF_WINDOW_SIZE; //((distance - min)/(max-min))*256;
00845                 if (distance < 0) distance = 1.0;
00846                 
00847                 // Draw point
00848                 Point2D<int> pt;
00849                 pt.i = HALF_WINDOW_SIZE - int(distance*sin(angle));
00850                 pt.j = HALF_WINDOW_SIZE - int(distance*cos(angle));
00851                 drawCircle(scannerImage, pt, 1, PixRGB<byte>(255,0,0));
00852                 
00853                 // Draw range lines
00854                 if(i == 0 || i == (scannerData.size() - 1)) {
00855                         pt.i = HALF_WINDOW_SIZE - int(HALF_WINDOW_SIZE*sin(angle));
00856                         pt.j = HALF_WINDOW_SIZE - int(HALF_WINDOW_SIZE*cos(angle));
00857                         drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,0,255));
00858                 }
00859 
00860                 /*if((i >= (-50 + 141)) && (i <= (49 + 141)))
00861                         drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,0,255));
00862                 else
00863                         drawLine(scannerImage, Point2D<int>(HALF_WINDOW_SIZE,HALF_WINDOW_SIZE),pt,PixRGB<byte>(0,255,0));*/
00864         }
00865         std::cout << "min, max: " << min << ", " << max << std::endl;
00866         
00867         return scannerImage;
00868 }
00869 
00870 // ######################################################################
00871 int main (int argc, char* argv[]) {
00872         std::cout << "HawkSlammer: starting..." << std::endl;
00873         
00874         HawkSlammer agent("HawkSlammer", argc, argv);
00875         agent.start();
00876         
00877         std::cout << "HawkSlammer: all done!" << std::endl;
00878 }
Generated on Sun May 8 08:41:20 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3