contrib/gel/gevd/gevd_fold.cxx
Go to the documentation of this file.
00001 // This is gel/gevd/gevd_fold.cxx
00002 #include "gevd_fold.h"
00003 //:
00004 // \file
00005 // Use 8 directions, with 45 degree angle in between them.
00006 //
00007 //\endverbatim
00008 
00009 #include <vcl_vector.h>
00010 #include <vcl_iostream.h>
00011 #include <vnl/vnl_math.h>
00012 #include <gevd/gevd_noise.h>
00013 #include <gevd/gevd_float_operators.h>
00014 #include <gevd/gevd_pixel.h>
00015 #include <gevd/gevd_bufferxy.h>
00016 #ifdef DEBUG
00017 # include <vul/vul_timer.h>
00018 #endif
00019 
00020 gevd_bufferxy* gevd_fold::null_bufferxy = 0;
00021 
00022 const unsigned char TWOPI = 8, FULLPI = 4, HALFPI = 2;
00023 const int DIS[] = { 1, 1, 0,-1,-1,-1, 0, 1, // 8-connected neighbors
00024                     1, 1, 0,-1,-1,-1, 0, 1, // wrapped by 2PI to
00025                     1, 1, 0,-1,-1,-1, 0, 1};// avoid modulo operations.
00026 const int DJS[] = { 0, 1, 1, 1, 0,-1,-1,-1,
00027                     0, 1, 1, 1, 0,-1,-1,-1,
00028                     0, 1, 1, 1, 0,-1,-1,-1};
00029 const int RDS[] = {0,-1, 1,-2, 2,-3, 3,-4, 4,-5, 5}; // radial search
00030 
00031 // const unsigned char DIR0 = 8, DIR1 = 9, DIR2 = 10, DIR3 = 11;
00032 const int FRAME = 4; // 3 for NMS and extension, 4 for contour
00033 
00034 gevd_fold::gevd_fold(float smooth_sigma, // width of filter dG
00035                      float noise_sigma,   // sensor/texture intensity noise -[0 1]
00036                      float contour_factor, float junction_factor)
00037   : smoothSigma(smooth_sigma), noiseSigma(noise_sigma),
00038     contourFactor(contour_factor), junctionFactor(junction_factor),
00039     filterFactor(6)              // factor from gevd_float_operators::Hessian
00040 {
00041   if (smoothSigma < 0.5)        // no guarantee for 2-pixel separation
00042     vcl_cerr << "gevd_fold::gevd_fold -- too small smooth_sigma: "
00043              << smoothSigma << vcl_endl;
00044   if (smoothSigma > 2)          // smooth out too much the junctions
00045     vcl_cerr << "gevd_fold::gevd_fold -- too large smooth_sigma: "
00046              << smoothSigma << vcl_endl;
00047   if (noiseSigma < -1) {
00048     vcl_cerr << "gevd_fold::gevd_fold -- noiseSigma out of range -[0 1]: "
00049              << noiseSigma << ". Reset to -1.\n";
00050     noiseSigma = -1;
00051   }
00052 
00053   //vcl_cout << "Init Step\n" << *this << vcl_endl;
00054 }
00055 
00056 
00057 bool
00058 gevd_fold::DetectEdgels(const gevd_bufferxy& image,
00059                         gevd_bufferxy*& contour, gevd_bufferxy*& direction,
00060                         gevd_bufferxy*& locationx, gevd_bufferxy*& locationy,
00061                         bool peaks_only,
00062                         bool valleys_only,
00063                         bool transfer, //compute mag and angle? default=false
00064                         gevd_bufferxy*& mag, gevd_bufferxy*& angle)
00065 {
00066   //vcl_cout << "*** Detect step profiles with second-derivative of Gaussian"
00067   //         << *this
00068   //         << vcl_endl;
00069   if (image.GetBitsPixel() != bits_per_float) {
00070     vcl_cerr << "gevd_fold::DetectEdgels requires float image\n";
00071     return false;
00072   }
00073 
00074   // -tpk @@ missing check if the requested buffer size is too small to contain the convolution operations
00075 
00076   // 1. Smooth image to regularize data, before taking derivatives
00077   gevd_bufferxy* smooth = NULL;      // Gaussian smoothed image
00078   // use float to avoid overflow/truncation
00079   filterFactor = gevd_float_operators::Gaussian((gevd_bufferxy&)image, // well-condition before
00080                                                 smooth, smoothSigma); // 2nd-difference
00081 
00082   // 2. Use 2nd-difference to estimate local curvature, filter is ddG.
00083   gevd_bufferxy *curvature = NULL;
00084   // need to make new arrays since later NonMaximumSupression clears
00085   // locationx locationy
00086   gevd_bufferxy *dirx = gevd_float_operators::SimilarBuffer(image);
00087   gevd_bufferxy *diry = gevd_float_operators::SimilarBuffer(image);
00088   filterFactor *= gevd_float_operators::Hessian(*smooth, // 2nd-order difference
00089                                                 curvature, dirx, diry); // mult factor returned
00090   //If only peaks or valleys are asked for
00091   for (int j = 0; j < image.GetSizeY(); j++)
00092     for (int i = 0; i < image.GetSizeX(); i++)
00093       if ( (peaks_only   && floatPixel(*diry, i, j)<0) ||
00094            (valleys_only && floatPixel(*diry, i, j)>0)
00095          )
00096         floatPixel(*curvature, i, j) = 0;
00097 
00098   delete smooth;
00099 
00100   // 2.5 JLM - Fill the theta array for use in outputting continuous digital curve
00101   //           directions later.  The angle definition here is consistent with
00102   //           EdgeDetector, i.e. angle = (180/PI)*atan2(dI/dy, dI/dx);
00103   if (transfer) //Fill magnitude and angle arrays needed by EdgelChain const.
00104   {
00105     mag = gevd_float_operators::SimilarBuffer(image);
00106     angle = gevd_float_operators::SimilarBuffer(image);
00107     const float kdeg = float(vnl_math::deg_per_rad);
00108     for (int j = 0; j < image.GetSizeY(); j++)
00109       for (int i = 0; i < image.GetSizeX(); i++)
00110         if ((floatPixel(*mag, i, j) = floatPixel(*curvature, i, j)))
00111             floatPixel(*angle, i, j) = kdeg*vcl_atan2(floatPixel(*diry, i, j),
00112                                                       floatPixel(*dirx, i, j));
00113           else
00114             floatPixel(*angle, i, j) = 0;
00115   }
00116 
00117 
00118   // 3. Estimate sensor/texture sigmas from histogram of weak step edgels
00119   if (noiseSigma <= 0)  {
00120     int nedgel = 0;             // all edgels in ROI at center of image
00121     float* edgels = gevd_noise::EdgelsInCenteredROI(*curvature, *dirx, *diry, nedgel);
00122     if (edgels) {
00123       gevd_noise noise(edgels, nedgel); // histogram of weak edgels only
00124       delete [] edgels;
00125       float sensorNoise, textureNoise;
00126       if (noise.EstimateSensorTexture(sensorNoise, textureNoise)) {
00127         const float k = -noiseSigma; // given linear interpolation factor
00128         noiseSigma = ((1-k)*sensorNoise + k*textureNoise) /
00129           NoiseResponseToFilter(1, smoothSigma, filterFactor);
00130       }
00131       else {
00132         vcl_cout << "Can not estimate sensor & texture noise\n";
00133         noiseSigma = 1;         // reasonable default for 8-bit
00134       }
00135     }
00136     else {
00137       vcl_cout << "Not enough edge elements to estimate noise\n";
00138       noiseSigma = 1;
00139     }
00140     //vcl_cout << "Set noise sigma = " << noiseSigma << vcl_endl;
00141   }
00142 
00143   // 4. Find contour pixels as local maxima along slope direction
00144   //
00145   //                  [i,j+1]
00146   //                    ^
00147   //                    |
00148   // Note that [i-1 ,j] -> [i+1,j] indicates the sign of dirx and diry.
00149   //                    |          That is, if the intensities at the arrows
00150   //                 [i, j-1]      are larger than that at (i,j) then dirx or
00151   //                               diry are positive.  Again shown for a
00152   //                               right-handed (i,j) system -- JLM
00153   //
00154   // Thus for the following contour:
00155   //          ^ j            Normal Direction
00156   //          |________            ^
00157   //    light |        |           |    dirx = 0 and diry = +.  The direction
00158   //        __|________|__Contour__|    code returned by NonMaximumSupression
00159   //          |xxxxxxxx|                is 10 -- modulo 8 is 2, as shown.
00160   //    dark  |xxxxxxxx|
00161   //           ----------> i
00162   //
00163   // -----------------------------------------------------------------
00164 
00165   gevd_float_operators::NonMaximumSuppression(*curvature, *dirx, *diry,
00166                                               NoiseThreshold(), // above noise
00167                                               contour, direction,
00168                                               locationx, locationy);
00169   delete curvature; delete dirx; delete diry;
00170   gevd_float_operators::FillFrameX(*contour, 0, FRAME); // erase pixels in frame border
00171   gevd_float_operators::FillFrameY(*contour, 0, FRAME);
00172   return true;
00173 }
00174 
00175 //:
00176 // Return -/+ PI/2, to encode the existence of an end point
00177 // on the left/right side of the current contour point i,j.
00178 static int
00179 LeftXorRightEnd(const gevd_bufferxy& contour,
00180                 int i, int j, // pixel on contour
00181                 int dir) // normal to contour
00182 {
00183   int di = DIS[dir], dj = DJS[dir];
00184   bool normalp = (floatPixel(contour, i - di, j - dj) ||
00185                   floatPixel(contour, i + di, j + dj));
00186   if (normalp)                  // Substitute neighbor
00187     return 0;                   // for left or right side.
00188   bool leftp = false;
00189   int ndir = dir - HALFPI;      // left ndir
00190   for (int n = 0; n < 3; ++n) { // 3 neighbors
00191     int theta = ndir + RDS[n];
00192     if (floatPixel(contour, i+DIS[theta], j+DJS[theta])) {
00193       leftp = true;             // found neighbor on left side
00194       break;
00195     }
00196   }
00197   bool rightp = false;
00198   ndir = dir + HALFPI;          // right ndir
00199   for (int n = 0; n < 3; ++n) { // 3 neighbors
00200     int theta = ndir + RDS[n];
00201     if (floatPixel(contour, i+DIS[theta], j+DJS[theta])) {
00202     rightp = true;            // found neighbor on right side
00203       break;
00204     }
00205   }
00206   return (leftp? 0: -HALFPI) + (rightp? 0: HALFPI); // increment from dir
00207 }
00208 
00209 //: Find best fold extension from end point, which has largest local maximum curvature.
00210 // Search all 3x3=9 neighboring locations/directions.
00211 // Return location, direction and strength of this extension pixel.
00212 //
00213 static float
00214 BestFoldExtension(const gevd_bufferxy& smooth,
00215                   int i, int j,
00216                   int ndir, // tangential dir to neighbor
00217                   float threshold,
00218                   int& best_i, int& best_j, // pixel
00219                   int& best_d, float& best_l) // direction + subloc
00220 {
00221   float best_s = threshold;     // insure greater response
00222   const int direc = ndir + HALFPI; // fold direction
00223   for (int n = 0; n < 3; n++) { // all 3 neighboring pixels
00224     int ntheta = ndir + RDS[n];
00225     int ni = i + DIS[ntheta];
00226     int nj = j + DJS[ntheta];
00227     float pix = floatPixel(smooth, ni, nj); // center
00228     for (int d = 0; d < 3; d++) { // all 3 neighboring directions
00229       int dir = direc + RDS[d];
00230       int di = DIS[dir];
00231       int dj = DJS[dir];
00232       float pix_m = floatPixel(smooth, ni-di, nj-dj);
00233       float pix_p = floatPixel(smooth, ni+di, nj+dj);
00234       float curvature = (float)vcl_fabs(pix_p + pix_m - 2*pix);
00235       float max_s = (dir%HALFPI)? best_s*2: best_s;
00236       if (curvature > max_s) {      // find best strength
00237         int di2 = 2*di;
00238         int dj2 = 2*dj;
00239         if (curvature > vcl_fabs(pix + floatPixel(smooth, ni-di2, nj-dj2)
00240                                  - 2 * pix_m) &&
00241             curvature > vcl_fabs(pix + floatPixel(smooth, ni+di2, nj+dj2)
00242                                  - 2 * pix_p)) {
00243           best_i = ni;
00244           best_j = nj;
00245           best_s = (dir%HALFPI)? curvature/2 : curvature;
00246           best_d = dir%FULLPI + TWOPI; // in range [0 FULLPI) + TWOPI
00247         }
00248       }
00249     }
00250   }
00251   if (best_s > threshold) {     // interpolate with parabola
00252     float pix = floatPixel(smooth, best_i, best_j);
00253     int di = DIS[best_d], dj = DJS[best_d];
00254     int di2 = 2*di, dj2 = 2*dj;
00255     float s_m = (float)vcl_fabs(pix + floatPixel(smooth, best_i-di2, best_j-dj2)
00256                                 - 2*floatPixel(smooth, best_i-di, best_j-dj));
00257     float s_p = (float)vcl_fabs(pix + floatPixel(smooth, best_i+di2, best_j+dj2)
00258                                 - 2*floatPixel(smooth, best_i+di, best_j+dj));
00259     if (best_d%HALFPI) {
00260       s_m /= (float)2.0;
00261       s_p /= (float)2.0;
00262     }
00263     best_l = gevd_float_operators::InterpolateParabola(s_m, best_s, s_p, best_s);
00264     return best_s;
00265   }
00266   else                        // not found
00267     return 0;
00268 }
00269 
00270 
00271 int
00272 gevd_fold::RecoverJunctions(const gevd_bufferxy& image,
00273                             gevd_bufferxy& contour, gevd_bufferxy& direction,
00274                             gevd_bufferxy& locationx, gevd_bufferxy& locationy,
00275                             int*& junctionx, int*& junctiony)
00276 {
00277 #if defined(DEBUG)
00278   vul_timer t;
00279 #endif
00280   if (image.GetBitsPixel() != bits_per_float) {
00281     vcl_cerr << "gevd_fold::RecoverJunction requires float image\n";
00282     return false;
00283   }
00284   const int rmax = 1+FRAME;     // 1 + kernel radius of BestStepExtension
00285   const int kmax = int(4 * smoothSigma + 0.5) + 2; // gap = 2
00286   const int xmax = image.GetSizeX()-rmax-1; // fill fold direction
00287   const int ymax = image.GetSizeY()-rmax-1;
00288 #ifdef DEBUG
00289   vcl_cout << "RecoverJunctions: rmax, kmax, xmax, ymax:" << rmax << ' ' << kmax << ' ' << xmax << ' ' << ymax << '\n';
00290 #endif
00291   // 1. Find end points of dangling contours
00292   //const int length0 = xmax/kmax*ymax/kmax/4;// 25% size
00293   //const float growth = 2;     // growth ratio of the arrays
00294 
00295   vcl_vector<int> ndir; //  ndir.set_growth_ratio(growth);
00296   vcl_vector<int> xloc; //  xloc.set_growth_ratio(growth); // dynamic array instead of long lists
00297   vcl_vector<int> yloc; //  yloc.set_growth_ratio(growth);
00298   int xdir;
00299   for (int y = rmax; y <= ymax; y++) // find end points of long contours
00300     for (int x = rmax; x <= xmax; x++) // inside image border - rmax
00301       if (floatPixel(contour, x, y) && // on contour
00302           (xdir = LeftXorRightEnd(contour, x, y, // left xor right neighbor
00303                                   bytePixel(direction, x, y))) != 0)
00304       {
00305         ndir.push_back(xdir);   // save end point of elongated contours
00306         xloc.push_back(x);
00307         yloc.push_back(y);
00308       }
00309   const int length = ndir.size();
00310   //vcl_cout << "% end pats = "     // trace allocated size
00311   //          << length*100 / float((xmax/kmax)*(ymax/kmax)) << vcl_endl;
00312 
00313   // 2. Extend from end points until they touch other contours
00314   gevd_bufferxy* smooth = NULL;
00315   gevd_float_operators::Gaussian((gevd_bufferxy&)image, smooth, smoothSigma/2); // avoid oversmoothing
00316   const bool shortp = true;     // short contours
00317   const float threshold = NoiseThreshold(shortp);
00318   float curvature, loc;
00319   int njunction = 0;            // number of junctions found
00320   for (int r = 1; r <= kmax; r++) { // breadth-first extension
00321     int ntouch = 0, nextension = 0;
00322     for (int i = 0; i < length; i++)
00323       if ((xdir = ndir[i]) != 0 && xdir != TWOPI) // still extension?
00324       {
00325         int x = xloc[i], y = yloc[i];
00326         int dir = bytePixel(direction, x, y); // direction of fold
00327         curvature = BestFoldExtension(*smooth,
00328                                       x, y, dir + xdir,     // current end pt
00329                                       threshold,
00330                                       x, y, dir, loc);
00331         if (curvature) {                 // next end point
00332           xloc[i] = x, yloc[i] = y; // update new end point
00333           xdir = LeftXorRightEnd(contour,    // mark completed if
00334                                  x, y, dir); // another contour is reached,
00335                                              // indicated by xdir==0.
00336           if (xdir)
00337           {
00338             if (x < rmax || x > xmax || // check for reaching border
00339                 y < rmax || y > ymax)
00340               xdir = 0;               // junction with virtual border
00341             else
00342               nextension++;
00343             floatPixel(contour, x, y) = curvature; // still disconnected
00344           }
00345           else
00346           {           // touching another contour
00347             ntouch++;
00348             xdir = TWOPI;     // mark junction, without linking chains
00349           }
00350           ndir[i] = xdir;
00351           bytePixel(direction, x, y) = (unsigned char)(dir);
00352           floatPixel(locationx, x, y) = loc*DIS[dir];
00353           floatPixel(locationy, x, y) = loc*DJS[dir];
00354         }
00355         else                  // no further extension found
00356           ndir[i] = 0;
00357       }
00358     //vcl_cout << "Touch " << ntouch << " contours.\n";
00359     // vcl_cout << "Will extend " << nextension << " contours.\n";
00360     njunction += ntouch;
00361     if (!nextension) break;     // all either junction or termination
00362   }
00363   delete smooth;
00364 
00365   // 3. Return the end points or junctions found
00366   if (junctionx) delete [] junctionx;
00367   if (junctiony) delete [] junctiony;
00368   junctionx = new int[njunction];
00369   junctiony = new int[njunction];
00370   for (int i = 0, j = 0; i < length; i++)
00371     if (ndir[i] == TWOPI) {
00372       junctionx[j] = xloc[i];
00373       junctiony[j] = yloc[i];
00374       j++;
00375     }
00376 #if defined(DEBUG)
00377   vcl_cout << "Find " << length << " end points, and "
00378            << njunction << " junctions.\n"
00379            << "Recover " << 100.0*njunction/length
00380            << "% end points as junctions > "
00381            << threshold << ", in " << t.real() << " msecs.\n";
00382 #endif
00383   return njunction;
00384 }
00385 
00386 
00387 float
00388 gevd_fold::NoiseSigma() const
00389 {
00390   return (noiseSigma <= 0)? 0: noiseSigma;
00391 }
00392 
00393 
00394 float
00395 gevd_fold::NoiseResponse() const
00396 {
00397   return NoiseResponseToFilter(noiseSigma,
00398                                smoothSigma, filterFactor);
00399 }
00400 
00401 
00402 float
00403 gevd_fold::NoiseThreshold(bool shortp) const
00404 {
00405   float factor = (shortp? junctionFactor : contourFactor);
00406   float smooth = (shortp? smoothSigma/2 : smoothSigma);
00407   return factor * 3 *          // 3*sigma for 99% removal confidence
00408          NoiseResponseToFilter(noiseSigma, smooth, filterFactor);
00409 }
00410 
00411 
00412 float
00413 gevd_fold::NoiseResponseToFilter(float noiseSigma,
00414                                  float smoothSigma,
00415                                  float filterFactor)
00416 {
00417   return noiseSigma /          // white noise
00418          (float)vcl_pow((double)smoothSigma, 2.5) * // size of filter ddG
00419          ((float)vcl_sqrt(0.1875 * vnl_math::two_over_sqrtpi)) *
00420          filterFactor;        // factor in Hessian image
00421 }
00422 
00423 
00424 //: Output a snapshot of current control parameters
00425 vcl_ostream& operator<< (vcl_ostream& os, const gevd_fold& st)
00426 {
00427   os << "Fold:\n"
00428      << "   smoothSigma " << st.smoothSigma << vcl_endl
00429      << "   noiseSigma " << st.noiseSigma << vcl_endl
00430      << "   contourFactor " << st.contourFactor << vcl_endl
00431      << "   junctionFactor " << st.junctionFactor << vcl_endl
00432      << "   filterFactor " << st.filterFactor << vcl_endl;
00433     return os;
00434 }
00435 
00436 
00437 //: Output a snapshot of current control parameters
00438 vcl_ostream& operator<< (vcl_ostream& os, gevd_fold& st)
00439 {
00440   os << "Fold:\n"
00441      << "   smoothSigma " << st.smoothSigma << vcl_endl
00442      << "   noiseSigma " << st.noiseSigma << vcl_endl
00443      << "   contourFactor " << st.contourFactor << vcl_endl
00444      << "   junctionFactor " << st.junctionFactor << vcl_endl
00445      << "   filterFactor " << st.filterFactor << vcl_endl;
00446     return os;
00447 }