naiveSnake.cpp

Go to the documentation of this file.
00001 
00015 #include <dlrComputerVision/getEuclideanDistance.h>
00016 #include <dlrComputerVision/naiveSnake.h>
00017 #include <dlrComputerVision/sobel.h>
00018 #include <dlrLinearAlgebra/linearAlgebra.h>
00019 #include <dlrNumeric/bilinearInterpolator.h>
00020 #include <dlrNumeric/utilities.h>
00021 
00022 namespace dlr {
00023 
00024   namespace computerVision {
00025     
00026     Snake::
00027     Snake()
00028       : m_alpha(0.1),
00029         m_beta(0.0),   // 10.0 is reasonable, but slows things down a lot!
00030         m_betaVector(),
00031         m_contourIterations(10),
00032         m_cornerAdditionThreshold(-2.0),
00033         m_cornerDeletionThreshold(2.0),
00034         m_externalForceGradientX(),
00035         m_externalForceGradientY(),
00036         m_forceBalanceMatrix(),
00037         m_gamma(1.0),
00038         m_isClosed(true),
00039         m_isConverged(false),
00040         m_isFixed(true),
00041         m_kappa(1.0),
00042         m_maxIterations(100),
00043         m_maxSnakeSize(500),
00044         m_maxSpanLength(3.0),
00045         m_minSpanLength(0.5),
00046         m_snake()
00047     {
00048       // Empty.
00049     }
00050 
00051 
00052     void
00053     Snake::
00054     enableCornerAdditionAndDeletion(bool enableFlag)
00055     {
00056       if(!enableFlag) {
00057         // Disable corner addition and deletion by setting thresholds
00058         // to out-of-bounds values.
00059         m_cornerAdditionThreshold = -2.0;
00060         m_cornerDeletionThreshold = 2.0;
00061       }
00062     }
00063 
00064     
00065     std::vector<Vector2D>
00066     Snake::
00067     run()
00068     {
00069       // Check state.
00070       if(m_externalForceGradientX.size() == 0) {
00071         DLR_THROW(StateException, "Snake::run()",
00072                   "Interest image has not been set.  Please call "
00073                   "setInterestImage() before calling run().");
00074       }
00075       
00076       // If snake has not been seeded, pick a reasonable default.
00077       if(m_snake.size() == 0) {
00078         size_t rows = m_externalForceGradientX.rows();
00079         size_t columns = m_externalForceGradientX.columns();
00080         m_snake.push_back(Vector2D(columns / 4.0, rows / 4.0));
00081         m_betaVector.push_back(m_beta);
00082         m_snake.push_back(Vector2D(columns / 4.0, 3 * rows / 4.0));
00083         m_betaVector.push_back(m_beta);
00084         m_snake.push_back(Vector2D(3 * columns / 4.0, 3 * rows / 4.0));
00085         m_betaVector.push_back(m_beta);
00086         m_snake.push_back(Vector2D(3 * columns / 4.0, rows / 4.0));
00087         m_betaVector.push_back(m_beta);
00088       }
00089 
00090       size_t iterationNumber = 0;
00091       while(!this->isConverged()) {
00092         if(iterationNumber >= m_maxIterations) {
00093           break;
00094         }
00095         this->runOneIteration();
00096         ++iterationNumber;
00097       }
00098       return m_snake;
00099     }
00100 
00101 
00102     std::vector<Vector2D>
00103     Snake::
00104     runOneIteration()
00105     {
00106       // No need to do anything if we've already converged.
00107       if(m_isConverged) {
00108         return m_snake;
00109       }
00110       
00111       // Check state.
00112       if(m_externalForceGradientX.size() == 0) {
00113         DLR_THROW(StateException, "Snake::runOneIteration()",
00114                   "Interest image has not been set.  Please call "
00115                   "setInterestImage() before calling runOneIteration().");
00116       }
00117 
00118       // If snake has not been seeded, pick a reasonable default.
00119       if(m_snake.size() == 0) {
00120         DLR_THROW(StateException, "Snake::runOneIteration()",
00121                   "Snake has not been initialized.  Please call "
00122                   "setSeedPoints() before calling runOneIteration().");
00123       }
00124     
00125       if(m_snake.size() < 4) {
00126         DLR_THROW(StateException, "Snake::runOneIteration()",
00127                   "Snake is too short.");
00128       }
00129 
00130       if(m_snake.size() > m_maxSnakeSize) {
00131         DLR_THROW(StateException, "Snake::runOneIteration()",
00132                   "Snake is too long.");
00133       }
00134 
00135       this->adjustBetas(m_snake, m_betaVector);
00136 
00137       std::pair< std::vector<Vector2D>, std::vector<double> >
00138         snake_betaVector = this->resampleSnake();
00139       m_snake = snake_betaVector.first;
00140       m_betaVector = snake_betaVector.second;
00141 
00142       for(size_t updateNumber = 0; updateNumber < m_contourIterations;
00143           ++updateNumber) {
00144         std::vector<Vector2D> oldSnake = m_snake;
00145         this->updateSnakePosition(m_snake);
00146         if(this->isConverged(m_snake, oldSnake)) {
00147           m_isConverged = true;
00148           break;
00149         }
00150       }
00151       return m_snake;
00152     }
00153 
00154 
00155     void
00156     Snake::
00157     setBendingConstant(double beta)
00158     {
00159       m_beta = beta;
00160       for(size_t index0 = 0; index0 < m_betaVector.size(); ++index0) {
00161         if(m_betaVector[index0] != 0.0) {
00162           m_betaVector[index0] = m_beta;
00163         }
00164       }
00165       m_isConverged = false;
00166     }
00167 
00168 
00169     void
00170     Snake::
00171     setCornerAdditionAngle(double theta)
00172     {
00173       if(theta < 0.0 || theta > 3.15) {
00174         DLR_THROW(ValueException, "Snake::setCornerAdditionAngle",
00175                   "Argument theta should be in the range [0.0, Pi].");
00176       }
00177       m_cornerAdditionThreshold = std::cos(theta);
00178     }
00179 
00180 
00181     void
00182     Snake::
00183     setCornerDeletionAngle(double theta)
00184     {
00185       if(theta < 0.0 || theta > 3.15) {
00186         DLR_THROW(ValueException, "Snake::setCornerDeletionAngle",
00187                   "Argument theta should be in the range [0.0, Pi].");
00188       }
00189       m_cornerDeletionThreshold = std::cos(theta);
00190     }
00191     
00192     
00193     void
00194     Snake::
00195     setInterestImage(const Image<GRAY1>& interestImage)
00196     {
00197       size_t numberOfPassesUsed;
00198       Array2D<double> distanceMatrix =
00199         getEuclideanDistance(interestImage, 10, numberOfPassesUsed);
00200       std::cout << "Euclidean distance calculation took " << numberOfPassesUsed
00201                 << " passes." << std::endl;
00202       m_externalForceGradientX =
00203         applySobelX(Image<GRAY_FLOAT64>(distanceMatrix)) / -8.0;
00204       m_externalForceGradientY =
00205         applySobelY(Image<GRAY_FLOAT64>(distanceMatrix)) / -8.0;
00206       m_isConverged = false;
00207     }
00208 
00209 
00210     void
00211     Snake::
00212     setSeedPoints(const std::vector<Vector2D>& seedPoints)
00213     {
00214       std::vector<bool> cornerFlags(seedPoints.size(), false);
00215       this->setSeedPoints(seedPoints, cornerFlags);
00216     }
00217 
00218 
00219     void
00220     Snake::
00221     setSeedPoints(const std::vector<Vector2D>& seedPoints,
00222                   const std::vector<bool> cornerFlags)
00223     {
00224       m_snake = seedPoints;
00225       m_betaVector = std::vector<double>(cornerFlags.size());
00226       for(size_t index0 = 0; index0 < m_betaVector.size(); ++index0) {
00227         if(cornerFlags[index0]) {
00228           m_betaVector[index0] = 0.0;
00229         } else {
00230           m_betaVector[index0] = m_beta;
00231         }
00232       }
00233       m_isConverged = false;
00234     }
00235 
00236     
00237     void
00238     Snake::
00239     addResampledSpan(std::vector<Vector2D>& snake,
00240                      std::vector<double>& betaVector,
00241                      const Vector2D& newPoint,
00242                      double newBeta,
00243                      bool isLast)
00244     {
00245       // This loop pops elements off the back of the snake until the
00246       // gap between the new point and the previous point is bigger
00247       // than m_minSpanLength.  If, during this popping loop, the
00248       // length of the snake reaches zero, then the new point is added
00249       // and we return.  If we encounter a corner (small beta) point
00250       // in the course of our popping, then leave the corner and
00251       // ignore newPoint instead.
00252       size_t snakeSize = snake.size();
00253       double spanLength = 0.0;
00254       while(1) {
00255         if(snakeSize == 0) {
00256           snake.push_back(newPoint);
00257           betaVector.push_back(newBeta);
00258           return;
00259         }
00260       
00261         // If the new point is sufficiently far from the previous point,
00262         // we can continue (leave the while loop).
00263         spanLength = magnitude(newPoint - snake[snakeSize - 1]);
00264         if(spanLength >= m_minSpanLength) {
00265           break;
00266         }
00267         
00268         // If we get here, then the new point is very close to the
00269         // previous point.  Delete the previous pont to make the space
00270         // is bigger.  We assume that smaller beta values are more
00271         // unusual and should be preserved, so we delete the more
00272         // unusual of newPoint and previousPoint.  Being the final
00273         // point of an open snake is most unusual of all.
00274         if((newBeta <= betaVector[betaVector.size() - 1])
00275            || (isLast && !m_isClosed)) {
00276           snake.pop_back();
00277           betaVector.pop_back();
00278           --snakeSize;
00279         } else {
00280           // looks like the previous point was more important than
00281           // the one were about to add.  Since the previous point
00282           // and the one we're adding are very close together, we
00283           // simply won't add the new point.
00284           return;
00285         }
00286       }
00287       
00288       // If the new point is very far from the previous point, add
00289       // some intermediate points to fill up the gap.
00290       if(spanLength >= m_maxSpanLength) {
00291         int numberToAdd = int(spanLength / m_maxSpanLength);
00292         Vector2D previousPoint = snake[snakeSize - 1];
00293         for(int extraPointIndex = 0; extraPointIndex < numberToAdd;
00294             ++extraPointIndex) {
00295           double fraction =
00296             double(extraPointIndex + 1) / double(numberToAdd + 1);
00297           double extraX = (fraction * newPoint.x()
00298                            + (1.0 - fraction) * previousPoint.x());
00299           double extraY = (fraction * newPoint.y()
00300                            + (1.0 - fraction) * previousPoint.y());
00301           snake.push_back(Vector2D(extraX, extraY));
00302           betaVector.push_back(m_beta);
00303         }
00304       }
00305 
00306       // Add the new point to the snake, unless this is the last
00307       // point of a closed snake (which would overlap the first
00308       // point).
00309       if(!(isLast && m_isClosed)) {
00310         snake.push_back(newPoint);
00311         betaVector.push_back(newBeta);
00312       }
00313     }
00314 
00315 
00316     void
00317     Snake::
00318     adjustBetas(const std::vector<Vector2D>& snake,
00319                 std::vector<double>& betaVector)
00320     {
00321       if((m_cornerAdditionThreshold < -1.0)
00322          && (m_cornerDeletionThreshold >= 1.0)) {
00323         return;
00324       }
00325 
00326       size_t startIndex = 0;
00327       size_t stopIndex = snake.size();
00328       if(!m_isClosed) {
00329         betaVector[0] = 0.0;
00330         betaVector[snake.size() - 1] = 0.0;
00331         startIndex = 1;
00332         stopIndex = snake.size() - 1;
00333       }
00334       
00335       for(size_t snakeIndex = 1; snakeIndex < snake.size() - 1; ++snakeIndex) {
00336         size_t previousIndex =
00337           (snakeIndex == 0) ? snake.size() - 1 : snakeIndex - 1;
00338         size_t nextIndex =
00339           (snakeIndex == snake.size() - 1) ? 0 : snakeIndex + 1;
00340           
00341         const Vector2D& currentPoint = snake[snakeIndex];
00342         const Vector2D& previousPoint = snake[previousIndex];
00343         const Vector2D& nextPoint = snake[nextIndex];
00344 
00345         Vector2D v0 = currentPoint - previousPoint;
00346         Vector2D v1 = nextPoint - currentPoint;
00347         v0 /= magnitude(v0);
00348         v1 /= magnitude(v1);
00349         double dotProduct = dot(v0, v1);
00350         if(dotProduct <= m_cornerAdditionThreshold) {
00351           betaVector[snakeIndex] = 0.0;
00352         } else if(dotProduct > m_cornerDeletionThreshold) {
00353           betaVector[snakeIndex] = m_beta;
00354         }
00355       }
00356     }
00357 
00358       
00359     Array2D<double>
00360     Snake::
00361     buildForceBalanceMatrix(size_t numberOfSnakePoints)
00362     {
00363       // For a traditional snake, we want to minimize the total
00364       // energy:
00365       // 
00366       //   E_tot = \alpha * E_stretch + \beta * E_bend + \kappa * E_ext
00367       // 
00368       // Where
00369       // 
00370       //   E_stretch = \sum_j((x_j - x_j-1)^2 + (y_j - y_j-1)^2)
00371       //   E_bend = \sum_j(((x_j+1 - x_j) - (x_j - x_j-1))^2
00372       //                  + ((y_j+1 - y_j) - (y_j - y_j-1))^2)
00373       //   E_ext = \sum_j(-1 * InterestArray[x_j, y_j])
00374       // 
00375       // To find the minimum, we do the usual trick of setting the
00376       // gradient to zero.
00377       // 
00378       //   For all j,
00379       //   0 = \alpha * \partial(E_stretch) / \partial(x_j)
00380       //       + \beta * \partial(E_bend) / \partial(x_j)
00381       //       + \kappa * \partial(E_external) / \partial(x_j)
00382       //   And similarly for y_j.
00383       //
00384       // Once after taking the derivative, this gives us
00385       //
00386       //   For all j,
00387       //   0 = \alpha * (4 * x_j - 2 * x_j-1 - 2 * x_j+1)
00388       //       + \beta * (12 * x_j - 8 * x_j-1 - 8 * x_j+1 + 2 * x_j-2
00389       //                  + 2 * x_j+2)
00390       //       - \kappa * \partial(InterestArray) / \partial(x_j)
00391       //
00392       // This is a linear equation, which we can solve directly for the
00393       // x_j (and y_j).  Of course, we still have to iterate because the
00394       // external forces vary spacially.  To stabiize the system, we add
00395       // a "viscosity" term... a force proportional to the change in x_j
00396       // & y_j at each iteration.
00397       //
00398       //   For all j,
00399       //   0 = \alpha * (4 * x_j - 2 * x_j-1 - 2 * x_j+1)
00400       //       + \beta * (12 * x_j - 8 * x_j-1 - 8 * x_j+1 + 2 * x_j-2
00401       //                  + 2 * x_j+2)
00402       //       - \kappa * \partial(InterestArray) / \partial(x_j)
00403       //       + \gamma * (x_j - prevousX_j)
00404       //
00405       //   (\gamma * previousX_j
00406       //    + \kappa * \partial(InterestArray) / \partial(x_j))
00407       //   = (\alpha * (4 * x_j - 2 * x_j-1 - 2 * x_j+1)
00408       //      + \beta * (12 * x_j - 8 * x_j-1 - 8 * x_j+1 + 2 * x_j-2
00409       //                 + 2 * x_j+2)
00410       //      + \gamma * x_j)
00411       //
00412       // For our implementation, we'd like to be able to have "corner"
00413       // points which bend freely, so we maintain a different beta for
00414       // each value of j.  In our implementation, the betas all have
00415       // the same value, except at corner points, which have beta set
00416       // to 0.0.  Redoing the above derivation with unique betas for
00417       // each value of j, and rearranging, we get:
00418       // 
00419       //   For all j,
00420       //   (\kappa * \partial(InterestArray) / \partial(x_j)
00421       //    + \gamma * previousX_j)
00422       //   = (x_j * (4 * \alpha + 8 * \beta_j 
00423       //             + 2 * \beta_j-1 + 2 * \beta_j+1 + \gamma)
00424       //      - x_j-1 * (2 * \alpha + 4 * \beta_j + 4 * \beta_j-1)
00425       //      - x_j+1 * (2 * \alpha + 4 * \beta_j + 4 * \beta_j+1)
00426       //      + x_j-2 * (2 * \beta_j-1) + x_j+2 * (2 * \beta_j+1))
00427       //      
00428       // We still have a special case to consider: what if we have a
00429       // non-closed snake with fixed endpoints?  In this case, \beta_0
00430       // and \beta_N-1 are set to 0.0, and the equations for j == 0
00431       // and j == N-1 are trivially changed to keep x_j and x_N-1
00432       // fixed.
00433       //
00434       //   For j == 0, j == N - 1,
00435       //   x_j = previousX_j
00436       //
00437       // For a closed snake, we simply imagine that the tail (j = N-1)
00438       // is connected to the head (j = 0), so that indices j == 0, j
00439       // == -1, j == -2, etc. are equivalent to j == N, j == N - 1, j
00440       // = N - 2, etc.
00441       // 
00442       // These equations, which we'll call the force balance equations,
00443       // are reflected in AMatrix and the vectors returned by
00444       // buildForceBalanceRHS().  We invert AMatrix because we want to
00445       // solve for x_j, y_j.
00446 
00447       // We use the single letter variable N because we'll be indexing
00448       // with it many times below.
00449       size_t N = numberOfSnakePoints;
00450       
00451       // Argh!  It hurts to make (& invert) such a big matrix, knowing
00452       // that it'll be mostly zeros.
00453       Array2D<double> AMatrix(N, N);
00454       AMatrix = 0.0;
00455     
00456       // Handle the special case of a non-closed, fixed endpoints snake.
00457       size_t loopStartRow = 0;
00458       size_t loopStopRow = N;
00459       if((!m_isClosed) && m_isFixed) {
00460         AMatrix(0, 0) = 1.0;
00461         AMatrix(N - 1, N - 1) = 1.0;
00462         loopStartRow = 1;
00463         loopStopRow = N - 1;
00464       }
00465 
00466       // Fill in the matrix.
00467       for(size_t rowIndex = loopStartRow; rowIndex < loopStopRow;
00468           ++rowIndex) {
00469         // Sort out the indices.
00470         size_t currentJ = rowIndex;
00471         size_t previousJ = (rowIndex == 0) ? (N - 1) : (rowIndex - 1);
00472         size_t prepreviousJ = (previousJ == 0) ? (N - 1) : (previousJ - 1);
00473         size_t nextJ = (rowIndex == N - 1) ? 0 : (rowIndex + 1);
00474         size_t nextnextJ = (nextJ == N - 1) ? 0 : (nextJ + 1);
00475 
00476         // We allow different betas for each node so that some nodes
00477         // can be stiff and others can be corners.
00478         double currentBeta = m_betaVector[currentJ];
00479         double previousBeta = m_betaVector[previousJ];
00480         double nextBeta = m_betaVector[nextJ];
00481 
00482         // Assign matrix elements as described in the comments above.
00483         AMatrix(currentJ, prepreviousJ) = 2.0 * previousBeta;
00484         AMatrix(currentJ, previousJ) = (
00485           -2.0 * m_alpha - 4.0 * currentBeta - 4.0 * previousBeta);
00486         AMatrix(currentJ, currentJ) = (
00487           4.0 * m_alpha + 8.0 * currentBeta + 2.0 * previousBeta
00488           + 2.0 * nextBeta + m_gamma);
00489         AMatrix(currentJ, nextJ) = (
00490           -2.0 * m_alpha - 4.0 * currentBeta - 4.0 * nextBeta);
00491         AMatrix(currentJ, nextnextJ) = 2.0 * nextBeta;
00492       }
00493 
00494       // Invert AMatrix and return.
00495       return inverse(AMatrix);
00496     }
00497 
00498 
00499     void
00500     Snake::
00501     buildForceBalanceRHS(const std::vector<Vector2D>& snake,
00502                          Array1D<double>& xRHS,
00503                          Array1D<double>& yRHS)
00504     {
00505       // Please read the comments in buildForceBalanceMatrix() in order
00506       // to understand this function.  For unimportant reasons, the
00507       // "Right Hand Side" quantities which we're building here show up
00508       // on the left hand side of the equations in those comments.
00509       if(xRHS.size() != snake.size()) {
00510         xRHS.reinit(snake.size());
00511       }
00512       if(yRHS.size() != snake.size()) {
00513         yRHS.reinit(snake.size());
00514       }
00515 
00516       // We use the single letter variable N because we'll be indexing
00517       // with it many times below.
00518       size_t N = snake.size();
00519       
00520       BilinearInterpolator<double> xInterp(m_externalForceGradientX);
00521       BilinearInterpolator<double> yInterp(m_externalForceGradientY);
00522       if((!m_isClosed) && m_isFixed) {
00523         xRHS[0] = snake[0].x();
00524         yRHS[0] = snake[0].y();
00525         xRHS[N - 1] = snake[N - 1].x();
00526         yRHS[N - 1] = snake[N - 1].y();
00527       } else {
00528         xRHS[0] = (m_gamma * snake[0].x()
00529                    + m_kappa * xInterp(snake[0].y(), snake[0].x()));
00530         yRHS[0] = (m_gamma * snake[0].y()
00531                    + m_kappa * yInterp(snake[0].y(), snake[0].x()));
00532         xRHS[N - 1] = (
00533           m_gamma * snake[N - 1].x()
00534           + m_kappa * xInterp(snake[N - 1].y(), snake[N - 1].x()));
00535         yRHS[N - 1] = (
00536           m_gamma * snake[N - 1].y()
00537           + m_kappa * yInterp(snake[N - 1].y(), snake[N - 1].x()));
00538       }
00539     
00540       for(size_t rowIndex = 1; rowIndex < N - 1; ++rowIndex) {
00541         const Vector2D& snakePoint = snake[rowIndex];
00542         xRHS[rowIndex] = (m_gamma * snakePoint.x()
00543                           + m_kappa * xInterp(snakePoint.y(), snakePoint.x()));
00544         yRHS[rowIndex] = (m_gamma * snakePoint.y()
00545                           + m_kappa * yInterp(snakePoint.y(), snakePoint.x()));
00546       }
00547     }
00548   
00549   
00550     bool
00551     Snake::
00552     isConverged(const std::vector<Vector2D>& snake,
00553                 const std::vector<Vector2D>& oldSnake)
00554     {
00555       bool convergedFlag = true;
00556       for(size_t pointNumber = 0; pointNumber < snake.size();
00557           ++pointNumber) {
00558         if(magnitude(snake[pointNumber] - oldSnake[pointNumber]) >= 0.5) {
00559           convergedFlag = false;
00560           break;
00561         }
00562       }
00563       return convergedFlag;
00564     }
00565   
00566     
00567     std::pair< std::vector<Vector2D>, std::vector<double> >
00568     Snake::
00569     resampleSnake()
00570     {
00571       std::vector<Vector2D> newSnake;
00572       std::vector<double> newBetaVector;
00573       
00574       size_t snakeIndex = 0;
00575       for(; snakeIndex < m_snake.size() - 1; ++snakeIndex) {
00576         this->addResampledSpan(newSnake, newBetaVector,
00577                                m_snake[snakeIndex], m_betaVector[snakeIndex]);
00578       }
00579 
00580       if(m_isClosed) {
00581         this->addResampledSpan(newSnake, newBetaVector,
00582                                m_snake[snakeIndex], m_betaVector[snakeIndex]);
00583         this->addResampledSpan(newSnake, newBetaVector,
00584                                m_snake[0], m_betaVector[0], true);
00585       } else {
00586         this->addResampledSpan(newSnake, newBetaVector, m_snake[snakeIndex],
00587                                m_betaVector[snakeIndex], true);
00588       }
00589 
00590       return std::make_pair(newSnake, newBetaVector);
00591     }
00592 
00593 
00594     void
00595     Snake::
00596     updateSnakePosition(std::vector<Vector2D>& snake)
00597     {
00598       if(snake.size() != m_forceBalanceMatrix.size()) {
00599         m_forceBalanceMatrix = this->buildForceBalanceMatrix(snake.size());
00600       }
00601       Array1D<double> xRHS;
00602       Array1D<double> yRHS;
00603       this->buildForceBalanceRHS(snake, xRHS, yRHS);
00604       Array1D<double> newX = matrixMultiply(m_forceBalanceMatrix, xRHS);
00605       Array1D<double> newY = matrixMultiply(m_forceBalanceMatrix, yRHS);
00606       for(size_t pointNum = 0; pointNum < snake.size(); ++pointNum) {
00607         snake[pointNum].setValue(newX[pointNum], newY[pointNum]);
00608       }
00609     }
00610 
00611   } // namespace computerVision
00612     
00613 } // namespace dlr

Generated on Mon Jul 9 20:34:03 2007 for dlrLibs Utility Libraries by  doxygen 1.5.2