contrib/mul/mbl/mbl_thin_plate_spline_2d.cxx
Go to the documentation of this file.
00001 // This is mul/mbl/mbl_thin_plate_spline_2d.cxx
00002 #include "mbl_thin_plate_spline_2d.h"
00003 //:
00004 // \file
00005 // \brief Construct thin plate spline to map 2D to 2D
00006 // \author Tim Cootes
00007 
00008 #include <vcl_cmath.h>
00009 #include <vcl_cstdlib.h> // for vcl_abort()
00010 #include <vsl/vsl_indent.h>
00011 #include <vsl/vsl_vector_io.h>
00012 #include <vnl/vnl_math.h>
00013 #include <vnl/algo/vnl_svd.h>
00014 #include <vnl/io/vnl_io_vector.h>
00015 #include <vnl/io/vnl_io_matrix.h>
00016 #include <vgl/vgl_vector_2d.h>
00017 #include <vgl/io/vgl_io_point_2d.h>
00018 #include <mbl/mbl_matxvec.h>
00019 
00020 //=======================================================================
00021 
00022 //=======================================================================
00023 // Dflt ctor
00024 // Default constructor gives identity mapping
00025 //=======================================================================
00026 
00027 mbl_thin_plate_spline_2d::mbl_thin_plate_spline_2d()
00028   : Wx_(0),Wy_(0),
00029     Ax0_(0),AxX_(1),AxY_(0),Ay0_(0),AyX_(0),AyY_(1),
00030     energy_x_(0),energy_y_(0),return_pure_affine_(false)
00031 {
00032 }
00033 
00034 //=======================================================================
00035 // Destructor
00036 //=======================================================================
00037 
00038 mbl_thin_plate_spline_2d::~mbl_thin_plate_spline_2d()
00039 {
00040 }
00041 // First some useful maths functions
00042 
00043 #if 0 // unused
00044 inline double r2lnr(const vgl_point_2d<double>&  pt)
00045 {
00046   double r2 = pt.x() * pt.x() + pt.y() * pt.y();
00047   if (r2>1e-8)   return 0.5 * r2 * vcl_log(r2);
00048   else     return 0;
00049 }
00050 #endif
00051 
00052 inline double r2lnr(const vgl_vector_2d<double>&  pt)
00053 {
00054   double r2 = pt.x() * pt.x() + pt.y() * pt.y();
00055   if (r2>1e-8)   return 0.5 * r2 * vcl_log(r2);
00056   else     return 0;
00057 }
00058 
00059 inline double r2lnr(double x, double y)
00060 {
00061   double r2 = x * x + y * y;
00062   if (r2>1e-8) return 0.5 * r2 * vcl_log(r2);
00063   else      return 0;
00064 }
00065 
00066 
00067 // Sets L to be a symmetric square matrix of size n + 3 (n = pts.nelems)
00068 // with L(i,j) = Uij = r2lnr(pts(i)-pts(j)) for i,j <= n
00069 static void build_K_part(vnl_matrix<double>& L,
00070                          const vcl_vector<vgl_point_2d<double> >& pts)
00071 {
00072   unsigned int n = pts.size();
00073   if ( (L.rows()!=n+3) | (L.columns()!=n+3) ) L.set_size(n+3,n+3);
00074 
00075   const vgl_point_2d<double> * pts_data = &pts[0];
00076   double** K_data = L.data_array();
00077 
00078     // Zero the diagonal
00079   for (unsigned int i=0;i<n;i++)
00080     K_data[i][i] = 0;
00081     // Now fill upper & lower triangles
00082   for (unsigned int i=1;i<n;i++)
00083     for (unsigned int j=0;j<i;j++)
00084     {
00085       K_data[i][j] = K_data[j][i] = r2lnr(pts_data[i]-pts_data[j]);
00086     }
00087 }
00088 
00089 // L is a (n+3) x (n+3) matrix;
00090 // L = ( K  Q )
00091 //     ( Q' 0 )
00092 // Where K is n x n, K(i,j) = Uij = r2lnr(pts(i)-pts(j)) for i,j <= n
00093 // and Q is ( 1 x0 y0 )
00094 //          ( 1 x1 y1 )
00095 //             . .  .
00096 static void build_L(vnl_matrix<double>& L, const vcl_vector<vgl_point_2d<double> >& pts)
00097 {
00098   int i,j;
00099 
00100   build_K_part(L,pts);
00101 
00102   int n = pts.size();
00103 
00104   const vgl_point_2d<double> * pts_data = &pts[0];
00105   double** L_data = L.data_array();
00106 
00107   // Build Q part
00108 
00109   for (i=0;i<n;i++)
00110   {
00111     L_data[i][n] = 1;
00112     L_data[i][n+1] = pts_data[i].x();
00113     L_data[i][n+2] = pts_data[i].y();
00114 
00115     L_data[n][i] = 1;
00116     L_data[n+1][i] = pts_data[i].x();
00117     L_data[n+2][i] = pts_data[i].y();
00118   }
00119 
00120   // put 0's in bottom left corner.
00121   for (i=n;i<n+3;i++)
00122     for (j=n;j<n+3;j++)
00123       L_data[i][j] = 0;
00124 }
00125 
00126 //: Build from small number of points
00127 void mbl_thin_plate_spline_2d::build_pure_affine(
00128         const vcl_vector<vgl_point_2d<double> >& source_pts,
00129         const vcl_vector<vgl_point_2d<double> >& dest_pts)
00130 {
00131   int n=source_pts.size();
00132   L_inv_.set_size(0,0);
00133   if (n==0)
00134   {
00135     // Set identity transformation
00136     Ax0_ = 0;
00137     Ay0_ = 0;
00138     AxX_ = 1; AxY_ = 0;
00139     AyX_ = 0; AyY_ = 1;
00140 
00141     Wx_.set_size(0);
00142     Wy_.set_size(0);
00143 
00144     src_pts_.resize(0);
00145 
00146     return;
00147   }
00148 
00149   if (n==1)
00150   {
00151     // Just apply a translation :
00152     Ax0_ = dest_pts[0].x() - source_pts[0].x();
00153     Ay0_ = dest_pts[0].y() - source_pts[0].y();
00154     Wx_.set_size(0);
00155     Wy_.set_size(0);
00156     AxX_ = 1; AxY_ = 0;
00157     AyX_ = 0; AyY_ = 1;
00158     src_pts_.resize(0);
00159 
00160     return;
00161   }
00162 
00163   if (n==2)
00164   {
00165     // Calculate trans, scaling & rotation required to
00166     // do mapping.
00167 
00168     // If points x1 & x2 get mapped to points u1 and u2
00169     // then the equation mapping a general point x into
00170     // u is
00171     //  u = (u1 - Rx1) + Rx
00172     // where R is the rotation matrix ( a -b )
00173     //                                ( b  a )
00174     // with a & b derived from the relative vectors in u & v
00175     // (see below for details)
00176 
00177     vgl_point_2d<double>  x1,x2,u1,u2;
00178     double dx,dy,du,dv;    // Points (x2-x1) and (u2-u1)
00179     double a,b;
00180 
00181     x1 = source_pts[0];
00182     x2 = source_pts[1];
00183     u1 = dest_pts[0];
00184     u2 = dest_pts[1];
00185 
00186     dx = x2.x() - x1.x();
00187     dy = x2.y() - x1.y();
00188     du = u2.x() - u1.x();
00189     dv = u2.y() - u1.y();
00190 
00191     double L2=(dx * dx + dy * dy);
00192     a = (dx * du + dy * dv)/L2;
00193     b = (dv * dx - du * dy)/L2;
00194 
00195     AxX_ = a;
00196     AxY_ = -b;
00197     AyX_ = b;
00198     AyY_ = a;
00199     Ax0_ = u1.x() - (a * x1.x() - b * x1.y());
00200     Ay0_ = u1.y() - (b * x1.x() + a * x1.y());
00201 
00202     Wx_.set_size(0);
00203     Wy_.set_size(0);
00204     src_pts_.resize(0);
00205 
00206     return;
00207   }
00208 
00209 
00210   if (n==3)
00211   {
00212     // Calculate the 6 affine parameters do mapping.
00213 
00214     // If points x0,x1 & x2 get mapped to points u0,u1 and u2
00215     // then the equation mapping a general point x into
00216     // u is
00217     //  u = (u0 - A.x0) + A.x
00218     // where A is the affine matrix ( a b )
00219     //                              ( c  d )
00220 
00221     vgl_point_2d<double>  x0,x1,x2,u0,u1,u2;
00222 
00223     x0 = source_pts[0];
00224     x1 = source_pts[1];
00225     x2 = source_pts[2];
00226     u0 = dest_pts[0];
00227     u1 = dest_pts[1];
00228     u2 = dest_pts[2];
00229 
00230     double dx1 = x1.x() - x0.x();
00231     double dy1 = x1.y() - x0.y();
00232     double dx2 = x2.x() - x0.x();
00233     double dy2 = x2.y() - x0.y();
00234 
00235     double du1 = u1.x() - u0.x();
00236     double dv1 = u1.y() - u0.y();
00237     double du2 = u2.x() - u0.x();
00238     double dv2 = u2.y() - u0.y();
00239 
00240     double L2=(dx1 * dy2 - dy1 * dx2);
00241     double a = (du1*dy2 - du2*dy1)/L2;
00242     double b = (du2*dx1 - du1*dx2)/L2;
00243     double c = (dv1*dy2 - dv2*dy1)/L2;
00244     double d = (dv2*dx1 - dv1*dx2)/L2;
00245 
00246     AxX_ = a;
00247     AxY_ = b;
00248     AyX_ = c;
00249     AyY_ = d;
00250     Ax0_ = u0.x() - (a * x0.x() + b * x0.y());
00251     Ay0_ = u0.y() - (c * x0.x() + d * x0.y());
00252 
00253     Wx_.set_size(0);
00254     Wy_.set_size(0);
00255     src_pts_.resize(0);
00256 
00257     return;
00258   }
00259 }
00260 
00261 //: Set parameters from vectors
00262 void mbl_thin_plate_spline_2d::set_params(const vnl_vector<double>& W1,
00263                                           const vnl_vector<double>& W2)
00264 {
00265   int n = W1.size()-3;
00266 
00267   if (int(Wx_.size()) < n) Wx_.set_size(n);
00268   if (int(Wy_.size()) < n) Wy_.set_size(n);
00269 
00270   double *Wx_data=Wx_.data_block();
00271   double *Wy_data=Wy_.data_block();
00272   const double *W1_data=W1.data_block();
00273   const double *W2_data=W2.data_block();
00274 
00275   for (int i=0;i<n;i++)
00276   {
00277     Wx_data[i] = W1_data[i];
00278     Wy_data[i] = W2_data[i];
00279   }
00280 
00281   Ax0_ = W1_data[n];
00282   AxX_ = W1_data[n+1];
00283   AxY_ = W1_data[n+2];
00284 
00285   Ay0_ = W2_data[n];
00286   AyX_ = W2_data[n+1];
00287   AyY_ = W2_data[n+2];
00288 }
00289 
00290 void mbl_thin_plate_spline_2d::compute_energy(vnl_vector<double>& W1,
00291                                               vnl_vector<double>& W2,
00292                                               const vnl_matrix<double>& L)
00293 {
00294   int n = W1.size()-3;
00295   double *W1_data=W1.data_block();
00296   double *W2_data=W2.data_block();
00297 
00298   // Compute bending energy = W_t.K.W/(8pi)
00299   // Set last elements to zero
00300   W1_data[n]=0.0;
00301   W1_data[n+1]=0.0;
00302   W1_data[n+2]=0.0;
00303   vnl_vector<double> LW;
00304   mbl_matxvec_prod_mv(L,W1,LW);
00305   energy_x_ = dot_product(W1,LW) / (8*vnl_math::pi);
00306 
00307   // Set last elements to zero
00308   W2_data[n]=0.0;
00309   W2_data[n+1]=0.0;
00310   W2_data[n+2]=0.0;
00311   mbl_matxvec_prod_mv(L,W2,LW);
00312   energy_y_ = dot_product(W2,LW) / (8*vnl_math::pi);
00313 }
00314 
00315 void mbl_thin_plate_spline_2d::set_up_rhs(vnl_vector<double>& Bx,
00316                                           vnl_vector<double>& By,
00317                                           const vcl_vector<vgl_point_2d<double> >& dest_pts)
00318 {
00319   int n =dest_pts.size();
00320 
00321   Bx.set_size(n+3);
00322   By.set_size(n+3);
00323   double* Bx_data=Bx.data_block();
00324   double* By_data=By.data_block();
00325   const vgl_point_2d<double>  *d_pts_data=&dest_pts[0];
00326 
00327   for (int i=0;i<n;i++)
00328   {
00329     Bx_data[i] = d_pts_data[i].x();
00330     By_data[i] = d_pts_data[i].y();
00331   }
00332   for (int i=n;i<n+3;i++)
00333   {
00334     Bx_data[i] = 0;
00335     By_data[i] = 0;
00336   }
00337 }
00338 
00339 void mbl_thin_plate_spline_2d::build(const vcl_vector<vgl_point_2d<double> >& source_pts,
00340                                      const vcl_vector<vgl_point_2d<double> >& dest_pts,
00341                                      bool compute_the_energy)
00342 {
00343   // See Booksteins paper in IPMI 1993 for details of calculation
00344 
00345   unsigned int n=source_pts.size();
00346   if (dest_pts.size() != n)
00347   {
00348     vcl_cerr<<"mbl_thin_plate_spline_2d::build - incompatible number of points.\n";
00349     vcl_abort();
00350   }
00351 
00352   L_inv_.set_size(0,0);
00353 
00354   if (n<=3)
00355   {
00356     build_pure_affine(source_pts,dest_pts);
00357     return;
00358   }
00359 
00360   src_pts_ = source_pts;
00361 
00362   vnl_matrix<double> L;
00363   vnl_vector<double> Bx(n+3), W1(n+3);  // Used to compute X parameters
00364   vnl_vector<double> By(n+3), W2(n+3);  // Used to compute Y parameters
00365 
00366   build_L(L,source_pts);
00367 
00368   set_up_rhs(Bx,By,dest_pts);
00369 
00370   // Solve LW = B for W1 and W2 :
00371   // Note that both Cholesky and QR decompositions fail, apparently because of the
00372   // zeroes on the diagonal.  Use SVD to be safe.
00373   {
00374     vnl_svd<double> svd(L);
00375     svd.solve(Bx.data_block(),W1.data_block());
00376     svd.solve(By.data_block(),W2.data_block());
00377   }
00378 
00379   set_params(W1,W2);
00380   if (compute_the_energy)
00381     compute_energy(W1,W2,L);
00382 }
00383 
00384 //: Define source point positions
00385 //  Performs pre-computations so that build(dest_points) can be
00386 //  called multiple times efficiently
00387 void mbl_thin_plate_spline_2d::set_source_pts(const vcl_vector<vgl_point_2d<double> >& source_pts)
00388 {
00389   unsigned int n=source_pts.size();
00390   src_pts_ = source_pts;
00391 
00392   if (n<=3)
00393   {
00394     L_inv_.set_size(0,0);
00395     return;
00396   }
00397 
00398   vnl_matrix<double> L;
00399   build_L(L,source_pts);
00400 
00401   // Compute inverse of L
00402   // Note that both Cholesky and QR decompositions fail, apparently because of the
00403   // zeroes on the diagonal.  Use SVD to be safe.
00404   {
00405     vnl_svd<double> svd(L);
00406     L_inv_ = svd.inverse();
00407   }
00408 }
00409 
00410 //: Sets up internal transformation to map source_pts onto dest_pts
00411 void mbl_thin_plate_spline_2d::build(const vcl_vector<vgl_point_2d<double> >& dest_pts)
00412 {
00413   unsigned int n=src_pts_.size();
00414   if (dest_pts.size() != n)
00415   {
00416     vcl_cerr<<"mbl_thin_plate_spline_2d::build - incompatible number of points.\n";
00417     vcl_abort();
00418   }
00419 
00420   if (n<=3)
00421   {
00422     build_pure_affine(src_pts_,dest_pts);
00423     return;
00424   }
00425 
00426   vnl_vector<double> Bx(n+3), W1(n+3);  // Used to compute X parameters
00427   vnl_vector<double> By(n+3), W2(n+3);  // Used to compute Y parameters
00428 
00429   set_up_rhs(Bx,By,dest_pts);
00430 
00431   mbl_matxvec_prod_mv(L_inv_,Bx,W1);
00432   mbl_matxvec_prod_mv(L_inv_,By,W2);
00433 
00434   set_params(W1,W2);
00435   energy_x_ = -1;
00436   energy_y_ = -1;
00437 }
00438 
00439 
00440 vgl_point_2d<double>  mbl_thin_plate_spline_2d::operator()(double x, double y) const
00441 {
00442   unsigned int n = src_pts_.size();
00443 
00444   double x_sum = Ax0_ + AxX_ * x + AxY_ * y;
00445   double y_sum = Ay0_ + AyX_ * x + AyY_ * y;
00446 
00447   if (n<=3 || return_pure_affine_)  // Pure affine
00448     return vgl_point_2d<double>(x_sum,y_sum);
00449 
00450   const vgl_point_2d<double> * pts_data = &src_pts_[0];
00451   const double* Wx_data = Wx_.data_block();
00452   const double* Wy_data = Wy_.data_block();
00453 
00454   for (unsigned int i=0;i<n;i++)
00455   {
00456     double Ui = r2lnr(x - pts_data[i].x(), y - pts_data[i].y() );
00457     x_sum += (Ui * Wx_data[i]);
00458     y_sum += (Ui * Wy_data[i]);
00459   }
00460 
00461   return vgl_point_2d<double>(x_sum,y_sum);
00462 }
00463 
00464 //=======================================================================
00465 // Method: version_no
00466 //=======================================================================
00467 
00468 short mbl_thin_plate_spline_2d::version_no() const
00469 {
00470   return 1;
00471 }
00472 
00473 
00474 //=======================================================================
00475 // Method: print
00476 //=======================================================================
00477 
00478   // required if data is present in this class
00479 void mbl_thin_plate_spline_2d::print_summary(vcl_ostream& os) const
00480 {
00481   os<<"\nfx: "<<Ax0_<<" + "<<AxX_<<"*x + "<<AxY_<<"*y   Nonlinear terms:";
00482   for (unsigned int i=0;i<Wx_.size();++i)
00483     os<<" "<<Wx_[i];
00484   os<<"\nfy: "<<Ay0_<<" + "<<AyX_<<"*x + "<<AyY_<<"*y   Nonlinear terms:";
00485   for (unsigned int i=0;i<Wy_.size();++i)
00486     os<<" "<<Wy_[i];
00487   os<<'\n';
00488 }
00489 
00490 //=======================================================================
00491 // Method: save
00492 //=======================================================================
00493 
00494   // required if data is present in this class
00495 void mbl_thin_plate_spline_2d::b_write(vsl_b_ostream& bfs) const
00496 {
00497   vsl_b_write(bfs,version_no());
00498   vsl_b_write(bfs,Wx_); vsl_b_write(bfs,Wy_);
00499   vsl_b_write(bfs,Ax0_); vsl_b_write(bfs,AxX_); vsl_b_write(bfs,AxY_);
00500   vsl_b_write(bfs,Ay0_); vsl_b_write(bfs,AyX_); vsl_b_write(bfs,AyY_);
00501   vsl_b_write(bfs,energy_x_); vsl_b_write(bfs,energy_y_);
00502   vsl_b_write(bfs,src_pts_);
00503   vsl_b_write(bfs,L_inv_);
00504 }
00505 
00506 //=======================================================================
00507 // Method: load
00508 //=======================================================================
00509 
00510   // required if data is present in this class
00511 void mbl_thin_plate_spline_2d::b_read(vsl_b_istream& bfs)
00512 {
00513   if (!bfs) return;
00514 
00515   short version;
00516   vsl_b_read(bfs,version);
00517   switch (version)
00518   {
00519     case (1):
00520       vsl_b_read(bfs,Wx_);
00521       vsl_b_read(bfs,Wy_);
00522       vsl_b_read(bfs,Ax0_); vsl_b_read(bfs,AxX_); vsl_b_read(bfs,AxY_);
00523       vsl_b_read(bfs,Ay0_); vsl_b_read(bfs,AyX_); vsl_b_read(bfs,AyY_);
00524       vsl_b_read(bfs,energy_x_); vsl_b_read(bfs,energy_y_);
00525       vsl_b_read(bfs,src_pts_);
00526       vsl_b_read(bfs,L_inv_);
00527       break;
00528     default:
00529       vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&, mbl_thin_plate_spline_2d &)\n"
00530                << "           Unknown version number "<< version << '\n';
00531       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00532       return;
00533   }
00534 }
00535 
00536 //: Comparison operator
00537 bool mbl_thin_plate_spline_2d::operator==(const mbl_thin_plate_spline_2d& tps) const
00538 {
00539   if (&tps==this) return true;
00540   if (vcl_fabs(Ax0_-tps.Ax0_)>1e-8) return false;
00541   if (vcl_fabs(AxX_-tps.AxX_)>1e-8) return false;
00542   if (vcl_fabs(AxY_-tps.AxY_)>1e-8) return false;
00543   if (vcl_fabs(Ay0_-tps.Ay0_)>1e-8) return false;
00544   if (vcl_fabs(AyX_-tps.AyX_)>1e-8) return false;
00545   if (vcl_fabs(AyY_-tps.AyY_)>1e-8) return false;
00546   if (vnl_vector_ssd(Wx_,tps.Wx_)>1e-6) return false;
00547   if (vnl_vector_ssd(Wy_,tps.Wy_)>1e-6) return false;
00548   return true;
00549 }
00550 
00551 //=======================================================================
00552 // Associated function: operator<<
00553 //=======================================================================
00554 
00555 void vsl_b_write(vsl_b_ostream& bfs, const mbl_thin_plate_spline_2d& b)
00556 {
00557     b.b_write(bfs);
00558 }
00559 
00560 //=======================================================================
00561 // Associated function: operator>>
00562 //=======================================================================
00563 
00564 void vsl_b_read(vsl_b_istream& bfs, mbl_thin_plate_spline_2d& b)
00565 {
00566     b.b_read(bfs);
00567 }
00568 
00569 //=======================================================================
00570 // Associated function: operator<<
00571 //=======================================================================
00572 
00573 vcl_ostream& operator<<(vcl_ostream& os,const mbl_thin_plate_spline_2d& b)
00574 {
00575   os << "mbl_thin_plate_spline_2d: ";
00576   vsl_indent_inc(os);
00577   b.print_summary(os);
00578   vsl_indent_dec(os);
00579   return os;
00580 }