TensorVoting.C

Go to the documentation of this file.
00001 /*!@file SceneUnderstanding/TensorVoting.C  */
00002 
00003 // //////////////////////////////////////////////////////////////////// //
00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00005 // by the 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: Lior Elazary <elazary@usc.edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/plugins/SceneUnderstanding/TensorVoting.C $
00035 // $Id: TensorVoting.C 13878 2010-09-03 18:45:11Z lior $
00036 //
00037 
00038 #ifndef TensorVoting_C_DEFINED
00039 #define TensorVoting_C_DEFINED
00040 
00041 #include "plugins/SceneUnderstanding/TensorVoting.H"
00042 
00043 #include "Image/DrawOps.H"
00044 #include "Image/MathOps.H"
00045 //#include "Image/OpenCVUtil.H"
00046 #include "Image/Kernels.H"
00047 #include "Image/FilterOps.H"
00048 #include "Image/Transforms.H"
00049 #include "Image/fancynorm.H"
00050 #include "Image/Convolutions.H"
00051 #include "Simulation/SimEventQueue.H"
00052 #include "GUI/DebugWin.H"
00053 //#include "Image/OpenCVUtil.H"
00054 #include <math.h>
00055 #include <fcntl.h>
00056 #include <limits>
00057 #include <string>
00058 
00059 // ######################################################################
00060 TensorVoting::TensorVoting() :
00061   itsSigma(2)
00062 {
00063   createTensorFields(itsSigma);
00064   //generateImage();
00065 }
00066 
00067 void TensorVoting::createTensorFields(float sigma)
00068 {
00069   //Create the tensor fields first, so we can save time latter
00070   itsBallField  = createBallTensorField(sigma);
00071 
00072   itsStickFields.clear();
00073   itsStickFields.resize(180);
00074 
00075   for(int i=0; i<180; i++)
00076   {
00077     float u = cos(i*M_PI/180);
00078     float v = sin(i*M_PI/180);
00079     TensorField stick = createStickTensorField(u, v, sigma);
00080     itsStickFields[i] = stick;
00081   }
00082 
00083 }
00084 
00085 
00086 void TensorVoting::generateImage()
00087 {
00088   //This is used for testing
00089   inputImg = Image<float>(241, 521, ZEROS);
00090   for(uint i=0; i<inputImg.size(); i++)
00091     inputImg[i] = -1;
00092   inputImg.setVal(21,266,0.000000);
00093   inputImg.setVal(23,303,6.709837);
00094   inputImg.setVal(23,306,0.000000);
00095   inputImg.setVal(27,334,14.036243);
00096   inputImg.setVal(32,356,14.036243);
00097   inputImg.setVal(37,372,15.945396);
00098   inputImg.setVal(48,399,24.443954);
00099   inputImg.setVal(57,418,24.443954);
00100   inputImg.setVal(64,427,32.005383);
00101   inputImg.setVal(76,443,39.805573);
00102   inputImg.setVal(89,456,53.130100);
00103   inputImg.setVal(109,470,49.398705);
00104   inputImg.setVal(131,478,73.300758);
00105   inputImg.setVal(140,478,90.000000);
00106   inputImg.setVal(173,485,60.945396);
00107   inputImg.setVal(185,493,57.994617);
00108   inputImg.setVal(199,503,50.194427);
00109   inputImg.setVal(218,516,75.963753);
00110   inputImg.setVal(231,520,63.434948);
00111   inputImg.setVal(236,517,233.130096);
00112   inputImg.setVal(240,496,206.565048);
00113   inputImg.setVal(223,481,251.565048);
00114   inputImg.setVal(213,475,233.130096);
00115   inputImg.setVal(186,450,219.805573);
00116   inputImg.setVal(182,439,200.556046);
00117   inputImg.setVal(174,418,203.962494);
00118   inputImg.setVal(167,405,206.565048);
00119   inputImg.setVal(152,373,203.962494);
00120   inputImg.setVal(143,348,197.525574);
00121   inputImg.setVal(136,328,197.525574);
00122   inputImg.setVal(133,309,187.594650);
00123   inputImg.setVal(131,303,180.000000);
00124   inputImg.setVal(129,279,180.000000);
00125   inputImg.setVal(130,258,180.000000);
00126   inputImg.setVal(131,244,180.000000);
00127   inputImg.setVal(134,216,171.869904);
00128   inputImg.setVal(136,203,165.963760);
00129   inputImg.setVal(144,173,163.300751);
00130   inputImg.setVal(152,151,159.443954);
00131   inputImg.setVal(161,131,156.037506);
00132   inputImg.setVal(172,109,151.699249);
00133   inputImg.setVal(175,101,158.198593);
00134   inputImg.setVal(185,72,164.744888);
00135   inputImg.setVal(187,59,168.690063);
00136   inputImg.setVal(189,46,180.000000);
00137   inputImg.setVal(177,18,243.434952);
00138   inputImg.setVal(168,15,270.000000);
00139   inputImg.setVal(140,19,291.801422);
00140   inputImg.setVal(122,27,300.963745);
00141   inputImg.setVal(102,44,320.194427);
00142   inputImg.setVal(89,61,323.130096);
00143   inputImg.setVal(80,74,333.434937);
00144   inputImg.setVal(65,103,335.224854);
00145   inputImg.setVal(60,114,338.198578);
00146   inputImg.setVal(50,138,338.198578);
00147   inputImg.setVal(42,160,340.016907);
00148   inputImg.setVal(38,173,345.963745);
00149   inputImg.setVal(33,196,345.963745);
00150   inputImg.setVal(28,216,348.690063);
00151   inputImg.setVal(23,248,353.290161);
00152   inputImg.setVal(23,250,0.000000);
00153 
00154 
00155 }
00156 
00157 // ######################################################################
00158 TensorVoting::~TensorVoting()
00159 {
00160 }
00161 
00162 
00163 TensorField TensorVoting::transImage(Image<float>& img)
00164 {
00165 
00166   TensorField tensorField(img.getDims(), ZEROS);
00167 
00168   for(int j=0; j<img.getHeight(); j++)
00169     for(int i=0; i<img.getWidth(); i++)
00170     {
00171       float val = img.getVal(i,j);
00172       if (val >= 0)
00173       {
00174           double x = cos((img.getVal(i)*M_PI/180) + (90*M_PI)/180);
00175           double y = sin((img.getVal(i)*M_PI/180) + (90*M_PI)/180);
00176 
00177           tensorField.t1.setVal(img.getWidth()+1-i, j, x*x);
00178           tensorField.t2.setVal(img.getWidth()+1-i, j, x*y);
00179           tensorField.t3.setVal(img.getWidth()+1-i, j, x*y);
00180           tensorField.t4.setVal(img.getWidth()+1-i, j, y*y);
00181       }
00182     }
00183 
00184   return tensorField;
00185 }
00186 
00187 TensorField TensorVoting::createStickTensorField(float u, float v, float sigma)
00188 {
00189   // Calculate the window size from sigma using
00190   // equation 5.7 from Emerging Topics in Computer Vision
00191   // make the field odd, if it turns out to be even.
00192   double ws = floor( ceil(sqrt(-log(0.01)*sigma*sigma)*2) / 2 )*2 + 1;
00193   double wHalf = (ws-1)/2;
00194 
00195   //// Turn the unit vector into a rotation matrix
00196   //double btheta = atan2(v,u);
00197 
00198 
00199   //Generate our theta's at each point in the
00200   //field, adjust by our base theta so we rotate
00201   //in funcion. Also generate the attenuation field at the same time
00202   //This is taken from Equation
00203   //5.2 in Emerging Topics in Computer Vision. Note our
00204   //thetas must be symmetric over the Y axis for the arc
00205   //length to be correct so there's a bit of a coordinate
00206   //translation.
00207 
00208   Image<float> theta((int)ws, (int)ws, ZEROS);
00209   Image<float> DF((int)ws, (int)ws, ZEROS);
00210   double norm = sqrt(u*u + v*v);
00211   double uNorm = u/norm;
00212   double vNorm = v/norm;
00213   for(int j=0; j<ws; j++)
00214     for(int i=0; i<ws; i++)
00215     {
00216       double Zj = j-wHalf;
00217       double Zi = wHalf-i;
00218       double x = vNorm*Zi + uNorm*Zj;
00219       double y = uNorm*Zi - vNorm*Zj ;
00220       double th = atan2(y,x);
00221       theta.setVal(i,j, th);
00222 
00223       //The attenuation field
00224       th = fabs(th);
00225       if (th > M_PI/2)
00226         th = M_PI - th;
00227       th = 4*th; //This was not in the original spec
00228 
00229       double l = sqrt(x*x + y*y);
00230 
00231       double s = 0;
00232       if (l != 0 && th != 0)
00233         s = (th*l)/sin(th);
00234       else if (l==0 || th == 0)
00235         s = l;
00236 
00237       double k=0;
00238       if (l != 0)
00239         k = 2*sin(th)/l;
00240 
00241       double c= (-16*log2(0.1)*(sigma-1))/(M_PI*M_PI);
00242       double df = exp(-((s*s+c*(k*k))/(sigma*sigma)));
00243       if (th <= M_PI/2)
00244         DF.setVal(i,j,df);
00245     }
00246 
00247   //Generate the final tensor field attenuated
00248   TensorField stickField(theta.getDims(), NO_INIT);
00249   double bTheta = atan2(v,u);
00250   for(int j=0; j<theta.getHeight(); j++)
00251     for(int i=0; i<theta.getWidth(); i++)
00252     {
00253       double th = theta.getVal(i,j);
00254       double b1 = -sin(2*th + bTheta);
00255       double b2 = cos(2*th + bTheta);
00256 
00257       double att = DF.getVal(i,j);
00258       stickField.t1.setVal(i, j, b1*b1*att);
00259       stickField.t2.setVal(i, j, b1*b2*att);
00260       stickField.t3.setVal(i, j, b1*b2*att);
00261       stickField.t4.setVal(i, j, b2*b2*att);
00262     }
00263 
00264   return stickField;
00265 }
00266 
00267 TensorField TensorVoting::createBallTensorField(float sigma)
00268 {
00269 
00270   //Create a ball tensor field by spinning a stick tensor
00271   double wsize = ceil(sqrt(-log(0.01)*sigma*sigma)*2);
00272   wsize = floor(wsize/2)*2+1;
00273 
00274   TensorField ballField(Dims((int)wsize, (int)wsize), ZEROS);
00275   for(float theta=0; theta < 2*M_PI; theta += (2*M_PI)/36)
00276   {
00277     float u = cos(theta); float v = sin(theta);
00278 
00279     TensorField stickField = createStickTensorField(u,v,sigma);
00280     ballField += stickField;
00281   }
00282 
00283   //Normalize the field
00284   ballField /= 36;
00285 
00286   EigenSpace eigen = getTensorEigen(ballField);
00287 
00288   return ballField;
00289 
00290 }
00291 
00292 
00293 void TensorVoting::getBallVotes(Image<float>& img,
00294     TensorField& tensorField, float sigma)
00295 {
00296 
00297   //Calculate Ball voting Field
00298   //TensorField ballField = createBallTensorField(sigma);
00299   TensorField ballField = itsBallField;
00300 
00301   //Go through the image, and vote at given feature position
00302   for(int j=0; j<img.getHeight(); j++)
00303     for(int i=0; i<img.getWidth(); i++)
00304     {
00305       float val=img.getVal(i,j);
00306 
00307       if (val > 0)
00308       {
00309         //Go through the vote template and vote
00310         for(int y=0; y<ballField.t1.getHeight(); y++)
00311           for(int x=0; x<ballField.t1.getWidth(); x++)
00312           {
00313             int ii = i+x - (ballField.t1.getWidth()/2);
00314             int jj = j+y - (ballField.t1.getHeight()/2);
00315 
00316             if (tensorField.t1.coordsOk(ii,jj))
00317             {
00318               tensorField.t1.setVal(ii, jj,
00319                   tensorField.t1.getVal(ii,jj) +
00320                   ballField.t1.getVal(x,y)*val);
00321 
00322               tensorField.t2.setVal(ii, jj,
00323                   tensorField.t2.getVal(ii,jj) +
00324                   ballField.t2.getVal(x,y)*val);
00325 
00326               tensorField.t3.setVal(ii, jj,
00327                   tensorField.t3.getVal(ii,jj) +
00328                   ballField.t3.getVal(x,y)*val);
00329 
00330               tensorField.t4.setVal(ii, jj,
00331                   tensorField.t4.getVal(ii,jj) +
00332                   ballField.t4.getVal(x,y)*val);
00333             }
00334 
00335           }
00336       }
00337     }
00338 }
00339 
00340 TensorField TensorVoting::getStickVotes(const TensorField& tensorField,
00341                                         float sigma)
00342 {
00343 
00344   //Calculate stick voting Field
00345 
00346   EigenSpace eigen = getTensorEigen(tensorField);
00347 
00348   TensorField voteField = tensorField;
00349 
00350   //Go thought the tensor, and vote at given feature position
00351   for(int j=0; j<eigen.l1.getHeight(); j++)
00352     for(int i=0; i<eigen.l1.getWidth(); i++)
00353     {
00354       //if the difference between the eigenvectors is greater the 0
00355       //then we have a stick vote
00356 
00357       float val=eigen.l1.getVal(i,j) - eigen.l2.getVal(i,j);
00358       if (val > 0)
00359       {
00360         //Get the direction of the vote from e1, while the weight is l1-l2
00361         float u = eigen.e1[1].getVal(i,j);
00362         float v = eigen.e1[0].getVal(i,j);
00363 
00364         //int angIdx = round((atan(u/v)-(M_PI/2))*180/M_PI);
00365         int angIdx = (int)round(atan(-u/v)*180/M_PI);
00366         if (angIdx < 0) angIdx += 180;
00367 
00368         //TensorField stickField = createStickTensorField(-u, v, sigma);
00369         TensorField stickField = itsStickFields.at(angIdx);
00370 
00371         //Go through the vote template and vote
00372         for(int y=0; y<stickField.t1.getHeight(); y++)
00373           for(int x=0; x<stickField.t1.getWidth(); x++)
00374           {
00375             int ii = i+x - (stickField.t1.getWidth()/2);
00376             int jj = j+y - (stickField.t1.getHeight()/2);
00377 
00378             if (voteField.t1.coordsOk(ii,jj))
00379             {
00380               voteField.t1.setVal(ii, jj,
00381                   voteField.t1.getVal(ii,jj) +
00382                   stickField.t1.getVal(x,y)*val);
00383               voteField.t2.setVal(ii, jj,
00384                   voteField.t2.getVal(ii,jj) +
00385                   stickField.t2.getVal(x,y)*val);
00386               voteField.t3.setVal(ii, jj,
00387                   voteField.t3.getVal(ii,jj) +
00388                   stickField.t3.getVal(x,y)*val);
00389               voteField.t4.setVal(ii, jj,
00390                   voteField.t4.getVal(ii,jj) +
00391                   stickField.t4.getVal(x,y)*val);
00392             }
00393           }
00394       }
00395     }
00396 
00397   return voteField;
00398 
00399 }
00400 
00401 TensorField TensorVoting::getStickVotes2(const TensorField& tensorField,
00402                                         float sigma)
00403 {
00404 
00405   EigenSpace eigen = getTensorEigen(tensorField);
00406 
00407   TensorField voteField(tensorField.t1.getDims(), ZEROS);
00408 
00409   //Go thought the tensor, and vote at given feature position
00410   for(int j=0; j<eigen.l1.getHeight(); j++)
00411     for(int i=0; i<eigen.l1.getWidth(); i++)
00412     {
00413       //if the difference between the eigenvectors is greater the 0
00414       //then we have a stick vote
00415 
00416       float val=eigen.l1.getVal(i,j) - eigen.l2.getVal(i,j);
00417       if (val > 0)
00418       {
00419         //Get the direction of the vote from e1, while the weight is l1-l2
00420         float u = eigen.e1[1].getVal(i,j);
00421         float v = eigen.e1[0].getVal(i,j);
00422 
00423         //int angIdx = round((atan(u/v)-(M_PI/2))*180/M_PI);
00424         int angIdx = (int)round(atan(-u/v)*180/M_PI);
00425         if (angIdx < 0) angIdx += 180;
00426 
00427         //TensorField stickField = createStickTensorField(-u, v, sigma);
00428         TensorField stickField = itsStickFields.at(angIdx);
00429 
00430         //Go through the vote template and vote
00431         val=1;
00432         for(int y=0; y<stickField.t1.getHeight(); y++)
00433           for(int x=0; x<stickField.t1.getWidth(); x++)
00434           {
00435             int ii = i+x - (stickField.t1.getWidth()/2);
00436             int jj = j+y - (stickField.t1.getHeight()/2);
00437 
00438             if (voteField.t1.coordsOk(ii,jj))
00439             {
00440               voteField.t1.setVal(ii, jj,
00441                   voteField.t1.getVal(ii,jj) +
00442                   stickField.t1.getVal(x,y)*val);
00443               voteField.t2.setVal(ii, jj,
00444                   voteField.t2.getVal(ii,jj) +
00445                   stickField.t2.getVal(x,y)*val);
00446               voteField.t3.setVal(ii, jj,
00447                   voteField.t3.getVal(ii,jj) +
00448                   stickField.t3.getVal(x,y)*val);
00449               voteField.t4.setVal(ii, jj,
00450                   voteField.t4.getVal(ii,jj) +
00451                   stickField.t4.getVal(x,y)*val);
00452             }
00453           }
00454       }
00455     }
00456 
00457   return voteField;
00458 
00459 }
00460 
00461 TensorField TensorVoting::calcSparseField(Image<float>& img)
00462 {
00463 
00464   TensorField tensorField(img.getDims(), ZEROS);
00465 
00466   for(uint i=0; i<img.size(); i++)
00467   {
00468     if (img[i] > 0)
00469     {
00470       tensorField.t1.setVal(i, 1);
00471       tensorField.t2.setVal(i, 0);
00472       tensorField.t3.setVal(i, 0);
00473       tensorField.t4.setVal(i, 1);
00474     }
00475   }
00476 
00477   return tensorField;
00478 
00479 }
00480 
00481 TensorField TensorVoting::calcRefinedField(TensorField& tensorField,
00482                                       Image<float>& img,
00483                                       float sigma)
00484 {
00485 
00486   TensorField ballVoteField = tensorField;
00487   getBallVotes(img, ballVoteField, sigma);
00488 
00489   ////Erase anything that is not in the original image
00490 
00491   for(uint i=0; i<img.size(); i++)
00492     if (img[i] == 0)
00493     {
00494       ballVoteField.t1.setVal(i, 0);
00495       ballVoteField.t2.setVal(i, 0);
00496       ballVoteField.t3.setVal(i, 0);
00497       ballVoteField.t4.setVal(i, 0);
00498     }
00499 
00500   return (tensorField+ballVoteField);
00501 
00502 }
00503 
00504 TensorField TensorVoting::findFeatures(TensorField& tensorField, float sigma)
00505 {
00506 
00507   EigenSpace eigen = getTensorEigen(tensorField);
00508 
00509  // Image<float> im = img;
00510  // //Normalize the gray scale image from 0 to 1;
00511  // float minVal, maxVal;
00512  // getMinMax(im, minVal, maxVal);
00513  // for(uint i=0; i<im.size(); i++)
00514  //   im[i] = im[i] / maxVal;
00515 
00516 
00517   //First step is to produce the initially encode the image
00518   //as sparse tensor tokens.
00519   TensorField sparseTf = calcSparseField(eigen.l1);
00520 
00521   LINFO("Refined field");
00522   TensorField refinedTf = calcRefinedField(sparseTf, eigen.l1, sigma);
00523 
00524 
00525   ////third run is to apply the stick tensor voting after
00526   ////zero'ing out the e2(l2) components so that everything
00527   ////is a stick vote.
00528   eigen = getTensorEigen(refinedTf);
00529 
00530   eigen.l2.clear();
00531   TensorField zeroTf = getTensor(eigen);
00532 
00533   LINFO("Stick Votes");
00534   tensorField = getStickVotes(zeroTf, sigma);
00535   LINFO("Done");
00536 
00537   return tensorField;
00538 
00539 }
00540 
00541 // ######################################################################
00542 void TensorVoting::evolve()
00543 {
00544   SHOWIMG(inputImg);
00545   TensorField tensorField = transImage(inputImg);
00546 
00547 
00548   tensorField = findFeatures(tensorField, itsSigma);
00549 
00550   //Show the features
00551   EigenSpace eigen = getTensorEigen(tensorField);
00552   Image<float> features = eigen.l1-eigen.l2;
00553   SHOWIMG(features);
00554 
00555 
00556 }
00557 
00558 TensorField TensorVoting::evolve(const Image<PixRGB<byte> >& img)
00559 {
00560 
00561   Image<float> lum = luminance(img);
00562   SHOWIMG(lum);
00563   TensorField tensorField = getTensor(lum);
00564 
00565   //Extract tokens by keeping only the edges with values grater
00566   //then 10% of the max mag.
00567   Image<float> mag = getTensorMag(tensorField);
00568   float min, max;
00569   getMinMax(mag, min,max);
00570 
00571   for(uint i=0; i<mag.size(); i++)
00572     if (mag[i] < max*0.10)
00573       tensorField.setVal(i,0);
00574 
00575   tensorField = getStickVotes2(tensorField, itsSigma);
00576 
00577   return tensorField;
00578 
00579 }
00580 
00581 TensorField TensorVoting::evolve(const TensorField& tf, bool performNonMaxSurp)
00582 {
00583   TensorField tensorField = tf;
00584 
00585   ////Extract tokens by keeping only the tensors with values grater
00586   ////then 10% of the max mag.
00587   //Image<float> mag = getTensorMag(tensorField);
00588 
00589   //float min, max;
00590   //getMinMax(mag, min,max);
00591 
00592   //for(uint i=0; i<mag.size(); i++)
00593   //  if (mag[i] < max*0.100)
00594   //    tensorField.setVal(i,0);
00595 
00596   itsTensorField = getStickVotes2(tensorField, itsSigma);
00597 
00598   if (performNonMaxSurp)
00599     nonMaxSurp(itsTensorField);
00600 
00601 
00602   return itsTensorField;
00603 
00604 }
00605 
00606 Image<float> TensorVoting::getTokensMag(bool normalize)
00607 {
00608   EigenSpace eigen = getTensorEigen(itsTensorField);
00609   Image<float> tokens = eigen.l1-eigen.l2;
00610 
00611   if (normalize)
00612     inplaceNormalize(tokens, 0.0F, 255.0F);
00613 
00614   return tokens;
00615 
00616 }
00617 
00618 
00619 // ######################################################################
00620 /* So things look consistent in everyone's emacs... */
00621 /* Local Variables: */
00622 /* indent-tabs-mode: nil */
00623 /* End: */
00624 
00625 #endif
00626 
Generated on Sun May 8 08:05:31 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3