contrib/rpl/rgrl/rgrl_transformation.cxx

Go to the documentation of this file.
00001 #include "rgrl_transformation.h"
00002 //:
00003 // \file
00004 // \brief Base class for transformation representation, estimations and application in generalized registration library
00005 // \author Chuck Stewart
00006 // \date 15 Nov 2002
00007 
00008 #include <rgrl/rgrl_util.h>
00009 #include <vnl/vnl_math.h>
00010 #include <vnl/vnl_least_squares_function.h>
00011 #include <vnl/algo/vnl_levenberg_marquardt.h>
00012 
00013 #include <vcl_iostream.h>
00014 #include <vcl_cassert.h>
00015 
00016 #include <vnl/vnl_cross.h>
00017 #include <vnl/vnl_double_3.h>
00018 #include <vnl/vnl_double_2.h>
00019 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00020 
00021 rgrl_transformation::
00022 rgrl_transformation( const vnl_matrix<double>& cov )
00023   : is_covar_set_(false)
00024 {
00025   set_covar( cov );
00026 }
00027 
00028 rgrl_transformation::
00029 ~rgrl_transformation()
00030 {
00031 }
00032 
00033 void
00034 rgrl_transformation::
00035 map_location( vnl_vector<double> const& from,
00036               vnl_vector<double>      & to    ) const
00037 {
00038   map_loc( from, to );
00039 }
00040 
00041 vnl_vector<double>
00042 rgrl_transformation::
00043 map_location( vnl_vector<double> const& p ) const
00044 {
00045   vnl_vector<double> result( p.size() );
00046   map_loc( p, result );
00047   return result;
00048 }
00049 
00050 void
00051 rgrl_transformation::
00052 map_direction( vnl_vector<double> const& from_loc,
00053                vnl_vector<double> const& from_dir,
00054                vnl_vector<double>      & to_dir    ) const
00055 {
00056   map_dir( from_loc, from_dir, to_dir );
00057 }
00058 
00059 void
00060 rgrl_transformation::
00061 map_tangent( vnl_vector<double> const& from_loc,
00062              vnl_vector<double> const& from_dir,
00063              vnl_vector<double>      & to_dir    ) const
00064 {
00065   vnl_matrix<double> J;
00066   this->jacobian_wrt_loc( J, from_loc );
00067   assert ( from_loc.size() == J.cols() );
00068   assert ( from_dir.size() == J.cols() );
00069 
00070   to_dir = J * from_dir;
00071   to_dir.normalize();
00072 }
00073 
00074 void
00075 rgrl_transformation::
00076 map_normal( vnl_vector<double> const & from_loc,
00077             vnl_vector<double> const & from_dir,
00078             vnl_vector<double>       & to_dir    ) const
00079 {
00080 #if 0
00081   // generate the tangent subspace
00082   vnl_matrix< double > tangent_subspace;
00083   vnl_matrix<double> one_row( 1, from_dir.size() );
00084   one_row.set_row( 0, from_dir );
00085   vnl_svd<double> normal_svd( one_row );
00086   tangent_subspace = normal_svd.nullspace();
00087   assert ( tangent_subspace.columns() == from_dir. size() - 1 );
00088 
00089   // compute the transformed normal from by transforming the tangent_subspace
00090   map_normal( from_loc, from_dir, tangent_subspace, to_dir );
00091 #endif
00092 
00093   // assuming it is face feature, i.e., with dimension n-1
00094   unsigned int m = from_loc.size();
00095   if (m == 2) //rotate the tangent by 90 degrees
00096   {
00097     // rotate from_dir
00098     vnl_vector< double > from_tangent(2);
00099     from_tangent[0] =  from_dir[1];
00100     from_tangent[1] = -from_dir[0];
00101 
00102     // map tangent
00103     vnl_vector< double > xformed_tangent;
00104     map_tangent(from_loc, from_tangent, xformed_tangent);
00105 
00106     // rotate mapped tangent to get normal
00107     xformed_tangent.normalize();
00108     to_dir.set_size( 2 );
00109     to_dir[0] = -xformed_tangent[1];
00110     to_dir[1] =  xformed_tangent[0];
00111   }
00112   else //m == 3, compute the normal as the cross-product of the two xformed tangents
00113   {
00114     // avoid using SVD to
00115     // obtain two tangent bases
00116     // Description of this somewhat naive method:
00117     // In order to obtain the 1st tangent basis,
00118     // given normal vector n, 3D orthogonality constraint is:
00119     // t^T * n = 0
00120     // Now, if we set one particular element as 0(suppose it is the first one)
00121     // what is left in the constraint is 0 + t2*n2 + t3*n3 = 0
00122     // which is essentially 2D.
00123     // We can solve scalars t2 and t3 using the above method.
00124     // Then we obtain the first tangent [0 t2 t3]
00125     // We can use cross product to obtain the second tangent.
00126     // The only tricky part is which element to pick to set as 0!
00127     // The solution is to pick the element with smallest magnitude
00128     // Why? Think about a normal vector in such a form [1 0.2 0].
00129     vnl_double_3 from_tangent0;
00130     vnl_double_3 from_tangent1;
00131     // find the element with smallest magnitude
00132     unsigned int min_index=0;
00133     double min = vcl_abs(from_dir[0]);
00134     for ( unsigned int i=1; i<3; i++)
00135       if ( vcl_abs(from_dir[i]) < min ) {
00136         min = vcl_abs( from_dir[i] );
00137         min_index = i;
00138       }
00139 
00140     // shrink it to 2D, by removing that smallest element.
00141     vnl_double_2 t, n;
00142     for (unsigned int i=0,j=0; i<3; ++i)
00143       if ( i != min_index )
00144         n[j++] = from_dir[i];
00145     // 2D orthogonality constraint
00146     t[0] =  n[1];
00147     t[1] = -n[0];
00148     // fill it back to 3D, with the corresponding smallest
00149     // element set as zero
00150     for (unsigned int i=0,j=0; i<3; ++i)
00151       if ( i != min_index )
00152         from_tangent0[i] = t[j++];
00153       else
00154         from_tangent0[i] = 0.0;
00155 
00156     // it is easier for the second one
00157     from_tangent1 = vnl_cross_3d( vnl_double_3(from_dir), from_tangent0 );
00158 
00159     // transform tangent dir
00160     vnl_vector< double > xformed_tangent0;
00161     vnl_vector< double > xformed_tangent1;
00162     map_tangent(from_loc, from_tangent0.as_ref(), xformed_tangent0);
00163     map_tangent(from_loc, from_tangent1.as_ref(), xformed_tangent1);
00164     to_dir = vnl_cross_3d( xformed_tangent0, xformed_tangent1 );
00165     to_dir.normalize();
00166   }
00167 }
00168 
00169 void
00170 rgrl_transformation::
00171 map_normal( vnl_vector<double> const  & from_loc,
00172             vnl_vector<double> const  & /*from_dir*/,
00173             vnl_matrix< double > const& tangent_subspace,
00174             vnl_vector<double>        & to_dir    ) const
00175 {
00176 #if 0
00177   // Transform basis tangent vectors
00178   vnl_matrix< double > xform_tangent_subspace = tangent_subspace.transpose();
00179   vnl_vector< double > xformed_tangent;
00180   for ( unsigned int i = 0; i< tangent_subspace.columns(); i++ ) {
00181     map_tangent(from_loc, tangent_subspace.get_column(i), xformed_tangent);
00182     xform_tangent_subspace.set_row(i, xformed_tangent);
00183   }
00184 
00185   // It is not necessary to orthogonize bases.  The reason is
00186   // the null space is always orthogonal to a linear combination
00187   // of any number of bases
00188   if ( tangent_subspace.columns() == 2 ) {
00189     // If (m == 3), make the 2 tangent vector orthogonal
00190     vnl_vector< double > tangent1 = xform_tangent_subspace.get_row(0);
00191     vnl_vector< double > tangent2 = xform_tangent_subspace.get_row(1);
00192     vnl_vector< double > ortho_tangent = tangent2 - inner_product(tangent2,tangent1)* tangent1;
00193     xform_tangent_subspace.set_row(1, ortho_tangent.normalize());
00194   }
00195 
00196   // Get the transformed normal from the xformed tangent subspace
00197   vnl_svd<double> tangent_svd( xform_tangent_subspace );
00198   assert ( tangent_svd.nullspace().columns() == 1 );
00199   to_dir = tangent_svd.nullspace().get_column(0);
00200 #endif
00201 
00202   unsigned int m = tangent_subspace.rows();
00203   if (m == 2) {//rotate the tangent by 90 degrees
00204     vnl_vector< double > xformed_tangent;
00205     map_tangent(from_loc, tangent_subspace.get_column(0), xformed_tangent);
00206     xformed_tangent.normalize();
00207     to_dir.set_size( 2 );
00208     to_dir[0] = -xformed_tangent[1];
00209     to_dir[1] =  xformed_tangent[0];
00210   }
00211   else { //m == 3, compute the normal as the cross-product of the two xformed tangents
00212     vnl_vector< double > xformed_tangent0;
00213     vnl_vector< double > xformed_tangent1;
00214     map_tangent(from_loc, tangent_subspace.get_column(0), xformed_tangent0);
00215     map_tangent(from_loc, tangent_subspace.get_column(1), xformed_tangent1);
00216     to_dir = vnl_cross_3d( xformed_tangent0, xformed_tangent1 );
00217     to_dir.normalize();
00218   }
00219 }
00220 
00221 double
00222 rgrl_transformation::
00223 map_intensity( vnl_vector<double> const& /*from*/,
00224                double intensity ) const
00225 {
00226   return intensity;
00227 }
00228 
00229 double
00230 rgrl_transformation::
00231 log_det_covar() const
00232 {
00233   return log_det_sym_matrix( covar_ );
00234 }
00235 
00236 double
00237 rgrl_transformation::
00238 log_det_sym_matrix( vnl_matrix<double> const& m ) const
00239 {
00240   assert( m.rows() && m.cols() );
00241   assert( m.rows() == m.cols() );
00242 
00243   // when computing the log of determinant,
00244   // because determinant is the product of all eigen-values,
00245   // and because a log is taken,
00246   // we go around it and do a sum on the log of *each* of the
00247   // eigen-values.
00248 
00249   double result = 0;
00250   vnl_symmetric_eigensystem<double> eig(m);
00251   for ( unsigned i=0; i<m.rows(); ++i )
00252     result += vcl_log( eig.get_eigenvalue(i) );
00253   return result;
00254 }
00255 
00256 double
00257 rgrl_transformation::
00258 log_det_covar_deficient( int rank ) const
00259 {
00260   // first, scan the matrix and eleminate
00261   // rows and columns containing only zeros
00262   vcl_vector<unsigned int> zero_indices;
00263   for ( unsigned i=0; i<covar_.rows(); ++i )
00264     if ( !covar_(i,i) ) {
00265 
00266       // scan the whole row
00267       bool all_zero=true;
00268       for ( unsigned j=0; j<covar_.cols()&&all_zero; ++j )
00269         if ( covar_(i,j) )
00270           all_zero=false;
00271 
00272       if ( all_zero )
00273         zero_indices.push_back( i );
00274     }
00275 
00276   vnl_matrix<double> m;
00277 
00278   if ( !zero_indices.empty() ) {
00279 
00280     // put together a new matrix without the rows and
00281     // columns of zeros
00282     const unsigned int num = covar_.rows() - zero_indices.size();
00283     m.set_size( num, num );
00284     for ( unsigned i=0,ic=0,iz=0; i<covar_.rows(); ++i ) {
00285 
00286       if ( iz<zero_indices.size() && i == zero_indices[iz] ) {
00287         ++iz;
00288         continue;
00289       }
00290 
00291       for ( unsigned j=0,jc=0,jz=0; j<covar_.cols(); ++j ) {
00292 
00293         if ( jz<zero_indices.size() && j == zero_indices[jz] ) {
00294           ++jz;
00295           continue;
00296         }
00297         m( ic, jc ) = covar_( i, j );
00298 
00299         ++jc;
00300       }
00301 
00302       ++ic;
00303     }
00304   } else
00305     m = covar_;
00306 
00307   // compute the log of determinant with the largest [rank] eigenvalues
00308   const int num = m.rows();
00309 
00310   // by default
00311   if ( rank <= 0 )
00312     rank = num;
00313 
00314   double result = 0;
00315   vnl_symmetric_eigensystem<double> eig(m);
00316   for ( int i=0; i<rank; ++i )
00317     result += vcl_log( eig.get_eigenvalue(num-1-i) );
00318   return result;
00319 }
00320 
00321 //:  Parameter covariance matrix
00322 vnl_matrix<double>
00323 rgrl_transformation::
00324 covar() const
00325 {
00326   assert( is_covar_set_ );
00327   return covar_;
00328 }
00329 
00330 vnl_matrix<double>
00331 rgrl_transformation::
00332 jacobian( vnl_vector<double> const& from_loc ) const
00333 {
00334   vnl_matrix<double> jac;
00335   this->jacobian_wrt_loc( jac, from_loc );
00336 
00337   return jac;
00338 }
00339 
00340 //:  set parameter covariance matrix
00341 void
00342 rgrl_transformation::
00343 set_covar( const vnl_matrix<double>& covar )
00344 {
00345   covar_ = covar;
00346   if ( covar.rows() > 0 && covar.cols() > 0 ) {
00347     is_covar_set_ = true;
00348   }
00349 }
00350 
00351 //: set scaling factors
00352 //  Unless the transformation is not estimated using estimators in rgrl,
00353 //  it does not need to be set explicitly
00354 void
00355 rgrl_transformation::
00356 set_scaling_factors( vnl_vector<double> const& scaling )
00357 {
00358   // checking scaling
00359   // set it to epsison if scaling is in fact zero
00360   for ( unsigned int i=0; i<scaling.size(); ++i )
00361     assert( vnl_math_isfinite( scaling[i] ) );
00362 
00363   scaling_factors_ = scaling;
00364 }
00365 
00366 
00367 vcl_ostream&
00368 operator<< (vcl_ostream& os, rgrl_transformation const& xform )
00369 {
00370   xform.write( os );
00371   return os;
00372 }
00373 
00374 //: output transformation
00375 void
00376 rgrl_transformation::
00377 write( vcl_ostream& os ) const
00378 {
00379   if ( is_covar_set() )
00380   {
00381     // write covariance
00382     os << "COVARIANCE\n"
00383        << covar_.rows() << ' ' << covar_.cols() << vcl_endl
00384        << covar_ << vcl_endl;
00385   }
00386 
00387   if ( scaling_factors_.size() )
00388   {
00389     // write scaling factors
00390     os << "SCALING_FACTORS\n"
00391        << scaling_factors_.size() << vcl_endl
00392        << scaling_factors_ << vcl_endl;
00393   }
00394 }
00395 
00396 //: input transformation
00397 bool
00398 rgrl_transformation::
00399 read( vcl_istream& is )
00400 {
00401   vcl_streampos pos;
00402   vcl_string tag_str;
00403 
00404   // skip any empty lines
00405   rgrl_util_skip_empty_lines( is );
00406   if ( !is.good() )
00407     return true;   // reach the end of stream
00408 
00409   tag_str="";
00410   pos = is.tellg();
00411   vcl_getline( is, tag_str );
00412 
00413   if ( tag_str.find("COVARIANCE") == 0 )
00414   {
00415     // read in covariance matrix
00416     int m=-1, n=-1;
00417     vnl_matrix<double> cov;
00418 
00419     // get dimension
00420     is >> m >> n;
00421     if ( !is || m<=0 || n<=0 )
00422       return false;   // cannot get the dimension
00423 
00424     cov.set_size(m, n);
00425     cov.fill(0.0);
00426     is >> cov;
00427 
00428     if ( !is.good() )
00429       return false;  // cannot read the covariance matrix
00430 
00431     this->set_covar( cov );
00432 
00433     // read in the next tag
00434     // skip any empty lines
00435     rgrl_util_skip_empty_lines( is );
00436     if ( is.eof() )
00437       return true;   // reach the end of stream
00438 
00439     tag_str="";
00440     pos = is.tellg();
00441     vcl_getline( is, tag_str );
00442   }
00443 
00444   if ( tag_str.find("SCALING_FACTORS") == 0 )
00445   {
00446     // read in scaling factors
00447     int m=-1;
00448 
00449     // get dimension
00450     is >> m;
00451 
00452     if ( !is || m<=0 )
00453       return false;  // cannot get dimension
00454 
00455     scaling_factors_.set_size( m );
00456     is >> scaling_factors_;
00457 
00458     return is.good();
00459   }
00460 
00461   // reset the stream pos
00462   is.seekg( pos );
00463   return is.good();
00464 }
00465 
00466 
00467 class inverse_mapping_func
00468 : public vnl_least_squares_function
00469 {
00470  public:
00471   inverse_mapping_func( rgrl_transformation const* xform_ptr, vnl_vector<double> const& to_loc )
00472     : vnl_least_squares_function( to_loc.size(), to_loc.size(), use_gradient ),
00473       xform_( xform_ptr ), to_loc_( to_loc )
00474   {}
00475 
00476   //: obj func value
00477   void f(vnl_vector<double> const& x, vnl_vector<double>& fx);
00478 
00479   //: Jacobian
00480   void gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian);
00481 
00482  public:
00483   const rgrl_transformation* xform_;
00484   vnl_vector<double>   to_loc_;
00485 };
00486 
00487 
00488 void
00489 inverse_mapping_func::
00490 f(vnl_vector<double> const& x, vnl_vector<double>& fx)
00491 {
00492   assert( xform_ );
00493 
00494   // x is the From location
00495   // just need to apply forward transformation and
00496   xform_->map_location( x, fx );
00497 
00498   // get the difference
00499   fx -= to_loc_;
00500 }
00501 
00502 void
00503 inverse_mapping_func::
00504 gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian)
00505 {
00506   assert( xform_ );
00507 
00508   // jacobian is just the jacobian of transformation w.r.t location
00509   // x is the From location
00510   xform_->jacobian_wrt_loc( jacobian, x );
00511 }
00512 
00513 //:  Inverse map with an initial guess
00514 void
00515 rgrl_transformation::
00516 inv_map( const vnl_vector<double>& to,
00517          bool initialize_next,
00518          const vnl_vector<double>& /*to_delta*/, // FIXME: unused
00519          vnl_vector<double>& from,
00520          vnl_vector<double>& from_next_est) const
00521 {
00522   // use different objects for different dimension
00523   if ( to.size() == 2 )
00524   {
00525     static inverse_mapping_func inv_map_func( this, to );
00526 
00527     // set transformation and the desired
00528     inv_map_func.to_loc_ = to;
00529     inv_map_func.xform_  = this;
00530 
00531     // solve for from location
00532     static vnl_levenberg_marquardt lm( inv_map_func );
00533 
00534     // lm.set_trace( true );
00535     // lm.set_check_derivatives( 5 );
00536     // we don't need it to be super accurate
00537     lm.set_f_tolerance( 1e-4 );
00538     lm.set_x_tolerance( 1e-3 );
00539     lm.set_max_function_evals( 100 );
00540 
00541     // run LM
00542     bool ret = lm.minimize_using_gradient( from );
00543 
00544     if ( !ret ) {
00545       WarningMacro( "Levenberg-Marquatt in rgrl_transformation::inv_map has failed!!!" );
00546       return;
00547     }
00548   }
00549   else if ( to.size() == 3 )
00550   {
00551     static inverse_mapping_func inv_map_func( this, to );
00552 
00553     // set transformation and the desired
00554     inv_map_func.to_loc_ = to;
00555     inv_map_func.xform_  = this;
00556 
00557     // solve for from location
00558     static vnl_levenberg_marquardt lm( inv_map_func );
00559 
00560     // lm.set_trace( true );
00561     // lm.set_check_derivatives( 5 );
00562     // we don't need it to be super accurate
00563     lm.set_f_tolerance( 1e-4 );
00564     lm.set_x_tolerance( 1e-3 );
00565     lm.set_max_function_evals( 100 );
00566 
00567     // run LM
00568     bool ret = lm.minimize_using_gradient( from );
00569 
00570     if ( !ret ) {
00571       WarningMacro( "Levenberg-Marquatt in rgrl_transformation::inv_map has failed!!!" );
00572       return;
00573     }
00574   }
00575   else
00576   {
00577     assert( ! "Other dimensioin is not implemented!" );
00578     return;
00579   }
00580 
00581   // initialize the next
00582   // NOTE:
00583   // no need to compute the inverse of jacobian here
00584   // because the inverse using SVD is about expensive as LM.
00585   // and there is extra memory allocation
00586   // Therefore, just initialize it as current from
00587   //
00588   if ( initialize_next ) {
00589     from_next_est = from;
00590   }
00591 }
00592 
00593 //:  Inverse map based on the transformation.
00594 //   This function only exist for certain transformations.
00595 void
00596 rgrl_transformation::
00597 inv_map( const vnl_vector<double>& to,
00598          vnl_vector<double>& from ) const
00599 {
00600   const bool initialize_next = false;
00601   vnl_vector<double> unused;
00602   inv_map( to, initialize_next, unused, from, unused );
00603 }
00604 
00605 //: Return an inverse transformation
00606 //  This function only exist for certain transformations.
00607 rgrl_transformation_sptr
00608 rgrl_transformation::
00609 inverse_transform() const
00610 {
00611   assert( !"Should never reach rgrl_transformation::inverse_transform()" );
00612   return 0;
00613 }
00614 

Generated on Mon Mar 8 05:27:39 2010 for contrib/rpl/rgrl by  doxygen 1.5.1