BeobotBrainMT.C

Go to the documentation of this file.
00001 /*!@file Beobot/BeobotBrainMT.C efficient implementation of the
00002   feature pyramids, Saliency, Gist, Shape Estimator  for Beobot         */
00003 // //////////////////////////////////////////////////////////////////// //
00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the //
00005 // University of Southern California (USC) and the iLab at USC.         //
00006 // See http://iLab.usc.edu for information about this project.          //
00007 // //////////////////////////////////////////////////////////////////// //
00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00010 // in Visual Environments, and Applications'' by Christof Koch and      //
00011 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00012 // pending; application number 09/912,225 filed July 23, 2001; see      //
00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status).     //
00014 // //////////////////////////////////////////////////////////////////// //
00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00016 //                                                                      //
00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00018 // redistribute it and/or modify it under the terms of the GNU General  //
00019 // Public License as published by the Free Software Foundation; either  //
00020 // version 2 of the License, or (at your option) any later version.     //
00021 //                                                                      //
00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00025 // PURPOSE.  See the GNU General Public License for more details.       //
00026 //                                                                      //
00027 // You should have received a copy of the GNU General Public License    //
00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00030 // Boston, MA 02111-1307 USA.                                           //
00031 // //////////////////////////////////////////////////////////////////// //
00032 //
00033 // Primary maintainer for this file: Christian Siagian <siagian@usc.edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Beobot/BeobotBrainMT.C $
00035 // $Id: BeobotBrainMT.C 12127 2009-12-06 03:05:23Z siagian $
00036 //
00037 
00038 // ######################################################################
00039 
00040 #include "Beobot/BeobotBrainMT.H"
00041 #include "Neuro/gistParams.H"
00042 #include "Neuro/GistEstimatorStd.H"
00043 
00044 #include "Image/MathOps.H"
00045 #include "Image/DrawOps.H"
00046 #include "Image/ShapeOps.H"
00047 #include "Image/Transforms.H"
00048 #include "Image/fancynorm.H"
00049 #include "Image/FilterOps.H"
00050 #include "Image/PyrBuilder.H"
00051 #include "Image/ColorOps.H"
00052 #include "Util/Timer.H"
00053 #include "Image/CutPaste.H"     // for inplacePaste()
00054 #include "Image/MorphOps.H"     // for openImg()
00055 
00056 // ######################################################################
00057 void *BeobotBrainMT_threadCompute(void *c)
00058 {
00059   BeobotBrainMT *d = (BeobotBrainMT *)c;
00060   d->threadCompute();
00061   return NULL;
00062 }
00063 
00064 // ######################################################################
00065 BeobotBrainMT::BeobotBrainMT(OptionManager& mgr,
00066                              const std::string& descrName,
00067                              const std::string& tagName)
00068   :
00069   ModelComponent(mgr, descrName, tagName)
00070 {
00071   numWorkers = 0U;
00072 
00073   // the number of channels we are going to compute
00074   itsNumChannels = NUM_CHANNELS;
00075 
00076   // initialize the channel weights
00077   itsChanWeight.resize(itsNumChannels);
00078   itsChanWeight[REDGREEN  ] = CWEIGHT;
00079   itsChanWeight[BLUEYELLOW] = CWEIGHT;
00080   itsChanWeight[INTENSITY ] = IWEIGHT;
00081   itsChanWeight[ORI0      ] = OWEIGHT;
00082   itsChanWeight[ORI45     ] = OWEIGHT;
00083   itsChanWeight[ORI90     ] = OWEIGHT;
00084   itsChanWeight[ORI135    ] = OWEIGHT;
00085 
00086   // conspicuity maps
00087   itsCMaps.resize(itsNumChannels);
00088 
00089   // initialize CSmaps, raw CS maps
00090   uint cscount =
00091     (level_max - level_min + 1) * (delta_max - delta_min + 1);
00092   itsCSMaps.resize(itsNumChannels);
00093   itsRawCSMaps.resize(itsNumChannels);
00094   for(uint i = 0; i < itsNumChannels; i++)
00095     {
00096       itsCSMaps[i].resize(cscount);
00097       itsRawCSMaps[i].resize(cscount);
00098     }
00099 
00100   // feature maps storage
00101   itsImgPyrs.resize(itsNumChannels);
00102 
00103   //create the structuring element for Chamfer smoothing
00104   const int ss = 8;
00105   structEl = Image<byte>(ss+ss,ss+ss,ZEROS);
00106   drawDisk(structEl, Point2D<int>(ss,ss),ss,(byte)1);
00107 
00108   // initializing gist vector
00109   itsGistFeatSize = NUM_GIST_FEAT_SIZE;
00110   itsGistVector = Image<double>(1, itsGistFeatSize, NO_INIT);
00111   LINFO("initialized");
00112 
00113   // window to debug: default 320 x 240
00114   //itsWin =
00115   //  new XWinManaged(Dims(2*IMAGE_WIDTH,2*IMAGE_HEIGHT), 0, 0, "bbmtWin");
00116 
00117   itsTimer.reset(new Timer(1000000));
00118   itsProcessTime = -1.0F;
00119 }
00120 
00121 // ######################################################################
00122 void BeobotBrainMT::start1()
00123 {
00124   // start threads. They should go to sleep on the condition since no
00125   // jobs have ben queued up yet:
00126   pthread_mutex_init(&jobLock, NULL);
00127   pthread_mutex_init(&mapLock, NULL);
00128   pthread_cond_init(&jobCond, NULL);
00129 
00130   LINFO("Starting with %d threads...", numBBMTthreads);
00131 
00132   // get our processing threads started:
00133   worker = new pthread_t[numBBMTthreads];
00134   for (uint i = 0; i < numBBMTthreads; i ++)
00135     {
00136       pthread_create(&worker[i], NULL, BeobotBrainMT_threadCompute,
00137                      (void *)this);
00138 
00139       // all threads should go and lock against our job condition.
00140       // Sleep a bit to make sure this really happens:
00141       usleep(100000);
00142     }
00143 }
00144 
00145 // ######################################################################
00146 void BeobotBrainMT::stop2()
00147 {
00148   // should cleanup the threads, mutexes, etc...
00149   pthread_cond_destroy(&jobCond);
00150 
00151   //for (uint i = 0; i < numBBMTthreads; i ++)
00152   //  pthread_delete(&worker[i].....
00153 
00154   delete [] worker;
00155 }
00156 
00157 // ######################################################################
00158 BeobotBrainMT::~BeobotBrainMT()
00159 { }
00160 
00161 // ######################################################################
00162 bool BeobotBrainMT::outputReady()
00163 {
00164   bool ret = false;
00165 
00166   pthread_mutex_lock(&jobLock);
00167   if (jobsTodo == 0U) ret = true;
00168   //LINFO("jobs left: %d", jobsTodo);
00169 
00170   // for the initial condition
00171   pthread_mutex_lock(&mapLock);
00172   if(!itsCurrImg.initialized()) ret = false;
00173   pthread_mutex_unlock(&mapLock);
00174 
00175   pthread_mutex_unlock(&jobLock);
00176 
00177   return ret;
00178 }
00179 
00180 // ######################################################################
00181 void BeobotBrainMT::input(Image< PixRGB<byte> > img)
00182 {
00183   itsTimer->reset();
00184   computeCIOpyr(img);
00185 }
00186 
00187 // ######################################################################
00188 void BeobotBrainMT::computeCIOpyr(Image< PixRGB<byte> > img)
00189 {
00190   pthread_mutex_lock(&mapLock);
00191 
00192   //LINFO("new input.....");
00193   // store current color image:
00194   itsCurrImg = img;
00195   itsCurrImgWidth  = img.getWidth();
00196   itsCurrImgHeight = img.getHeight();
00197   //itsWin->setDims(Dims(2*itsCurrImgWidth, 2*itsCurrImgHeight));
00198 
00199   // also kill any old output and internals:
00200   itsSalmap.freeMem();
00201   gotLum = false; gotRGBY = false;
00202   pthread_mutex_unlock(&mapLock);
00203 
00204   // setup job queue:
00205   pthread_mutex_lock(&jobLock);
00206   jobQueue.clear();
00207 
00208   // color channel
00209   jobQueue.push_back(jobData(REDGREEN, Gaussian5, CWEIGHT, 0.0F));
00210   jobQueue.push_back(jobData(BLUEYELLOW, Gaussian5, CWEIGHT, 0.0F));
00211 
00212   // intensity channel
00213   jobQueue.push_back(jobData(INTENSITY, Gaussian5, IWEIGHT, 0.0F));
00214 
00215   // orientation channel
00216   jobQueue.push_back(jobData(ORI0, Oriented5, OWEIGHT, 0.0F));
00217   jobQueue.push_back(jobData(ORI45, Oriented5, OWEIGHT, 45.0F));
00218   jobQueue.push_back(jobData(ORI90, Oriented5, OWEIGHT, 90.0F));
00219   jobQueue.push_back(jobData(ORI135, Oriented5, OWEIGHT, 135.0F));
00220 
00221   jobsTodo = jobQueue.size();
00222   pthread_mutex_unlock(&jobLock);
00223 
00224   // broadcast on job queue condition to wake up worker threads:
00225   pthread_cond_broadcast(&jobCond);
00226   //LINFO("new input ok.....");
00227 }
00228 
00229 // ######################################################################
00230 Image<float> BeobotBrainMT::getSalMap()
00231 {
00232   Image<float> ret;
00233 
00234   pthread_mutex_lock(&mapLock);
00235   ret = itsSalmap;
00236   pthread_mutex_unlock(&mapLock);
00237 
00238   return ret;
00239 }
00240 
00241 // ######################################################################
00242 Image<double> BeobotBrainMT::getGist()
00243 {
00244   Image<double> ret;
00245   pthread_mutex_lock(&gistLock);
00246   ret = itsGistVector;
00247   pthread_mutex_unlock(&gistLock);
00248 
00249   return ret;
00250 }
00251 
00252 // ######################################################################
00253 // The threaded function
00254 void BeobotBrainMT::threadCompute()
00255 {
00256   pthread_mutex_lock(&mapLock);
00257   uint myNum = numWorkers ++;
00258   pthread_mutex_unlock(&mapLock);
00259   LINFO("  ... worker %u ready.", myNum);
00260 
00261   while(true)
00262     {
00263       // wait until there are jobs in the queue that we can process:
00264       pthread_mutex_lock(&jobLock);
00265       jobData current(0, Gaussian5, 0.0F, 0.0F); bool nojobs = true;
00266       if (jobQueue.empty() == false)
00267         {
00268           current = jobQueue.front();
00269           jobQueue.pop_front();
00270           nojobs = false;
00271         }
00272       else
00273         pthread_cond_wait(&jobCond, &jobLock);
00274       pthread_mutex_unlock(&jobLock);
00275 
00276       // if we don't have a job to do, just wait more:
00277       if (nojobs) continue;
00278       //LINFO("[%u] GOT: job type %d", myNum, int(current.jobType));
00279 
00280       // read next entry in job queue and perform desired action on
00281       // current image and record result in output image
00282       // (accumulative)
00283       Image<float> curImage;
00284 
00285       // The case statement on this end parses the desired action from
00286       // the job queue and performs the needed image pre-processing
00287       pthread_mutex_lock(&mapLock);
00288       switch(current.jobType)
00289         {
00290           // While shared resources are used here, they are only read,
00291           // so they should not need to be protected by mutexers
00292 
00293           // ##################################################
00294         case REDGREEN:
00295           if (gotRGBY == false)
00296             {
00297               getRGBY(itsCurrImg, rgimg, byimg, 25.5f);
00298               gotRGBY = true;
00299             }
00300             curImage = rgimg;
00301           break;
00302 
00303           // ##################################################
00304         case BLUEYELLOW:
00305           if (gotRGBY == false)
00306             {
00307               getRGBY(itsCurrImg, rgimg, byimg, 25.5f);
00308               gotRGBY = true;
00309             }
00310           curImage = byimg;
00311           break;
00312 
00313           // ##################################################
00314         case ORI0:
00315         case ORI45:
00316         case ORI90:
00317         case ORI135:
00318         case INTENSITY:
00319           if (gotLum == false)
00320             {
00321               itsCurrLumImg =
00322                 Image<float>(luminance(Image<PixRGB<float> >(itsCurrImg)));
00323             gotLum = true;
00324             }
00325           curImage = itsCurrLumImg;
00326           break;
00327 
00328           // ##################################################
00329         default:
00330           LERROR("invalid job type");
00331           curImage = itsCurrLumImg;
00332         }
00333       pthread_mutex_unlock(&mapLock);
00334 
00335       ImageSet<float> pyr;
00336 
00337       // alloc conspicuity map and clear it:
00338       int cW = itsCurrImgWidth /(int)(pow(2,sml));
00339       int cH = itsCurrImgHeight/(int)(pow(2,sml));
00340       Image<float> cmap(cW,cH, ZEROS);
00341 
00342       // get the proper conspicuity map and get its corresponding gist vector
00343       // The case statement on this end parses the desired action from
00344       // the job queue and performs the needed image pre-processing
00345       int count = 0; uint cscount = 0;
00346       int s_index = 0; int offset = 0;
00347       switch(current.jobType)
00348         {
00349           // ##################################################
00350         case INTENSITY:  s_index +=  6;
00351         case BLUEYELLOW: s_index +=  6;
00352         case REDGREEN:   s_index += 16;
00353 
00354           // compute & store oriented pyramid:
00355           pyr = buildPyrGaussian(curImage, 0, maxdepth, 5);
00356           itsImgPyrs[current.jobType] = pyr;
00357 
00358           // intensities is the max-normalized weighted sum of IntensCS:
00359           // we also add the gist extraction line
00360           for (int lev = level_min; lev <= level_max; lev ++)
00361             for (int delta = delta_min; delta <= delta_max; delta ++)
00362               {
00363                 Image<float> tmp = centerSurround(pyr, lev, lev + delta, true);
00364 
00365                 // extract and store the gist
00366                 LDEBUG("%d_%d_%d",current.jobType,lev,lev+delta);
00367                 pthread_mutex_lock(&gistLock);
00368                 inplacePaste(itsGistVector, getSubSum(tmp),
00369                              Point2D<int>(0, (s_index + count)*NUM_GIST_FEAT));
00370                 pthread_mutex_unlock(&gistLock);
00371                 count++;
00372 
00373                 // LINFO("tmp(%d,%d) : cmap(%d,%d)",
00374                 //       tmp.getWidth(), tmp.getHeight(),
00375                 //       cmap.getWidth(), cmap.getHeight());
00376                 // accumulate the CMaps
00377                 tmp = rescale(tmp, cmap.getWidth(), cmap.getHeight());
00378                 //tmp = downSize(tmp, cmap.getWidth(), cmap.getHeight());
00379                 float minv, maxv; getMinMax(tmp, minv, maxv);
00380                 itsRawCSMaps[current.jobType][cscount] = tmp;
00381                 tmp = maxNormalize(tmp, MAXNORMMIN, MAXNORMMAX, normtyp);
00382                 cmap += tmp;
00383                 itsCSMaps[current.jobType][cscount] = tmp; cscount++;
00384               }
00385           // store the center surround maps
00386           break;
00387 
00388           // ##################################################
00389 
00390           // ##################################################
00391         case ORI135: offset += 1;
00392         case ORI90:  offset += 1;
00393         case ORI45:  offset += 1;
00394         case ORI0:
00395 
00396           // compute & store oriented pyramid:
00397           pyr = buildPyrOriented(curImage, 0, maxdepth, 9,
00398                                  offset*45.0f, 10.0f);
00399           itsImgPyrs[current.jobType] = pyr;
00400 
00401           // extract the gist for the orientation channel
00402           for(int i = 0; i < NUM_GIST_LEV; i++)
00403             {
00404               LDEBUG("or_%f_%d",offset*45.0f,i);
00405               pthread_mutex_lock(&gistLock);
00406               inplacePaste
00407                 (itsGistVector, getSubSum(pyr[i]),
00408                  Point2D<int>(0, (i * NUM_GIST_LEV + offset) * NUM_GIST_FEAT));
00409               pthread_mutex_unlock(&gistLock);
00410              }
00411 
00412           // intensities is the max-normalized weighted sum of IntensCS:
00413           for (int lev = level_min; lev <= level_max; lev ++)
00414             for (int delta = delta_min; delta <= delta_max; delta ++)
00415               {
00416                 Image<float> tmp = centerSurround(pyr, lev, lev + delta, true);
00417                 //tmp = downSize(tmp, cmap.getWidth(), cmap.getHeight());
00418                 tmp = rescale(tmp, cmap.getWidth(), cmap.getHeight());
00419                 itsRawCSMaps[current.jobType][cscount] = tmp;
00420                 tmp = maxNormalize(tmp, MAXNORMMIN, MAXNORMMAX, normtyp);
00421                 cmap += tmp;
00422 
00423                 itsCSMaps[current.jobType][cscount] = tmp; cscount++;
00424               }
00425           // store the center surround maps
00426 
00427           break;
00428           // ##################################################
00429         default:
00430           LERROR("invalid job type");
00431         }
00432 
00433       // store the Conspicuity maps
00434       itsCMaps[current.jobType] = cmap;
00435 
00436       // add noise to the saliency map
00437       inplaceAddBGnoise(cmap, 25.0F);
00438 
00439       if (normtyp == VCXNORM_MAXNORM)
00440         cmap = maxNormalize(cmap, MAXNORMMIN, MAXNORMMAX, normtyp);
00441       else
00442         cmap = maxNormalize(cmap, 0.0f, 0.0f, normtyp);
00443 
00444       // multiply by conspicuity coefficient:
00445       if (current.weight != 1.0F) cmap *= current.weight;
00446 
00447       // Add to saliency map:
00448       pthread_mutex_lock(&mapLock);
00449       if (itsSalmap.initialized()) itsSalmap += cmap;
00450       else itsSalmap = cmap;
00451       pthread_mutex_unlock(&mapLock);
00452 
00453       pthread_mutex_lock(&jobLock);
00454       jobsTodo--;
00455 
00456       //LINFO("done with job type %d, %u todo...",
00457       //      int(current.jobType),jobsTodo);
00458       if(jobsTodo == 0)
00459         {
00460           // get the objects found
00461           findObjects();
00462 
00463           // compute the salient features of the objects
00464           computeSalientFeatures();
00465 
00466           itsProcessTime = itsTimer->get()/1000.0F;
00467         }
00468       pthread_mutex_unlock(&jobLock);
00469     }
00470 }
00471 
00472 // ######################################################################
00473 void BeobotBrainMT::findObjects()
00474 {
00475   // reset everything
00476   itsWinner.clear();
00477   itsObjRect.clear();
00478   itsWinningChan.clear();
00479   itsWinningSubmapNum.clear();
00480   itsWinningMap.clear();
00481   itsObjectMask.clear();
00482 
00483   int scale = (int)(pow(2,sml));
00484   Image<float> currSalMap = itsSalmap;
00485   Image<byte> accMask(currSalMap.getDims(),ZEROS);
00486   int area = 0;
00487 
00488   // find most salient location
00489   float maxval; Point2D<int> currwin; findMax(currSalMap, currwin, maxval);
00490   float cmval = maxval;
00491   int maxArea = currSalMap.getSize();
00492   LDEBUG("max val: %f", maxval);
00493   // criteria of keep searching for objects
00494   //   space to segment < 50%
00495   //   number of objects < 5
00496   //   strength of value is higher than 5%
00497   uint i = 0;
00498   while((.05 *maxval < cmval) && (i < 5) && (float(area)/maxArea < .5))
00499     {
00500       // get the next object
00501       Point2D<int> cwins = currwin*scale;
00502       itsWinner.push_back(cwins);
00503       setWinningMap(cwins);
00504       Image<float> cobjmask = getObjectMask();
00505       itsObjectMask.push_back(cobjmask);
00506       Rectangle crect = getSEBoundingBox();
00507       itsObjRect.push_back(crect);
00508 
00509       // suppress the previous salient region
00510       Image<float> temp(cobjmask);
00511       temp = convGauss<float>(temp,4,4,2);
00512       inplaceNormalize(temp, 0.0F,3.0F);
00513       inplaceClamp(temp, 0.0F, 1.0F);
00514       Image<float> supMask(temp);
00515 
00516       //Image<float> supMask(cobjmask);
00517       inplaceNormalize(supMask, 0.0F, 1.0F);
00518       supMask = (supMask * -1.0F) + 1.0F;
00519       currSalMap *= supMask;
00520 
00521       // get the next salient location
00522       findMax(currSalMap, currwin, cmval);
00523 
00524       // get amount of space segmented out
00525       accMask += cobjmask;
00526       area = int(sum(cobjmask)/255.0);
00527 
00528       //int w = itsCMaps[0].getWidth()*scale;
00529       //int h = itsCMaps[0].getHeight()*scale;
00530       //Image<float> disp(w*2, h*2, ZEROS);
00531       //Image<float> damask(accMask); inplaceNormalize(damask, 0.0F, 255.0F);
00532       //Image<float> dsupmask(supMask); inplaceNormalize(dsupmask, 0.0F, 255.0F);
00533       //Image<float> dcsm(currSalMap); inplaceNormalize(dcsm, 0.0F, 255.0F);
00534       //Image<float> dsm(itsSalmap); inplaceNormalize(dsm, 0.0F, 255.0F);
00535       LDEBUG("accMask: %d cmval: %f", area, cmval);
00536       //inplacePaste(disp, zoomXY(damask, scale), Point2D<int>(0, 0));
00537       //inplacePaste(disp, zoomXY(dsupmask, scale), Point2D<int>(w, 0));
00538       //inplacePaste(disp, zoomXY(dcsm, scale), Point2D<int>(0, h));
00539       //inplacePaste(disp, zoomXY(dsm, scale), Point2D<int>(w, h));
00540       //itsWin->drawImage(disp,0,0);
00541 
00542       // check for rectangle overlap
00543       float maxovl = 0.0;
00544       for(uint j = 0; j < itsObjRect.size()-1; j++)
00545         {
00546           Rectangle covl = crect.getOverlap(itsObjRect[j]);
00547           float pcnt = 0.0;
00548           if(covl.isValid())
00549             {
00550               pcnt = (float(covl.area())/crect.area() +
00551                       float(covl.area())/itsObjRect[j].area())/2.0;
00552               if(maxovl < pcnt) maxovl = pcnt;
00553               //LINFO("area: %d pcnt[%d]: %f", covl.area(), j, pcnt);
00554             }
00555           //else LINFO("area: 0 pcnt[%d]: 0.0",j);
00556         }
00557 
00558       // too much overlap
00559       if(maxovl > .66)
00560         {
00561           // pop back the last inserted object
00562           itsWinner.pop_back();
00563           itsObjRect.pop_back();
00564           itsWinningChan.pop_back();
00565           itsWinningSubmapNum.pop_back();
00566           itsWinningMap.pop_back();
00567           itsObjectMask.pop_back();
00568           LDEBUG("object overlap with previous ones > .66");
00569         }
00570       else
00571         {
00572           LDEBUG("sal pt %u done",i); i++;
00573         }
00574       //Raster::waitForKey();
00575     }
00576 }
00577 
00578 // ######################################################################
00579 bool BeobotBrainMT::setWinningMap(Point2D<int> winner)
00580 {
00581   Point2D<int> scwin = downScaleCoords(winner, itsCMaps[0].getDims());
00582 
00583   // loop over the channels
00584   uint wchan; float mx = -1.0F;
00585   for (uint i = 0; i < itsNumChannels; ++i)
00586     {
00587       Image<float> output = itsCMaps[i] * itsChanWeight[i];
00588       float curr_val = output.getVal(scwin);
00589       if (curr_val >= mx){mx = curr_val; wchan = i;}
00590     }
00591   //if (mx <= 0.0F) return false;
00592 
00593   // loop over the csmaps
00594   uint wschan; mx = -1.0F;
00595   for (uint j = 0; j < itsCSMaps[wchan].size(); j++)
00596     {
00597       Image<float> csmap = itsCSMaps[wchan][j];
00598       float curr_val = csmap.getVal(scwin);
00599       if (curr_val >= mx) { mx = curr_val; wschan = j; }
00600 
00601       //imean  = float(mean(csmap)); istdev = float(stdev(csmap));
00602       //LINFO("    csmap[%d][%d]: val: %f [%f,%f] -> %f Min: %f, Max: %f",
00603       //      i,j, curr_val, imean, istdev,
00604       //      (curr_val - imean)/istdev, min, max);
00605     }
00606 
00607 //  // loop over the submaps of the winning channel
00608 //   mx = -1.0F;
00609 //   for (uint i = 0; i < itsImgPyrs[wchan].size(); ++i)
00610 //     {
00611 //       Image<float> submap = (itsImgPyrs[wchan])[i];
00612 //       float curr_val =
00613 //         submap.getVal(downScaleCoords(winner,submap.getDims()));
00614 //       LINFO("submap = %i -> val = %f", i, curr_val);
00615 //       if (curr_val >= mx) { mx = curr_val; wschan = i; }
00616 //     }
00617 //   if (mx <= 0.0F) return false;
00618 
00619   // store the results
00620   itsWinningChan.push_back(wchan);
00621   itsWinningSubmapNum.push_back(wschan);
00622   itsWinningMap.push_back(itsCSMaps[wchan][wschan]);
00623 
00624   LDEBUG("[%d,%d] winner: chan(%d), sub-chan(%d)",
00625          winner.i, winner.j, wchan, wschan);
00626   return true;
00627 }
00628 
00629 // ######################################################################
00630 Point2D<int> BeobotBrainMT::downScaleCoords(Point2D<int> winner, Dims targetDims)
00631 {
00632 
00633   int i = (winner.i * targetDims.w()) / itsCurrImgWidth;
00634   int j = (winner.j * targetDims.h()) / itsCurrImgHeight;
00635   return Point2D<int>(i,j);
00636 }
00637 
00638 // ######################################################################
00639 Image<byte> BeobotBrainMT::getObjectMask()
00640 {
00641   int index = itsWinningMap.size() - 1;
00642   Image<float> winMapNormalized = itsWinningMap[index];
00643   inplaceNormalize(winMapNormalized, 0.0F, 1.0F);
00644 
00645   Point2D<int> wp = downScaleCoords(itsWinner[index],
00646                                itsWinningMap[index].getDims());
00647 
00648   // the winner usually falls at the junction of four pixels in the
00649   // saliency/conspicuity/feature map - have a look at all of the pixels
00650   std::vector<Point2D<int> > surr(4);
00651   surr[0] = Point2D<int>(0,0); surr[1] = Point2D<int>(-1,0);
00652   surr[2] = Point2D<int>(0,1); surr[3] = Point2D<int>(-1,1);
00653 
00654   bool haveValue = false;
00655   for (uint i = 0; i < surr.size(); ++i)
00656     {
00657       Point2D<int> wp2 = wp + surr[i];
00658       wp2.clampToDims(itsWinningMap[index].getDims());
00659       if (itsWinningMap[index].getVal(wp2) > 0.0F)
00660         {
00661           wp = wp2; haveValue = true; break;
00662         }
00663     }
00664 
00665   Image<byte> objMask;
00666   if (haveValue) objMask = segmentObjectClean(winMapNormalized, wp, 4);
00667   return objMask;
00668 }
00669 
00670   //  Dims dims(IMAGE_WIDTH, IMAGE_HEIGHT);
00671   // case SESMgaussian:
00672 //   Image<float> temp =
00673 //     (Image<float>)scaleBlock(itsObjectMask, dims);
00674 //   itsSmoothMask = convGauss<float>(temp,SIGMA,SIGMA,5);
00675 //   inplaceNormalize(itsSmoothMask, 0.0F,3.0F);
00676 //   inplaceClamp(itsSmoothMask, 0.0F, 1.0F);
00677 
00678 //   Timer tim2(1000000); tim2.reset();
00679 //   // case SESMchamfer:
00680 //   const byte cutoff = 100;
00681 //   Image<byte> temp = scaleBlock(itsObjectMask, dims);
00682 //   temp = chamfer34(openImg(temp,structEl),cutoff);
00683 //   itsSEmask = binaryReverse((Image<float>)temp,255.0F);
00684 //   inplaceNormalize(itsSEmask, 0.0F,1.0F);
00685 //   uint64 t2 = tim2.get();
00686 //   LINFO("---->tim2: %8.3f",t2/1000.0);
00687 
00688 // ######################################################################
00689 Rectangle BeobotBrainMT::getSEBoundingBox()
00690 {
00691   int scale = (int)(pow(2,sml));
00692 
00693   // get bounding box and add 1 pixel padding
00694   int index = itsObjectMask.size() - 1;
00695   Rectangle r = findBoundingRect(itsObjectMask[index], byte(255));
00696   int tr = r.top();    if(tr > 0) tr--;
00697   int lr = r.left();   if(lr > 0) lr--;
00698   int br = r.bottomO(); if(br < itsCurrImgHeight/scale) br++;
00699   int rr = r.rightO();  if(rr < itsCurrImgWidth/scale)  rr++;
00700   Rectangle rsc =
00701     Rectangle::tlbrO(tr*scale, lr*scale, br*scale, rr*scale);
00702 
00703   // correct the salient region to optimize SIFT recognition
00704   int w = itsCMaps[0].getWidth()*scale;
00705   int h = itsCMaps[0].getHeight()*scale;
00706   Rectangle rscc = correctBB(rsc, itsWinner[index], w, h);
00707   //}
00708 
00709 
00710 //// ######################################################################
00711 //void BeobotBrainMT::display()
00712 //{
00713 //   int index = itsWinningMap.size() - 1;
00714 //   int scale = (int)(pow(2,sml));
00715 //   int w = itsCMaps[0].getWidth()*scale;
00716 //   int h = itsCMaps[0].getHeight()*scale;
00717 
00718 //   // loop over the channels
00719 //   Image<float> disp(w*2, h*2, ZEROS);
00720 //   Image<float> mask = zoomXY(itsObjectMask[index], scale);
00721 //   inplaceNormalize(mask, 0.0F, 1.0F);
00722 //   drawRect(mask, rsc, 1.0F);
00723 //   inplacePaste(disp, mask, Point2D<int>(w, 0));
00724 //   for (uint i = 0; i < itsNumChannels; i++)
00725 //     {
00726 //       Image<float> output = itsCMaps[i] * itsChanWeight[i];
00727 //       Point2D<int> winPt = downScaleCoords(itsWinner[index], output.getDims());
00728 //       float curr_val = output.getVal(winPt);
00729 //       LINFO("channel = %i -> val(%d,%d) = %f", i, winPt.i, winPt.j, curr_val);
00730 
00731 //       Image<float> disp2 = zoomXY(output,scale);
00732 //       float min,max; getMinMax(disp2, min,max);
00733 //       float imean  = float(mean(output));
00734 //       float istdev = float(stdev(output));
00735 //       if(itsWinningChan[index] == i)
00736 //         LINFO("WIN Chan[%d]: val: %f [%f,%f] -> %f === Min: %f, Max: %f",
00737 //               i, curr_val, imean, istdev, (curr_val - imean)/istdev, min, max);
00738 //       else
00739 //         LINFO("    Chan[%d]: val: %f [%f,%f] -> %f === Min: %f, Max: %f",
00740 //               i, curr_val, imean, istdev, (curr_val - imean)/istdev, min, max);
00741 //       //drawDisk(disp2, itsWinner[index], 2, max);
00742 //       //drawRect(disp2, rsc, max);
00743 //       //drawRect(disp2, rscc, max/2.0F);
00744 //       inplaceNormalize(disp2, 0.0F, 1.0F);
00745 //       if(itsWinningChan[index] == i)
00746 //         inplacePaste(disp, disp2, Point2D<int>(0, 0));
00747 
00748 //       //itsWin->drawImage(disp,0,0);
00749 //       //Raster::waitForKey();
00750 
00751 //       // loop over the feature maps
00752 //       //if(i == itsWinningChan)
00753 //       for (uint j = 0; j < 8; j++)
00754 //         {
00755 //           Image<float> submap = itsImgPyrs[i][j];
00756 //           float curr_val =
00757 //             submap.getVal(downScaleCoords(itsWinner[index], submap.getDims()));
00758 //           Image<float> disp3(w,h,ZEROS);
00759 //           getMinMax(submap, min,max);
00760 //           LINFO("  fmap[%d][%d]: [%d,%d] val: %f Min: %f, Max: %f",
00761 //                 i,j, submap.getWidth(), submap.getHeight(), curr_val,min,max);
00762 
00763 //           if(j < 6)
00764 //             {
00765 //               inplacePaste(disp3, zoomXY(submap, int(pow(2,j))), Point2D<int>(0,0));
00766 //               //drawDisk(disp3, itsWinner[index], 2, max);
00767 //               //drawRect(disp3, rsc, max);
00768 //               //drawRect(disp3, rscc, max/2.0F);
00769 //               inplaceNormalize(disp3, 0.0F, 1.0F);
00770 //               drawGrid(disp3, w/4,h/4,1,1, 1.0F);
00771 //               inplacePaste(disp, disp3, Point2D<int>(0, h));
00772 //               itsWin->drawImage(disp,0,0);
00773 //               Raster::waitForKey();
00774 //             }
00775 //         }
00776 
00777 //       // loop over the csmaps
00778 //       //if(i == itsWinningChan)
00779 //       for (uint j = 0; j < itsCSMaps[i].size(); j++)
00780 //         {
00781 //           Image<float> csmap = itsCSMaps[i][j];
00782 //           float curr_val =
00783 //             csmap.getVal(downScaleCoords(itsWinner[index], csmap.getDims()));
00784 //           imean  = float(mean(csmap));
00785 //           istdev = float(stdev(csmap));
00786 //           Image<float> disp4;
00787 
00788 //           for(uint k = 0;  k < 6; k++)
00789 //             {
00790 //               disp4 = toPower(zoomXY(csmap, scale), (1.0+k*.2));
00791 //               getMinMax(disp4, min,max);
00792 //               LINFO("    csmap[%d][%d]: val: %f [%f,%f] -> %f = {%f %f}",
00793 //                     i,j, curr_val, imean, istdev,
00794 //                     (curr_val - imean)/istdev, min, max);
00795 
00796 //               //drawDisk(disp4, itsWinner[index], 2, max);
00797 //               //drawRect(disp4, rsc, max);
00798 //               //drawRect(disp4, rscc, max/2.0F);
00799 //               inplaceNormalize(disp4, 0.0F, 1.0F);
00800 //               drawGrid(disp4, w/4,h/4,1,1, 1.0F);
00801 //               //if(j == itsWinningSubmapNum[index])
00802 //               inplacePaste(disp, disp4, Point2D<int>(w, h));
00803 //               itsWin->drawImage(disp,0,0);
00804 //               Raster::waitForKey();
00805 //             }
00806 //         }
00807 //     }
00808 //   itsWin->drawImage(disp,0,0);
00809 //   Raster::waitForKey();
00810 
00811   return rscc;
00812 }
00813 
00814 // ######################################################################
00815 Rectangle BeobotBrainMT::correctBB(Rectangle r, Point2D<int> p, int w, int h)
00816 {
00817   float wlow = .35;        float whigh = .5;
00818   float hlow = .35;        float hhigh = .5;
00819 
00820   // pixel slack
00821   int pslack = 5;
00822 
00823   int tr = r.top();       int lr = r.left();
00824   int br = r.bottomI();    int rr = r.rightI();
00825   int wr = r.width();     int hr = r.height();
00826   LDEBUG("SE bb [%3d,%3d,%3d,%d](%dx%d): p:[%d,%d]",
00827          tr, lr, br, rr, wr, hr, p.i, p.j);
00828 
00829   // before we start make sure that
00830   // the salient point is within the rectangle
00831   if(((lr + pslack) > p.i) || ((rr - pslack) < p.i) ||
00832      ((tr + pslack) > p.j) || ((br - pslack) < p.j)   )
00833     {
00834       LDEBUG("point is out of or near border of box");
00835 
00836       // expand window to get the points
00837       // to be more in the center
00838       if((lr + pslack) > p.i)
00839         { lr = p.i - pslack; if(lr < 0  ) lr = 0;  LDEBUG("left  "); }
00840       if((rr - pslack) < p.i)
00841         { rr = p.i + pslack; if(rr > w-1) rr = w-1;LDEBUG("right "); }
00842       if((tr + pslack) > p.j)
00843         { tr = p.j - pslack; if(tr < 0  ) tr = 0;  LDEBUG("top   "); }
00844       if((br - pslack) < p.j)
00845         { br = p.j + pslack; if(br > h-1) br = h-1;LDEBUG("bottom"); }
00846 
00847       wr = rr - lr;
00848       hr = br - tr;
00849 
00850       LDEBUG("CNT SE bb [%3d,%3d,%3d,%d](%dx%d) p: [%d,%d]",
00851              tr, lr, br, rr, wr, hr, p.i, p.j);
00852     }
00853 
00854   // correct for size
00855 
00856   // correct the width: make sure it's not too small
00857   if(wr < wlow * w)
00858     {
00859       int wdiff = int(wlow * w) - wr;
00860       float pnt = float(wdiff)/(wr + 0.0f);
00861       LDEBUG("enlarge width (%d -> %d) by: %d (%f)",
00862              wr, int(wlow * w), wdiff, pnt);
00863 
00864       int ls = 0, rs = 0;
00865       if((p.i - lr)/(wr+0.0) > .7)
00866         {
00867           ls = int(.7 * wr); rs = int(.3 * wr); LDEBUG("left skew");
00868         }
00869       else if((rr - p.i)/(wr+0.0) > .7)
00870         {
00871           ls = int(.3 * wr); rs = int(.7 * wr); LDEBUG("right skew");
00872         }
00873       else
00874         {
00875           ls = p.i - lr; rs = rr - p.i; LDEBUG("horz centered");
00876         }
00877       int ladd = int(ls * pnt);
00878       int radd = int(rs * pnt);
00879       LDEBUG("add: l: %d, r: %d", ladd, radd);
00880 
00881       if(lr < ladd)
00882         {
00883           rr += radd + ladd - lr; lr = 0;
00884           LDEBUG("hit left border: lr: %d, rr:%d", lr, rr);
00885         }
00886       else if(w - 1 < (rr + radd))
00887         {
00888           ladd += rr + radd - w + 1;
00889           lr = lr - ladd;
00890           rr = w - 1;
00891           LDEBUG("hit right border: lr: %d, rr:%d", lr, rr);
00892         }
00893       else
00894         {
00895           lr = lr - ladd; rr = rr + radd;
00896           LDEBUG("centered: lr: %d, rr:%d", lr, rr);
00897         }
00898     }
00899 
00900   // or too large
00901   else if(wr > whigh * w)
00902     {
00903       int wdiff = wr - int(whigh * w);
00904       float pnt = float(wdiff)/(wr + 0.0f);
00905       LDEBUG("decrease width (%d -> %d) by: %d (%f)",
00906             wr, int(whigh * w), wdiff, pnt);
00907       int mid = (lr + rr)/2;
00908       LDEBUG("mid horz: %d", mid);
00909 
00910       // if the sal pt is to the left of mid point
00911       if(mid > p.i)
00912         {
00913           // check sal point slack before cutting
00914           int ldiff = p.i - lr;
00915           LDEBUG("Point more left %d, right %d", ldiff, rr - p.i);
00916           if(ldiff >= pslack)
00917             {
00918               int cut = int (ldiff * pnt);
00919               lr = lr + cut;
00920               wdiff-= cut;
00921               LDEBUG("cut left slack %d, lr %d, wdiff now: %d",
00922                      cut, lr, wdiff);
00923             }
00924           else LDEBUG("left is not cut");
00925 
00926           rr-= wdiff; LDEBUG("rr: %d", rr);
00927         }
00928       // if it's to the right
00929       else
00930         {
00931           // check sal point slack before cutting
00932           int rdiff = rr - p.i;
00933           LDEBUG("Point more right %d left: %d", rdiff, p.i - lr);
00934           if(rdiff >= pslack)
00935             {
00936               int cut = int (rdiff * pnt);
00937               rr = rr - cut;
00938               wdiff-= cut;
00939               LDEBUG("cut right slack %d, rr %d, wdiff now: %d",
00940                      cut, rr, wdiff);
00941             }
00942           else LDEBUG("right is not cut");
00943 
00944           lr+= wdiff; LDEBUG("lr: %d", lr);
00945         }
00946     }
00947 
00948   // correct the height: make sure it's not too small
00949   if(hr < hlow * h)
00950     {
00951       int hdiff = int(hlow * h) - hr;
00952       float pnt = float(hdiff)/(hr + 0.0f);
00953       LDEBUG("increase the height (%d -> %d) by: %d (%f)",
00954              hr, int(hlow * h), hdiff, pnt);
00955 
00956       int ts = 0, bs = 0;
00957       if((p.j - tr)/(hr+0.0) > .7)
00958         {
00959           ts = int(.7 * hr); bs = int(.3 * hr); LDEBUG("top skew");}
00960       else if((br - p.j)/(hr+0.0) > .7)
00961         {
00962           ts = int(.3 * hr); bs = int(.7 * hr); LDEBUG("bottom skew");
00963         }
00964       else
00965         {
00966           ts = p.j - tr; bs = br - p.j; LDEBUG("vert centered");
00967         }
00968 
00969       int tadd = int(ts * pnt);
00970       int badd = int(bs * pnt);
00971       LDEBUG("adding: t: %d, b: %d", tadd, badd);
00972 
00973       if(tr < tadd)
00974         {
00975           br += badd + tadd - tr; tr = 0;
00976           LDEBUG("hit top border: tr: %d, br:%d", tr, br);
00977         }
00978       else if(h - 1 < (br + badd))
00979         {
00980           tadd += br + badd - h + 1;
00981           tr = tr - tadd;
00982           br = h - 1;
00983           LDEBUG("hit bottom border: tr: %d, br:%d", tr, br);
00984         }
00985       else
00986         {
00987           tr = tr - tadd; br = br + badd;
00988           LDEBUG("centered vertically: lr: %d, rr:%d", tr, br);
00989         }
00990     }
00991   // or too large
00992   else if(hr > hhigh * h)
00993     {
00994       int hdiff = hr - int(hhigh * h);
00995       float pnt = float(hdiff)/(hr + 0.0f);
00996       LDEBUG("enlarge the heigth (%d -> %d) by: %d (%f)",
00997              hr, int(hhigh * h), hdiff, pnt);
00998       int mid = (tr + br)/2;
00999       LDEBUG("mid vert: %d", mid);
01000 
01001       // if the sal pt is on top of midpoint
01002       if(mid > p.j)
01003         {
01004           // check sal point slack before cutting
01005           int tdiff = p.j - tr;
01006           LDEBUG("Point more top %d, bottom %d", tdiff, br - p.j);
01007           if(tdiff >= pslack)
01008             {
01009               int cut = int (tdiff * pnt);
01010               tr = tr + cut;
01011               hdiff-= cut;
01012               LDEBUG("cut top slack %d, tr %d, hdiff now: %d",
01013                      cut, tr, hdiff);
01014             }
01015           else LDEBUG("top not cut");
01016 
01017           br-= hdiff; LDEBUG("br: %d", br);
01018         }
01019       // if it's lower than the midpoint
01020       else
01021         {
01022           // check sal point slack before cutting
01023           int bdiff = br - p.j;
01024           LDEBUG("Point more bottom %d top: %d", bdiff, p.j - tr);
01025           if(bdiff >= pslack)
01026             {
01027               int cut = int (bdiff * pnt);
01028               br = br - cut;
01029               hdiff-= cut;
01030               LDEBUG("cut bottom slack %d, br %d, wdiff now: %d",
01031                      cut, br, hdiff);
01032             }
01033           else LDEBUG("bottom not cut");
01034 
01035           tr+= hdiff; LDEBUG("tr: %d", tr);
01036         }
01037     }
01038 
01039   // also, after we are finished make sure that
01040   // the salient point is within the rectangle
01041   if(((lr + pslack) > p.i) || ((rr - pslack) < p.i) ||
01042      ((tr + pslack) > p.j) || ((br - pslack) < p.j)   )
01043     {
01044       LDEBUG("point is out of or near border of box");
01045       if((lr + pslack) > p.i)
01046         { lr = p.i - pslack; if(lr < 0  ) lr = 0;   LDEBUG("left  "); }
01047       if((rr - pslack) < p.i)
01048         { rr = p.i + pslack; if(rr > w-1) rr = w-1; LDEBUG("right "); }
01049       if((tr + pslack) > p.j)
01050         { tr = p.j - pslack; if(tr < 0  ) tr = 0;   LDEBUG("top   "); }
01051       if((br - pslack) < p.j)
01052         { br = p.j + pslack; if(br > h-1) br = h-1; LDEBUG("bottom"); }
01053     }
01054 
01055   Rectangle newR = Rectangle::tlbrI(tr, lr, br, rr);
01056   LDEBUG("SE bb [%3d,%3d,%3d,%d](%dx%d): p:[%d,%d]",
01057          tr, lr, br, rr, newR.width(), newR.height(), p.i, p.j);
01058   return newR;
01059 }
01060 
01061 // ######################################################################
01062 void BeobotBrainMT::computeSalientFeatures()
01063 {
01064   itsSalientFeatures.resize(itsWinner.size());
01065   for(uint i = 0; i < itsWinner.size(); i++)
01066     {
01067       // The coordinates we receive are at the scale of the original
01068       // image, and we will need to rescale them to the size of the
01069       // various submaps we read from. The first image in our first
01070       // pyramid has the dims of the input:
01071       //float iw = float(itsImgPyrs[0][0].getWidth());
01072       //float ih = float(itsImgPyrs[0][0].getHeight());
01073       itsSalientFeatures[i].clear();
01074       Point2D<int> locn = itsWinner[i];
01075       for(uint c = 0; c < itsNumChannels; c++)
01076         {
01077           for(int di = -2; di < 3; di++)
01078             for(int dj = -2; dj < 3; dj++)
01079               {
01080                 uint fidx = 0;
01081                 for (int lev = level_min; lev <= level_max; lev ++)
01082                   for (int delta = delta_min; delta <= delta_max; delta ++)
01083                     {
01084 //                   ImageSet<float> pyr = itsImgPyrs[c];
01085 //                   uint clev = lev, slev = lev + delta;
01086 
01087 //                   // read center value with bilinear interpolation:
01088 //                   float cval = pyr[clev].getValInterpScaled(locn, itsImgPyrs[0][0].getDims());
01089 
01090 //                   // read surround value with bilinear interpolation:
01091 //                   float cval = pyr[slev].getValInterpScaled(locn, itsImgPyrs[0][0].getDims());
01092 
01093 //                   cval+=0.0F; sval+= 0.0F;
01094 //                   compute center - surround and take absolute value
01095 //                   float rval = fabs(cval - sval);
01096 //                   float minv, maxv;
01097 //                   getMinMax(itsRawCSMaps[c][fidx], minv, maxv);
01098 //                   LINFO("rawcs min: %f max: %f ", minv, maxv);
01099 //                   float val = (rval - minv)/(maxv - minv);
01100 //                   if(val < 0.0F) val = 0.0F; else if(val > 1.0F) val = 1.0F;
01101 //                   LINFO("(%d,%d:%d,%d) [c(%f,%f): %f |s(%f,%f): %f]"
01102 //                         "[%f %f] ->%f -> %f",
01103 //                     locn.i, locn.j, clev, slev, cx, cy, cval, sx, sy, sval,
01104 //                     minv, maxv, rval, val);
01105 
01106                       // CS maps implementations
01107                       // Features are all normalized [ 0.0 ... 1.0 ]
01108                       // but if range is < 1.0 (no dominant peak) val = 0.0
01109                       int csi = locn.i/4, csj = locn.j/4;
01110                       float rval = 0.0;
01111                       if(itsCSMaps[c][fidx].coordsOk(csi + di, csj + dj))
01112                         rval = fabs(itsCSMaps[c][fidx].getVal(csi, csj));
01113                       float minv, maxv;
01114                       getMinMax(itsCSMaps[c][fidx], minv, maxv);
01115                       float val;
01116                       if((maxv - minv) < 1.0) val = 0.0F;
01117                       else val = (rval - minv)/(maxv - minv);
01118                       //LINFO("(%d,%d:%d,%d):[%f %f]  %f -> %f",
01119                       //      csi, csj ,clev, slev, minv, maxv, rval, val);
01120 
01121                       itsSalientFeatures[i].push_back(double(val));
01122                       fidx++;
01123                     }
01124               }
01125         }
01126     }
01127 }
01128 
01129 // ######################################################################
01130 /* So things look consistent in everyone's emacs... */
01131 /* Local Variables: */
01132 /* indent-tabs-mode: nil */
01133 /* End: */
Generated on Sun May 8 08:04:28 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3