contrib/gel/vdgl/vdgl_ortho_regress.cxx
Go to the documentation of this file.
00001 // This is gel/vdgl/vdgl_ortho_regress.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 //  \file
00007 
00008 #include "vdgl_ortho_regress.h"
00009 #include <vcl_cmath.h>
00010 #include <vcl_cassert.h>
00011 
00012 // convenience methods
00013 
00014 void vdgl_ortho_regress::add_points(double const *x, double const *y, unsigned n) {
00015   for (unsigned i=0; i<n; ++i)
00016     add_point(x[i], y[i]);
00017 }
00018 
00019 void vdgl_ortho_regress::add_points(float const *x, float const *y, unsigned n) {
00020   for (unsigned i=0; i<n; ++i)
00021     add_point(x[i], y[i]);
00022 }
00023 
00024 double vdgl_ortho_regress::rms_cost(double a, double b, double c) const {
00025   return vcl_sqrt( cost(a, b, c) / S1 );
00026 }
00027 
00028 //--------------------------------------------------------------------------------
00029 
00030 // this can be done without netlib
00031 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00032 
00033 // f(x, y)
00034 // =
00035 // a x^2 + 2 b x y + c y^2
00036 // =
00037 // [x y] [a b] [x]
00038 //       [b c] [y]
00039 static
00040 bool symmetric_2x2_eigen_system(double a, double b, double c,
00041                                 double *xmin, double *ymin, double *lmin,
00042                                 double *xmax, double *ymax, double *lmax)
00043 {
00044   vnl_matrix<double> M(2, 2);
00045   M(0, 0) = a;
00046   M(0, 1) = M(1, 0) = b;
00047   M(1, 1) = c;
00048   vnl_symmetric_eigensystem<double> sym(M);
00049   if (xmin) *xmin = sym.V(0, 0);
00050   if (ymin) *ymin = sym.V(1, 0);
00051   if (lmin) *lmin = sym.D(0, 0);
00052 
00053   if (xmax) *xmax = sym.V(0, 1);
00054   if (ymax) *ymax = sym.V(1, 1);
00055   if (lmax) *lmax = sym.D(1, 1);
00056 
00057   return true;
00058 }
00059 
00060 //:
00061 // The problem is to minimize total squared distance to
00062 // the line u.x + d = 0, ie. :
00063 // minimize     \sum_i | u.x_i + d |^2
00064 // subject to   |u|=1
00065 //
00066 // Writing x_i = xbar + y_i, where xbar is the centroid
00067 // of the point set, the sum becomes
00068 // \sum | u.(x_i - xbar) |^2 + S1 | u.xbar + d |^2
00069 // , so that the optimal line must pass through the centroid.
00070 // To find u, we find the eigenvector of the scatter matrix
00071 // M = \sum (x_i - xbar)*(x_i - xbar)^t which has least
00072 // eigenvalue.
00073 //
00074 double vdgl_ortho_regress::cost(double a, double b, double c) const {
00075   return
00076     (Sxx*a*a + 2*Sxy*a*b + Syy*b*b + 2*Sx*a*c + 2*Sy*b*c + S1*c*c)/(a*a+b*b);
00077 }
00078 
00079 bool vdgl_ortho_regress::fit(double &a, double &b, double &c) const {
00080   assert(S1 >= 2);
00081   double xbar = Sx/S1;
00082   double ybar = Sy/S1;
00083 
00084   if (!symmetric_2x2_eigen_system(Sxx-Sx*Sx/S1, Sxy-Sx*Sy/S1, Syy-Sy*Sy/S1,
00085                                   &a, &b, 0,
00086                                   0, 0, 0))
00087     return false;
00088 
00089   c = -(a*xbar+b*ybar);
00090   return true;
00091 }
00092 
00093 //:
00094 // The problem is to minimize total squared distance from
00095 // a line passing through a given point x_0 :
00096 //
00097 // minimize     \sum_i | u.x_i + d |^2
00098 // subject to   |u|=1
00099 //              u.x_0 + d = 0
00100 //
00101 // But u.x_i + d = u.(x_i - x_0), so that we have to minimize
00102 // u^t M u , where M = \sum_i (x_i - x_0)*(x_i - x_0)^t. This
00103 // is solved by taking the eigenvector of M of least
00104 // eigenvalue.
00105 //
00106 bool vdgl_ortho_regress::fit_constrained(double x, double y,
00107                                          double &a, double &b, double &c) const
00108 {
00109   assert(S1 >= 1);
00110 
00111   if (!symmetric_2x2_eigen_system(Sxx-2*Sx*x+S1*x*x, Sxy-Sx*y-x*Sy+S1*x*y, Syy-2*Sy*y+S1*y*y,
00112                                   &a, &b, 0,
00113                                   0, 0, 0))
00114     return false;
00115 
00116   c = -(a*x + b*y);
00117   return true;
00118 }