contrib/brl/bseg/sdet/sdet_gauss_fit.cxx
Go to the documentation of this file.
00001 // This is brl/bseg/sdet/sdet_gauss_fit.cxx
00002 #include "sdet_gauss_fit.h"
00003 //:
00004 // \file
00005 
00006 #include <vcl_iostream.h>
00007 #include <vcl_fstream.h>
00008 #include <vcl_cmath.h>
00009 #include <vgl/vgl_point_3d.h>
00010 #include <vnl/vnl_math.h>
00011 #include <vsol/vsol_point_2d.h> // for dereferencing ps_list[j]
00012 
00013 // for new matrix operations
00014 #include <vnl/vnl_matrix_fixed.h>
00015 #include <vnl/vnl_inverse.h>
00016 
00017 #include <vnl/algo/vnl_levenberg_marquardt.h>
00018 
00019 #define JIL_DIAGNOSTICS
00020 // -----------------------------------------------------------------
00021 // constructor
00022 sdet_adjust_lsqr::
00023     sdet_adjust_lsqr( vcl_vector<vgl_point_3d<double> > const& img_pts,
00024                       unsigned int num_unknowns, unsigned int num_residuals,
00025                       int n_peaks)
00026             : vnl_least_squares_function(num_unknowns, num_residuals,
00027                                          vnl_least_squares_function::no_gradient)
00028             , img_pts_(img_pts)
00029             , num_pixels_(img_pts_.size())
00030             , n_peaks_(n_peaks)
00031 {
00032 }
00033 
00034 // -----------------------------------------------------------------
00035 // MAIN method: function "f" to calculate errors
00036 // The virtual least-squares cost function -
00037 //        calculates the fit of pixels to 2-d gaussian curve
00038 
00039 // The 1 + n+peaks*6 unknowns to fit:
00040 //                 [0] plane = zero plane (floor) of gaussian distributions, same for all peaks
00041 //        Each peak has the following 6 parameters
00042 //                 [1] peak  = peak value of gaussian distribution
00043 //                 [2] x_bar = x location (in pixels) of peak
00044 //                 [3] y_bar = y location (in pixels) of peak
00045 //            And the 2x2 covariance matrix (the 2 off-diagonals are equal)
00046 //                 [4] var_x = variance in x direction [0,0]
00047 //                 [5] var_y = variance in y direction [1,1]
00048 //                 [6] covar = off-diagonal covariance values [0,1] = [1,0]
00049 
00050 // The fit_error residual vector elements are mis-fit errors for each image point
00051 //    (NOTE: the errors are set to the absolute values (not squares) of the differences)
00052 
00053 void sdet_adjust_lsqr::f( vnl_vector<double> const& unknowns,
00054                           vnl_vector<double> & fit_error)
00055 {
00056   // set the parameters of the 2-d Gaussian from "unknowns" and
00057   // calculate the pixel point predictions using these values
00058 
00059   vnl_vector<double> fit_value(num_pixels_); // the gaussian predictions of image intensity
00060   vnl_vector<double> sum(num_pixels_); // the sum of gaussians from all peaks
00061   // zero out all sums
00062   for (unsigned i=0; i < num_pixels_; ++i)  {
00063     sum[i] = 0.0;
00064   }
00065 
00066   // Multiple peaks complicate this calculation, as the contribution of all the
00067   //   peaks must be added together to get the predicted value at a pixel
00068 
00069   // Loop through the peaks to add up predicted value for each pixel
00070   for (int j=0; j < n_peaks_; ++j)
00071   {
00072     vnl_matrix_fixed<double,2,2> V;
00073     V(0,0) = unknowns[6*j+4];                    // the x variance
00074     V(0,1) = unknowns[6*j+6];                    // the covariance (equal)
00075     V(1,0) = unknowns[6*j+6];                    // the covariance (equal)
00076     V(1,1) = unknowns[6*j+5];                    // the y variance
00077 
00078     vnl_matrix_fixed<double,2,2> Vinv = vnl_inverse(V);
00079 
00080     for (unsigned i=0; i < img_pts_.size(); ++i)
00081     {
00082       double peak_delta = unknowns[6*j+1] - unknowns[0]; //predicted height of peak above bkgd
00083 
00084       vnl_matrix_fixed<double,2,1> D;                    // the pixel's xi-xbari matrix
00085       D(0,0) = img_pts_[i].x() - unknowns[6*j+2];        // pixel x loc - peak x loc
00086       D(1,0) = img_pts_[i].y() - unknowns[6*j+3];        // pixel y loc - peak y loc
00087 
00088       vnl_matrix_fixed<double,1,2> Dtrans = D.transpose();    // the xi-xbari matrix transpose
00089       double exponent = ((-1.0 * Dtrans * Vinv * D)(0,0))/2.0;    // exponent of e
00090 
00091       //value of gaussian at x,y, a matrix operation
00092       double gaussian = peak_delta * vcl_exp(exponent);
00093 
00094       // add up value of summed components of multiple peaks at eacy image point
00095       sum[i] += gaussian;
00096     }
00097   }
00098 
00099   // finally, add "floor" value to sum
00100   for (unsigned i=0; i < img_pts_.size(); ++i)
00101     fit_value[i] = sum[i] + unknowns[0];    // add value of pixel above "floor"
00102 
00103 #if 0 // Debug:  print out values for this step
00104   vcl_cout << "plane(" << unknowns[0] << "),  peak(" << unknowns[1] << "),\n"
00105            << "x(" << unknowns[2] << "),  y(" << unknowns[3] << ")\n"
00106            << "var x(" << unknowns[4] << "),  var y(" << unknowns[5] << ")\n"
00107            << "covar(" << unknowns[6] << ')' << vcl_endl;
00108 #endif
00109 
00110 // calculate the fit_error values of the current fit
00111   for (unsigned i = 0; i < img_pts_.size(); ++i)
00112   {
00113     double e = (fit_value[i] - img_pts_[i].z());
00114     fit_error[i] = vcl_fabs(e);
00115 #if 0 // Debug:  print out values of fit error for each point
00116     vcl_cout << "fiterror[" << i << ']' << fit_error[i] << vcl_endl;
00117 #endif
00118   }
00119 }
00120 
00121 
00122 // -------------------------------------------------------------------------
00123 // Initialize the process
00124 
00125 static bool init(vcl_vector<vgl_point_3d<double> > img_pts, vcl_vector<double>& peak,
00126                  double& plane, vcl_vector<double>& ux, vcl_vector<double>& uy,
00127                  vcl_vector<double>& sxx, vcl_vector<double>& syy, vcl_vector<double>& sxy,
00128                  vcl_vector<vsol_point_2d_sptr> ps_list, int n_peaks, double xmin, double ymin)
00129 {
00130   //find the smallest intensity value in polygon
00131   plane = 65000.;
00132   for (unsigned i=0; i<img_pts.size(); ++i)
00133   {
00134     if ( img_pts[i].z() < plane )
00135     plane = img_pts[i].z();
00136   }
00137 
00138   // With multiple peaks, finding the largest is more complex,
00139   // Let's assume that the largest value around each peak will occur near the
00140   //   selected position.  So let's just look in that local area for a couple pixels.
00141 
00142   for (int j=0; j < n_peaks; ++j)
00143   {
00144     peak.push_back(0.0);                // create vector elements for this peak
00145     ux.push_back(0.0);
00146     uy.push_back(0.0);
00147     sxx.push_back(0.0);
00148     syy.push_back(0.0);
00149     sxy.push_back(0.0);
00150 
00151     double xf = ps_list[j]->x() - xmin;            // "picked" values of x & y for this peak
00152     double yf = ps_list[j]->y() - ymin;
00153 
00154     for (unsigned i=0; i<img_pts.size(); ++i)
00155     {
00156       double dx = img_pts[i].x() - xf;
00157       double dy = img_pts[i].y() - yf;
00158       // is this point within 2 pixels of selected peak's "picked" location??
00159       if (dx >= -2. && dx <= 2. && dy >= -2. && dy <= 2.)
00160       if ( img_pts[i].z() > peak[j] )  {
00161         peak[j] = img_pts[i].z();
00162         ux[j] = img_pts[i].x();                // these are our rough guesses
00163         uy[j] = img_pts[i].y();
00164       }
00165     }
00166 
00167     // Compute various moments of the "Gaussian" distribution
00168     // For multiple peaks, the moments become much more difficult to calculate.
00169     // Let's see if a simplifying assumption can be used and still converge.  This
00170     // assumption will be that the highest pixel value around the peak will be a
00171     // good starting point for the peak and x,y values.  We will assume the variances
00172     // will be about half a pixel with covariance = 0. (see above and below)
00173 
00174 #if 0 // Original code, now commented out
00175     double sum = 0.0;
00176     ux = 0.0;
00177     uy = 0.0;
00178     for (unsigned i=0; i<img_pts.size(); ++i)
00179     {
00180       vgl_point_3d<double>& p = img_pts[i];
00181       double w = p.z()-plane;
00182       sum += w;
00183       double x = p.x(), y = p.y();
00184       ux += w*x;
00185       uy += w*y;
00186       sxx[j] += x*x*w;
00187       syy[j] += y*y*w;
00188       sxy[j] += x*y*w;
00189     }
00190 
00191     if (sum<=0) return false;
00192     ux /= sum;
00193     uy /= sum;
00194     sxx[j] = 0;
00195     syy[j] = 0;
00196     sxy[j] = 0;
00197 
00198     for (unsigned i=0; i<img_pts.size(); ++i)
00199     {
00200       vgl_point_3d<double>& p = img_pts[i];
00201       double w = p.z()-plane;
00202       double x = p.x(), y = p.y();
00203       sxx[j] += (x-ux)*(x-ux)*w;
00204       syy[j] += (y-uy)*(y-uy)*w;
00205       sxy[j] += (x-ux)*(y-uy)*w;
00206     }
00207 
00208     sxx[j] /= sum;
00209     syy[j] /= sum;
00210     sxy[j] /= sum;
00211 
00212 #else // more rough guesses
00213     sxx[j] = 0.5;
00214     syy[j] = 0.5;
00215     sxy[j] = 0.0;
00216 #endif // 0
00217   }                            // end of for j loop
00218   return true;
00219 }
00220 
00221 
00222 // ------------------------------------------------------------
00223 //: Adjust the parameters of the 2d gaussian
00224 //    Returns adjusted fit parameters
00225 
00226 vnl_vector<double> sdet_gauss_fit::adjust( vcl_vector<vgl_point_3d<double> > img_pts,
00227                                            vcl_vector<vsol_point_2d_sptr> ps_list,
00228                                            int n_peaks, vcl_ofstream& outfile,
00229                                            double xmin, double ymin)
00230 {
00231   unsigned num_pixels = img_pts.size();
00232   unsigned num_unknowns = 6*n_peaks + 1;            // zero level + (6 * number of peaks)
00233   unsigned num_residuals = num_pixels;
00234 
00235   // Do some calculations to find reasonable values to start with
00236 
00237   // We want to be able to draw a polygon on the image and fit only the
00238   //   pixels in that polygon.  So here the pixels and their x,y location
00239   //   in the image are stored in a vcl_vector<vgl_point_3d<double> img_pnts
00240 
00241   double plane;
00242   vcl_vector<double> peak;
00243   vcl_vector<double> ux;
00244   vcl_vector<double> uy;
00245   vcl_vector<double> sxx;
00246   vcl_vector<double> syy;
00247   vcl_vector<double> sxy;
00248 
00249   if (!init(img_pts, peak, plane, ux, uy, sxx, syy, sxy, ps_list, n_peaks, xmin, ymin))
00250   {
00251     outfile << "ERROR!! sdet_gauss_fit::adjust(), Cannot init()" << vcl_endl;
00252     vcl_cerr << "ERROR!! sdet_gauss_fit::adjust(), Cannot init()\n";
00253   }
00254 
00255   for (int i=0; i < n_peaks; ++i)
00256   {
00257 #if 0
00258     outfile << "Init values:\n-----------------------------------------------\n Peak "
00259             << i+1 << "\n -------------------------\nplane= " << plane << vcl_endl
00260             << "peak=" << peak[i] << ",  ux=" << ux[i]
00261             << ",  uy=" << uy[i] << ",  sxx=" << sxx[i] << ",  syy=" << syy[i]
00262             << ",  sxy=" << sxy[i] << vcl_endl;
00263     outfile.flush();
00264 #endif
00265     vcl_cout << "Init values:\n-----------------------------------------------\n Peak "
00266              << i+1 << "\n -------------------------\nplane= " << plane << vcl_endl
00267              << "peak=" << peak[i] << ",  ux=" << ux[i]
00268              << ",  uy=" << uy[i] << ",  sxx=" << sxx[i] << ",  syy=" << syy[i]
00269              << ",  sxy=" << sxy[i] << vcl_endl;
00270   }
00271 
00272   // Initialize the least squares function
00273   sdet_adjust_lsqr lsf(img_pts, num_unknowns, num_residuals, n_peaks);
00274 
00275   // --------
00276   // Create the Levenberg Marquardt minimizer
00277   vnl_levenberg_marquardt levmarq(lsf);
00278 
00279 #ifdef JIL_DIAGNOSTICS
00280   levmarq.set_verbose(true);
00281   levmarq.set_trace(true);
00282 #endif
00283 
00284   //Use the default values - Leaving these calls for potential future need
00285 #if 0
00286   // Set the x-tolerance.  When the length of the steps taken in X (variables)
00287   // are no longer than this, the minimization terminates.
00288   levmarq.set_x_tolerance(1e-10);
00289 
00290   // Set the epsilon-function.  This is the step length for FD Jacobian.
00291   levmarq.set_epsilon_function(1);
00292 
00293   // Set the f-tolerance.  When the successive RMS errors are less than this,
00294   // minimization terminates.
00295   levmarq.set_f_tolerance(1e-15);
00296 #endif
00297 
00298   // Set the maximum number of iterations
00299   levmarq.set_max_function_evals(1000);
00300 
00301   // Create an INITIAL values of parameters with which to start
00302   vnl_vector<double> unknowns(num_unknowns);
00303 
00304   unknowns[0] = plane;
00305   for (int i=0; i < n_peaks; ++i)
00306   {
00307     unknowns[1+6*i] = peak[i];
00308     unknowns[2+6*i] = ux[i];
00309     unknowns[3+6*i] = uy[i];
00310     unknowns[4+6*i] = sxx[i];
00311     unknowns[5+6*i] = syy[i];
00312     unknowns[6+6*i] = sxy[i];
00313 
00314     outfile << "max pixel for peak " << i+1 << " = " << peak[i] << vcl_endl;
00315   }
00316 
00317   vcl_cout << "\nStarting Levenberg-Marquardt minimizer, may take a few seconds per iteration"
00318            << vcl_endl;
00319 
00320   // Minimize the error and get best correspondence vertices and translations
00321   levmarq.minimize(unknowns);
00322 
00323   // Summarize the results
00324   levmarq.diagnose_outcome();
00325 #if 0
00326   outfile << "Min error of " << levmarq.get_end_error()
00327           << " at the following local minima :" << vcl_endl;
00328 #endif
00329   vcl_cout << "Min error of " << levmarq.get_end_error()
00330            << " at the following local minima :" << vcl_endl;
00331 
00332   // print out parameter fit results
00333 #if 0
00334   outfile << "  -----------------------------\nFitted parameters:\n------------" << vcl_endl;
00335 #endif
00336   vcl_cout << "  -----------------------------\nFitted parameters:\n------------" << vcl_endl;
00337 
00338   for (unsigned i=0; i<num_unknowns; ++i)
00339   {
00340 #if 0
00341     outfile << "unknowns[" << i << "]= " << unknowns[i] << vcl_endl;
00342     if (i%6==4||i%6==5) vcl_cout << "  sd = " << vcl_sqrt(unknowns[i]) << vcl_endl;
00343 #endif
00344 
00345     vcl_cout << "unknowns[" << i << "]= " << unknowns[i] << vcl_endl;
00346     if (i%6==4||i%6==5) vcl_cout << "  sd = " << vcl_sqrt(unknowns[i]) << vcl_endl;
00347 
00348     if ((i % 6) == 0)  {
00349 #if 0
00350       outfile << "------------" << vcl_endl;
00351 #endif
00352       vcl_cout << "------------" << vcl_endl;
00353     }
00354   }
00355   outfile.flush();
00356   vcl_cout.flush();
00357 
00358   return unknowns;
00359 }
00360 
00361 // -------------------------------------------------------
00362 // Calculate the parameters for the ellipse
00363 // Argument is fitted 2-d gaussian parameters, returns ellipse angle and parameters
00364 
00365 vnl_vector<double> sdet_gauss_fit::calculate_ellipse( vnl_vector<double> result, float x,
00366                                                       float y, int i, int n_peaks,
00367                                                       vcl_ofstream& outfile)
00368 {
00369   // First check to be sure n_peaks agrees with the length of result
00370   vnl_vector<double> params(3*n_peaks);
00371 
00372   const double pi = vnl_math::pi;
00373   double theta = 0.0;                  // angle from x axis to ellipse major axis
00374   double a = 0.0;                      // ellipse major axis std deviation
00375   double b = 0.0;                      // ellipse minor axis std deviation
00376   double sxx = result[6*i+4];          // copy result data into local variables
00377   double syy = result[6*i+5];
00378   double sxy = result[6*i+6];
00379 
00380   // take care of special cases where sxy = 0 or sxx = syy
00381   // First calculate the appropriate booleans about relationship of s(nn)
00382   bool xxgtyy = (sxx > syy);
00383   bool xxeqyy = (vcl_fabs(sxx - syy) < 0.0001);
00384   bool xygt0 = (sxy > 0.0001);
00385   bool xyeq0 = (vcl_fabs(sxy) < 0.0001);
00386 
00387   // Case 7
00388   if (xyeq0 && xxeqyy)
00389     theta = 0.0;                    // 0 degrees, along x axis
00390   // Case 2
00391   else if (xxeqyy && xygt0)
00392     theta = pi/4.0;                        // 45 degrees, diagonal
00393   // Case 3
00394   else if (xxeqyy && !xygt0)
00395     theta = 3.0*pi/4.0;                    // 135 degrees, diagonal
00396   // the other normal cases
00397   else
00398   {
00399     theta = 0.5 * vcl_atan(2.0 * sxy/(sxx - syy));
00400 
00401     // Case 1
00402     if (xxgtyy && xygt0) {}                // theta is correct
00403     // Case 4
00404     else if (xxgtyy && !xygt0)
00405       theta = theta + pi;                // theta + 180 degrees
00406     // Case 5&6
00407     else if (!xxgtyy)
00408       theta = theta + pi/2.0;            // theta + 90 degrees
00409   }
00410 
00411   double angle = theta*180./pi;
00412 
00413   // now calculate the one sigma length of the major and minor axis
00414   double sqrt2 = vcl_sqrt(2.0);
00415   double factor = vcl_sqrt(sxx*sxx + syy*syy + 4.0*sxy*sxy - 2.0*sxx*syy);
00416   a = vcl_sqrt(sxx + syy + factor)/sqrt2;
00417   b = vcl_sqrt(sxx + syy - factor)/sqrt2;
00418 
00419   // print out and write results to a file too
00420   outfile << "--------------------\nresults for peak " << i+1 << vcl_endl;
00421   vcl_cout << "--------------------\nresults for peak " << i+1 << vcl_endl;
00422 
00423   double bkgd = result[0];
00424   double peak = result[6*i+1];
00425   double d_peak = peak - bkgd;
00426 
00427   outfile << "bkgd= " << bkgd << ",  peak= " << peak << ",  d_peak= "
00428           << d_peak << vcl_endl
00429           << "x= " << x << ",  y= " << y << vcl_endl;
00430   vcl_cout << "bkgd= " << bkgd << ",  peak= " << peak << ",  d_peak= "
00431            << d_peak << vcl_endl
00432            << "x= " << x << ",  y= " << y << vcl_endl;
00433 #if 0
00434   outfile << "sxx= " << sxx << ",  syy= " << syy << ",  sxy= " << sxy
00435           << ",  factor= " << factor << vcl_endl;
00436   vcl_cout << "sxx= " << sxx << ",  syy= " << syy << ",  sxy= " << sxy
00437            << ",  factor= " << factor << vcl_endl
00438            << "sin(theta)= " << vcl_sin(theta) << ",   cos(theta)= "
00439            << vcl_cos(theta) << vcl_endl;
00440 #endif
00441 
00442   outfile << "theta= " << theta << ",   angle= " << angle
00443           << ",   a= " << a << ",   b= " << b << vcl_endl;
00444   vcl_cout << "theta= " << theta << ",   angle= " << angle
00445            << ",   a= " << a << ",   b= " << b << vcl_endl;
00446   outfile.flush();
00447   vcl_cout.flush();
00448 
00449   params[0] = theta;
00450   params[1] = a;
00451   params[2] = b;
00452 
00453     return params;
00454 }