contrib/gel/gevd/gevd_step.cxx
Go to the documentation of this file.
00001 // This is gel/gevd/gevd_step.cxx
00002 #include "gevd_step.h"
00003 //:
00004 // \file
00005 // Use 8 directions, with 45 degree angle in between them.
00006 // The array DIS gives the i-component of the directions
00007 // The array DJS gives the j-component of the directions.
00008 // Shown here for a right-handed coordinate system for (i,j)
00009 // in accordance with standard texts. -- JLM
00010 //\verbatim
00011 //               j
00012 //         ______|_____________
00013 //        |      |      |      |
00014 //        |  3   |  2   |  1 <------ Direction Code
00015 //        |______|______|______|     for 8-connected
00016 //        |      |      |      |     neighbors
00017 //        |  4   |(i,j) |  0   |
00018 //        |______|______|______|___ i
00019 //        |      |      |      |
00020 //        |  5   |  6   |  7   |
00021 //        |______|______|______|
00022 //
00023 //\endverbatim
00024 
00025 #include <vcl_vector.h>
00026 #include <vcl_iostream.h>
00027 #include <vnl/vnl_math.h>
00028 #include <gevd/gevd_noise.h>
00029 #include <gevd/gevd_float_operators.h>
00030 #include <gevd/gevd_pixel.h>
00031 #include <gevd/gevd_bufferxy.h>
00032 #ifdef DEBUG
00033 # include <vul/vul_timer.h>
00034 #endif
00035 
00036 const unsigned char TWOPI = 8, FULLPI = 4, HALFPI = 2;
00037 const int DIS[] = { 1, 1, 0,-1,-1,-1, 0, 1, // 8-connected neighbors
00038                     1, 1, 0,-1,-1,-1, 0, 1, // wrapped by 2PI to
00039                     1, 1, 0,-1,-1,-1, 0, 1};// avoid modulo operations.
00040 const int DJS[] = { 0, 1, 1, 1, 0,-1,-1,-1,
00041                     0, 1, 1, 1, 0,-1,-1,-1,
00042                     0, 1, 1, 1, 0,-1,-1,-1};
00043 const int RDS[] = {0,-1, 1,-2, 2,-3, 3,-4, 4,-5, 5}; // radial search
00044 
00045 // const unsigned char DIR0 = 8, DIR1 = 9, DIR2 = 10, DIR3 = 11;
00046 const int FRAME = 4; // 3 for NMS and extension, 4 for contour
00047 
00048 //: Save parameters and create workspace for detecting step profiles.
00049 // High frequency features are smoothed away by smooth_sigma.
00050 // Texture or white noise less than noise_sigma are not detected.
00051 // smooth_sigma = 0.5-2.0, 1.0 insures separation of independent steps >= 2.
00052 //
00053 // If noise_sigma > 0, noise_sigma = is interpreted as the standard deviation
00054 //    of intensity z(x,y) in a uniform region.
00055 // If -1 <= noise_sigma <= 0, then noise_sigma is interpreted as a factor -k,
00056 // where k is a weighting factor between two estimates: sensorNoise and textureNoise.
00057 //
00058 //  noiseSigma = ((1-k)*sensorNoise + k*textureNoise)/K
00059 //
00060 // The values of sensorNoise and textureNoise are computed from the shape of
00061 // the histogram of gradient magnitude values, in the neighborhood of low gradients.
00062 // The value of K is computed in NoiseResponseToFilter and defined by:
00063 //
00064 //  1/(smoothSigma^1.5)*(.5/M_PI^.25)*filterFactor.
00065 //
00066 // Given the defaults:  smoothSigma =1 and filterFactor = 2, K = .75
00067 //
00068 // The threshold used to delete weak edges is modified depending on the
00069 // context.  For edgels along a contour, shortp = false, and the threshold
00070 // is given by:
00071 //
00072 //    NoiseThreshold(shortp) = 3*K*noiseSigma
00073 //
00074 // For edgels at junctions, shortp = true, the threshold is computed
00075 // using different factors.  The difference is controlled by the parameters:
00076 // contour_factor and junction_factor.  smoothSigma when extending the
00077 // boundary at junctions is 1/2 the value along contours.
00078 //
00079 gevd_step::gevd_step(float smooth_sigma, // width of filter dG
00080                      float noise_sigma,   // sensor/texture intensity noise -[0 1]
00081                      float contour_factor, float junction_factor)
00082   : smoothSigma(smooth_sigma), noiseSigma(noise_sigma),
00083     contourFactor(contour_factor), junctionFactor(junction_factor),
00084     filterFactor(2)              // factor from gevd_float_operators::Gradient
00085 {
00086   if (smoothSigma < 0.5)        // no guarantee for 2-pixel separation
00087     vcl_cerr << "gevd_step::gevd_step -- too small smooth_sigma: "
00088              << smoothSigma << vcl_endl;
00089   if (smoothSigma > 3)          // smooth out too much the junctions
00090     vcl_cerr << "gevd_step::gevd_step -- too large smooth_sigma: "
00091              << smoothSigma << vcl_endl;
00092   if (noiseSigma < -1) {
00093     vcl_cerr << "gevd_step::gevd_step -- noiseSigma out of range -[0 1]: "
00094              << noiseSigma << ". Reset to -1.\n";
00095     noiseSigma = -1;
00096   }
00097 
00098   //vcl_cout << "Init Step\n" << *this << vcl_endl;
00099 }
00100 
00101 
00102 //: Free space allocated for detecting step profiles.  Does nothing.
00103 gevd_step::~gevd_step() {}
00104 
00105 //: Detect step profiles with Canny edge detector.
00106 // The image is convolved with a Gaussian to smooth away
00107 // high frequency noise, and insure separation of step responses.
00108 // Then local gradient magnitude and direction is detected
00109 // using first difference [-1 0 +1].
00110 // Optionally estimate sensor/texture sigma and set threshold.
00111 // Finally, non maximum suppression is done to find strict
00112 // local maxima of slope.
00113 // The Canny edge detector finds elongated contours only.
00114 // These contours are typically broken at junctions because non
00115 // maximum suppression is done along only the strongest direction.
00116 // Return contour (float), direction (byte), location (float) images.
00117 // Return true if no exception.
00118 // J. Canny, A Computational Approach to Edge Detection,
00119 // IEEE Trans on PAMI, vol 8, no 6, Nov 1986.
00120 bool
00121 gevd_step::DetectEdgels(const gevd_bufferxy& image,
00122                         gevd_bufferxy*& contour, gevd_bufferxy*& direction,
00123                         gevd_bufferxy*& locationx, gevd_bufferxy*& locationy,
00124                         gevd_bufferxy*& grad_mag, gevd_bufferxy*& angle)
00125 {
00126   //vcl_cout << "*** Detect step profiles with first-derivative of Gaussian"
00127   //         << *this
00128   //         << vcl_endl;
00129   if (image.GetBitsPixel() != bits_per_float) {
00130     vcl_cerr << "gevd_step::DetectEdgels requires float image\n";
00131     return false;
00132   }
00133 
00134   // -tpk @@ missing check if the requested buffer size is too small to contain the convolution operations
00135 
00136   // 1. Smooth image to regularize data, before taking derivatives
00137   gevd_bufferxy* smooth = NULL;      // Gaussian smoothed image
00138   // use float to avoid overflow/truncation
00139   filterFactor = gevd_float_operators::Gaussian((gevd_bufferxy&)image, // well-condition before
00140                                                 smooth, smoothSigma); // 1st-difference
00141 
00142   // 2. Use 1st-difference to estimate local slope, filter is dG.
00143   gevd_bufferxy *slope = NULL, *dirx=NULL, *diry=NULL;
00144   filterFactor *= gevd_float_operators::Gradient(*smooth, // directional 1st-difference
00145                                                  slope, dirx, diry); // mult factor returned
00146   delete smooth;
00147 
00148   // 2.5 JLM - Fill the theta array for use in outputting continuous digital curve
00149   //           directions later.  The angle definition here is consistent with
00150   //           EdgeDetector, i.e. angle = (180/M_PI)*vcl_atan2(dI/dy, dI/dx);
00151 
00152   grad_mag = gevd_float_operators::SimilarBuffer(image);
00153   angle = gevd_float_operators::SimilarBuffer(image);
00154   const double kdeg = vnl_math::deg_per_rad;
00155   for (int j = 0; j < image.GetSizeY(); j++)
00156     for (int i = 0; i < image.GetSizeX(); i++)
00157       if ((floatPixel(*grad_mag, i, j) = floatPixel(*slope, i, j)))
00158         floatPixel(*angle, i, j) = float(kdeg*vcl_atan2(floatPixel(*diry, i, j),
00159                                                         floatPixel(*dirx, i, j)));
00160       else
00161         floatPixel(*angle, i, j) = 0;
00162 
00163 
00164   // 3. Estimate sensor/texture sigmas from histogram of weak step edgels
00165   if (noiseSigma <= 0)  {
00166     int nedgel = 0;             // all edgels in ROI at center of image
00167     float* edgels = gevd_noise::EdgelsInCenteredROI(*slope, *dirx, *diry,
00168                                                     nedgel);
00169     if ((edgels) && (nedgel > 0 )) {
00170       gevd_noise noise(edgels, nedgel); // histogram of weak edgels only
00171       delete [] edgels;
00172       float sensorNoise, textureNoise;
00173       if (noise.EstimateSensorTexture(sensorNoise, textureNoise)) {
00174         const float k = -noiseSigma; // given linear interpolation factor
00175         noiseSigma = ((1-k)*sensorNoise + k*textureNoise) /
00176           NoiseResponseToFilter(1, smoothSigma, filterFactor);
00177       }
00178       else {
00179         vcl_cout << "Can not estimate sensor & texture noise\n";
00180         noiseSigma = 1;         // reasonable default for 8-bit
00181       }
00182     }
00183     else {
00184       vcl_cout << "Not enough edge elements to estimate noise\n";
00185       noiseSigma = 1;
00186     }
00187     //vcl_cout << "Set noise sigma = " << noiseSigma << vcl_endl;
00188   }
00189 
00190   // 4. Find contour pixels as local maxima along slope direction
00191   //
00192   //                  [i,j+1]
00193   //                    ^
00194   //                    |
00195   // Note that [i-1 ,j] -> [i+1,j] indicates the sign of dirx and diry.
00196   //                    |          That is, if the intensities at the arrows
00197   //                 [i, j-1]      are larger than that at (i,j) then dirx or
00198   //                               diry are positive.  Again shown for a
00199   //                               right-handed (i,j) system -- JLM
00200   //
00201   // Thus for the following contour:
00202   //          ^ j            Normal Direction
00203   //          |________            ^
00204   //    light |        |           |    dirx = 0 and diry = +.  The direction
00205   //        __|________|__Contour__|    code returned by NonMaximumSupression
00206   //          |xxxxxxxx|                is 10 -- modulo 8 is 2, as shown.
00207   //    dark  |xxxxxxxx|
00208   //           ----------> i
00209   //
00210   // -----------------------------------------------------------------
00211 
00212   gevd_float_operators::NonMaximumSuppression(*slope, *dirx, *diry,
00213                                               NoiseThreshold(), // above noise
00214                                               contour, direction,
00215                                               locationx, locationy);
00216   delete slope; delete dirx; delete diry;
00217   gevd_float_operators::FillFrameX(*contour, 0, FRAME); // erase pixels in frame border
00218   gevd_float_operators::FillFrameY(*contour, 0, FRAME);
00219   return true;
00220 }
00221 
00222 //:
00223 // Return -/+ PI/2, to encode the existence of an end point
00224 // on the left/right side of the current contour point i,j.
00225 // Note, in vil1, images have a left-handed (i,j) coordinate frame,
00226 // i.e., i increases left to right and j increases downward in the image.
00227 //
00228 // The search proceeds as follows:
00229 //
00230 // 1)
00231 //\verbatim
00232 //        X
00233 //    --(i,j)--
00234 //        X    |
00235 //             v  Normal to contour (dir)
00236 //\endverbatim
00237 // if either X is occupied then it must be a corner -- so do nothing.
00238 //
00239 // 2) Next, search along the contour to the left or right
00240 //\verbatim
00241 //      L         R
00242 //      L--(i,j)--R
00243 //      L    |    R
00244 //           v Normal to contour (dir)
00245 //
00246 //    left       right      if any R position is occupied but not any L
00247 //  neighbor    neighbor    return -2.
00248 //                          if any L position is occupied but not any R
00249 //                          return +2.  Otherwise return 0.
00250 //\endverbatim
00251 //  Thus, the direction code along the contour at the end of the contour
00252 //  is returned.
00253 static int
00254 LeftXorRightEnd(const gevd_bufferxy& contour,
00255                 int i, int j, // pixel on contour
00256                 const int dir) // normal to contour
00257 {
00258   int di = DIS[dir], dj = DJS[dir];
00259   bool normalp = (floatPixel(contour, i - di, j - dj) ||
00260                   floatPixel(contour, i + di, j + dj));
00261   if (normalp)                  // Substitute neighbor
00262     return 0;                   // for left or right side.
00263   bool leftp = false;
00264   int ndir = dir - HALFPI;      // left ndir
00265   for (int n = 0; n < 3; ++n) { // 3 neighbors
00266     int theta = ndir + RDS[n];
00267     if (floatPixel(contour, i+DIS[theta], j+DJS[theta])) {
00268       leftp = true;             // found neighbor on left side
00269       break;
00270     }
00271   }
00272   bool rightp = false;
00273   ndir = dir + HALFPI;          // right ndir
00274   for (int n = 0; n < 3; ++n) { // 3 neighbors
00275     int theta = ndir + RDS[n];
00276     if (floatPixel(contour, i+DIS[theta], j+DJS[theta])) {
00277     rightp = true;            // found neighbor on right side
00278       break;
00279     }
00280   }
00281   return (leftp? 0: -HALFPI) + (rightp? 0: HALFPI); // increment from dir
00282 }
00283 
00284 //: Find best step extension from end point, which has largest local maximum slope.
00285 // Search all 3x3=9 neighboring locations/orientations
00286 // in the direction of the contour.  If the best gradient along the contour
00287 // extension is below the threshold, then return 0, otherwise
00288 // return the location, direction and strength of the extension pixel.
00289 //
00290 static float
00291 BestStepExtension(const gevd_bufferxy& smooth,
00292                   const int i, const int j,
00293                   const int ndir, // tangential dir to neighbor
00294                   const float threshold,
00295                   int& best_i, int& best_j, // pixel
00296                   int& best_d, float& best_l) // direction + subloc
00297 {
00298   float best_s = threshold;     // insure greater response
00299   const int direc = ndir + HALFPI; // step direction
00300   for (int n = 0; n < 3; n++) { // all 3 neighboring pixels
00301     int ntheta = ndir + RDS[n];
00302     int ni = i + DIS[ntheta];
00303     int nj = j + DJS[ntheta];
00304     float pix = floatPixel(smooth, ni, nj); // center
00305     for (int d = 0; d < 3; d++) { // all 3 neighboring directions
00306       int dir = direc + RDS[d];
00307       int di = DIS[dir];
00308       int dj = DJS[dir];
00309       float pix_m = floatPixel(smooth, ni-di, nj-dj);
00310       float pix_p = floatPixel(smooth, ni+di, nj+dj);
00311       float slope = (float)vcl_fabs(pix_p - pix_m);
00312       float max_s = (dir%HALFPI)? best_s*(float)vcl_sqrt(2.0): best_s;
00313       if (slope > max_s) {      // find best strength
00314         int di2 = 2*di;
00315         int dj2 = 2*dj;
00316         if (slope > vcl_fabs(pix - floatPixel(smooth, ni-di2, nj-dj2)) &&
00317             slope > vcl_fabs(pix - floatPixel(smooth, ni+di2, nj+dj2))) {
00318           best_i = ni;
00319           best_j = nj;
00320           best_s = (dir%HALFPI)? slope/(float)vcl_sqrt(2.0) : slope;
00321           best_d = dir%FULLPI + TWOPI; // in range [0 FULLPI) + TWOPI
00322         }
00323       }
00324     }
00325   }
00326   if (best_s > threshold) {     // interpolate with parabola
00327     float pix = floatPixel(smooth, best_i, best_j);
00328     int di2 = 2 * DIS[best_d], dj2 = 2 * DJS[best_d];
00329     float s_m = (float)vcl_fabs(pix - floatPixel(smooth, best_i-di2, best_j-dj2));
00330     float s_p = (float)vcl_fabs(pix - floatPixel(smooth, best_i+di2, best_j+dj2));
00331     if (best_d%HALFPI) {
00332       s_m /= (float)vcl_sqrt(2.0);
00333       s_p /= (float)vcl_sqrt(2.0);
00334     }
00335     best_l = gevd_float_operators::InterpolateParabola(s_m, best_s, s_p, best_s);
00336     return best_s;
00337   }
00338   else                        // not found
00339     return 0;
00340 }
00341 
00342 
00343 //:
00344 // Find junctions by searching for extensions of contours from
00345 // their dangling end points. Non maximum suppression insures that
00346 // contours have width < 2, and so we can find the left/right neighbors,
00347 // and deduce end points. By using a minimally smoothed image,
00348 // we find step profiles up to joining with a stronger contour, thus
00349 // recovering the missing junction caused by NMS along only 1 direction.
00350 // The junctions are returned but are not set in the contour image,
00351 // to prevent incorrect tracing of stronger contours first.
00352 // The search is extended outward for a distance of kmax which is currently
00353 // 4*smoothSigma + 2.
00354 int
00355 gevd_step::RecoverJunctions(const gevd_bufferxy& image,
00356                             gevd_bufferxy& contour, gevd_bufferxy& direction,
00357                             gevd_bufferxy& locationx, gevd_bufferxy& locationy,
00358                             int*& junctionx, int*& junctiony)
00359 {
00360 #if defined(DEBUG)
00361   vul_timer t;
00362 #endif
00363   if (image.GetBitsPixel() != bits_per_float) {
00364     vcl_cerr << "gevd_step::RecoverJunction requires float image\n";
00365     return false;
00366   }
00367   const int rmax = 1+FRAME;     // 1 + kernel radius of BestStepExtension
00368   const int kmax = int(4 * smoothSigma + 0.5) + 2; // gap = 2
00369   const int xmax = image.GetSizeX()-rmax-1; // fill step direction
00370   const int ymax = image.GetSizeY()-rmax-1;
00371 #ifdef DEBUG
00372   vcl_cout << "RecoverJunctions: rmax, kmax, xmax, ymax:" << rmax << ' ' << kmax << ' ' << xmax << ' ' << ymax << '\n';
00373 #endif
00374   // 1. Find end points of dangling contours
00375   //const int length0 = xmax/kmax*ymax/kmax/4;// 25% size
00376   //const float growth = 2;     // growth ratio of the arrays
00377 
00378   vcl_vector<int> ndir; //  ndir.set_growth_ratio(growth);
00379   vcl_vector<int> xloc; //  xloc.set_growth_ratio(growth); // dynamic array instead of long lists
00380   vcl_vector<int> yloc; //  yloc.set_growth_ratio(growth);
00381   int xdir;
00382   for (int y = rmax; y <= ymax; y++) // find end points of long contours
00383     for (int x = rmax; x <= xmax; x++) // inside image border - rmax
00384       if (floatPixel(contour, x, y) && // on contour
00385           (xdir = LeftXorRightEnd(contour, x, y, // left xor right neighbor
00386                                   bytePixel(direction, x, y))) != 0)
00387       {
00388         ndir.push_back(xdir);   // save end point of elongated contours
00389         xloc.push_back(x);
00390         yloc.push_back(y);
00391       }
00392   const int length = ndir.size();
00393   //vcl_cout << "% end pats = "     // trace allocated size
00394   //          << length*100 / float((xmax/kmax)*(ymax/kmax)) << vcl_endl;
00395   if (!length) return 0;        // no end points exist
00396 
00397   // 2. Extend from end points until they touch other contours
00398   gevd_bufferxy* smooth = NULL;
00399   gevd_float_operators::Gaussian((gevd_bufferxy&)image, smooth, smoothSigma/2); // avoid oversmoothing
00400   const bool shortp = true;     // short contours
00401   const float threshold = NoiseThreshold(shortp);
00402   float slope, loc;
00403   int njunction = 0;            // number of junctions found
00404   for (int r = 1; r <= kmax; r++) { // breadth-first extension
00405     int ntouch = 0, nextension = 0;
00406     for (int i = 0; i < length; i++)
00407       if ((xdir = ndir[i]) != 0 && xdir != TWOPI) // still extension?
00408       {
00409         int x = xloc[i], y = yloc[i];
00410         int dir = bytePixel(direction, x, y); // direction of step
00411         slope = BestStepExtension(*smooth,
00412                                   x, y, dir + xdir,     // current end pt
00413                                   threshold,
00414                                   x, y, dir, loc);
00415         if (slope) {                 // next end point
00416           xloc[i] = x, yloc[i] = y; // update new end point
00417           xdir = LeftXorRightEnd(contour,    // mark completed if
00418                                  x, y, dir); // another contour is reached,
00419                                              // indicated by xdir==0.
00420           if (xdir)
00421           {
00422             if (x < rmax || x > xmax || // check for reaching border
00423                 y < rmax || y > ymax)
00424               xdir = 0;               // junction with virtual border
00425             else
00426               nextension++;
00427             floatPixel(contour, x, y) = slope; // still disconnected
00428           }
00429           else
00430           {           // touching another contour
00431             ntouch++;
00432             xdir = TWOPI;     // mark junction, without linking chains
00433           }
00434           ndir[i] = xdir;
00435           bytePixel(direction, x, y) = byte(dir);
00436           floatPixel(locationx, x, y) = loc*DIS[dir];
00437           floatPixel(locationy, x, y) = loc*DJS[dir];
00438         }
00439         else                  // no further extension found
00440           ndir[i] = 0;
00441       }
00442     //vcl_cout << "Touch " << ntouch << " contours.\n";
00443     // vcl_cout << "Will extend " << nextension << " contours.\n";
00444     njunction += ntouch;
00445     if (!nextension) break;     // all either junction or termination
00446   }
00447   delete smooth;
00448 
00449   // 3. Return the end points or junctions found
00450   if (junctionx) delete [] junctionx;
00451   if (junctiony) delete [] junctiony;
00452   junctionx = new int[njunction];
00453   junctiony = new int[njunction];
00454   for (int i = 0, j = 0; i < length; i++)
00455     if (ndir[i] == TWOPI) {
00456       junctionx[j] = xloc[i];
00457       junctiony[j] = yloc[i];
00458       j++;
00459     }
00460 #if defined(DEBUG)
00461   vcl_cout << "Find " << length << " end points, and "
00462            << njunction << " junctions.\n"
00463            << "Recover " << 100.0*njunction/length
00464            << "% end points as junctions > "
00465            << threshold << ", in " << t.real() << " msecs.\n";
00466 #endif
00467   return njunction;
00468 }
00469 
00470 
00471 //:
00472 // Return the standard deviation of raw noise, in the original image,
00473 // either estimated or given by the user. If the noise has not been
00474 // estimated, return 0.
00475 float
00476 gevd_step::NoiseSigma() const
00477 {
00478   return (noiseSigma <= 0)? 0: noiseSigma;
00479 }
00480 
00481 
00482 //:
00483 // Compute response of white noise through the filter dG, or
00484 // second-derivative of the Gaussian. Using a threshold of 3 times
00485 // this noise response would eliminate 99% of the noise edges.
00486 float
00487 gevd_step::NoiseResponse() const
00488 {
00489   return NoiseResponseToFilter(noiseSigma,
00490                                smoothSigma, filterFactor);
00491 }
00492 
00493 
00494 //:
00495 // Return threshold for detecting contour or junction,
00496 // which is response of white gaussian noise, noise_sigma,
00497 // to step edge detector, i.e. first-order derivative of Gaussian,
00498 // smooth_sigma.
00499 // noise_sigma can be estimated by finding the standard deviation
00500 // in a region of constant intensity, and no texture patterns.
00501 // Use short_factor*noise_sigma and smooth_sigma/2, when detecting
00502 // junctions, to account for multiple responses to step edge detector.
00503 float
00504 gevd_step::NoiseThreshold(bool shortp) const
00505 {
00506   float factor = (shortp? junctionFactor : contourFactor);
00507   float smooth = (shortp? smoothSigma/2 : smoothSigma);
00508   return factor * 3 *          // 3*sigma for 99% removal confidence
00509          NoiseResponseToFilter(noiseSigma, smooth, filterFactor);
00510 }
00511 
00512 
00513 //:
00514 // Compute response of white noise through the filter dG, or
00515 // first-derivative of the Gaussian. Using a threshold of 3 times
00516 // this noise response would eliminate 99% of the noise edges.
00517 float
00518 gevd_step::NoiseResponseToFilter(const float noiseSigma,
00519                                  const float smoothSigma,
00520                                  const float filterFactor)
00521 {
00522   return noiseSigma /          // white noise
00523          (float)vcl_pow((double)smoothSigma, 1.5) * // size of filter dG
00524          (0.5f / (float)vcl_pow(vnl_math::pi, 0.25)) *
00525          filterFactor;        // multiplication factor
00526 }
00527 
00528 
00529 //: Output a snapshot of current control parameters
00530 vcl_ostream& operator<< (vcl_ostream& os, const gevd_step& st)
00531 {
00532   os << "Step:\n"
00533      << "   smoothSigma " << st.smoothSigma << vcl_endl
00534      << "   noiseSigma " << st.noiseSigma << vcl_endl
00535      << "   contourFactor " << st.contourFactor << vcl_endl
00536      << "   junctionFactor " << st.junctionFactor << vcl_endl
00537      << "   filterFactor " << st.filterFactor << vcl_endl;
00538     return os;
00539 }
00540 
00541 
00542 //: Output a snapshot of current control parameters
00543 vcl_ostream& operator<< (vcl_ostream& os, gevd_step& st)
00544 {
00545   os << "Step:\n"
00546      << "   smoothSigma " << st.smoothSigma << vcl_endl
00547      << "   noiseSigma " << st.noiseSigma << vcl_endl
00548      << "   contourFactor " << st.contourFactor << vcl_endl
00549      << "   junctionFactor " << st.junctionFactor << vcl_endl
00550      << "   filterFactor " << st.filterFactor << vcl_endl;
00551     return os;
00552 }