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 eliminate
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   }
00305   else
00306     m = covar_;
00307 
00308   // compute the log of determinant with the largest [rank] eigenvalues
00309   const int num = m.rows();
00310 
00311   // by default
00312   if ( rank <= 0 )
00313     rank = num;
00314 
00315   double result = 0;
00316   vnl_symmetric_eigensystem<double> eig(m);
00317   for ( int i=0; i<rank; ++i )
00318     result += vcl_log( eig.get_eigenvalue(num-1-i) );
00319   return result;
00320 }
00321 
00322 //:  Parameter covariance matrix
00323 vnl_matrix<double>
00324 rgrl_transformation::
00325 covar() const
00326 {
00327   assert( is_covar_set_ );
00328   return covar_;
00329 }
00330 
00331 vnl_matrix<double>
00332 rgrl_transformation::
00333 jacobian( vnl_vector<double> const& from_loc ) const
00334 {
00335   vnl_matrix<double> jac;
00336   this->jacobian_wrt_loc( jac, from_loc );
00337 
00338   return jac;
00339 }
00340 
00341 //:  set parameter covariance matrix
00342 void
00343 rgrl_transformation::
00344 set_covar( const vnl_matrix<double>& covar )
00345 {
00346   covar_ = covar;
00347   if ( covar.rows() > 0 && covar.cols() > 0 ) {
00348     is_covar_set_ = true;
00349   }
00350 }
00351 
00352 //: set scaling factors
00353 //  Unless the transformation is not estimated using estimators in rgrl,
00354 //  it does not need to be set explicitly
00355 void
00356 rgrl_transformation::
00357 set_scaling_factors( vnl_vector<double> const& scaling )
00358 {
00359   // checking scaling
00360   // set it to epsilon if scaling is in fact zero
00361   for ( unsigned int i=0; i<scaling.size(); ++i )
00362     assert( vnl_math_isfinite( scaling[i] ) );
00363 
00364   scaling_factors_ = scaling;
00365 }
00366 
00367 
00368 vcl_ostream&
00369 operator<< (vcl_ostream& os, rgrl_transformation const& xform )
00370 {
00371   xform.write( os );
00372   return os;
00373 }
00374 
00375 //: output transformation
00376 void
00377 rgrl_transformation::
00378 write( vcl_ostream& os ) const
00379 {
00380   if ( is_covar_set() )
00381   {
00382     // write covariance
00383     os << "COVARIANCE\n"
00384        << covar_.rows() << ' ' << covar_.cols() << vcl_endl
00385        << covar_ << vcl_endl;
00386   }
00387 
00388   if ( scaling_factors_.size() )
00389   {
00390     // write scaling factors
00391     os << "SCALING_FACTORS\n"
00392        << scaling_factors_.size() << vcl_endl
00393        << scaling_factors_ << vcl_endl;
00394   }
00395 }
00396 
00397 //: input transformation
00398 bool
00399 rgrl_transformation::
00400 read( vcl_istream& is )
00401 {
00402   vcl_streampos pos;
00403   vcl_string tag_str;
00404 
00405   // skip any empty lines
00406   rgrl_util_skip_empty_lines( is );
00407   if ( !is.good() )
00408     return true;   // reach the end of stream
00409 
00410   tag_str="";
00411   pos = is.tellg();
00412   vcl_getline( is, tag_str );
00413 
00414   if ( tag_str.find("COVARIANCE") == 0 )
00415   {
00416     // read in covariance matrix
00417     int m=-1, n=-1;
00418     vnl_matrix<double> cov;
00419 
00420     // get dimension
00421     is >> m >> n;
00422     if ( !is || m<=0 || n<=0 )
00423       return false;   // cannot get the dimension
00424 
00425     cov.set_size(m, n);
00426     cov.fill(0.0);
00427     is >> cov;
00428 
00429     if ( !is.good() )
00430       return false;  // cannot read the covariance matrix
00431 
00432     this->set_covar( cov );
00433 
00434     // read in the next tag
00435     // skip any empty lines
00436     rgrl_util_skip_empty_lines( is );
00437     if ( is.eof() )
00438       return true;   // reach the end of stream
00439 
00440     tag_str="";
00441     pos = is.tellg();
00442     vcl_getline( is, tag_str );
00443   }
00444 
00445   if ( tag_str.find("SCALING_FACTORS") == 0 )
00446   {
00447     // read in scaling factors
00448     int m=-1;
00449 
00450     // get dimension
00451     is >> m;
00452 
00453     if ( !is || m<=0 )
00454       return false;  // cannot get dimension
00455 
00456     scaling_factors_.set_size( m );
00457     is >> scaling_factors_;
00458 
00459     return is.good();
00460   }
00461 
00462   // reset the stream pos
00463   is.seekg( pos );
00464   return is.good();
00465 }
00466 
00467 
00468 class inverse_mapping_func
00469 : public vnl_least_squares_function
00470 {
00471  public:
00472   inverse_mapping_func( rgrl_transformation const* xform_ptr, vnl_vector<double> const& to_loc )
00473     : vnl_least_squares_function( to_loc.size(), to_loc.size(), use_gradient ),
00474       xform_( xform_ptr ), to_loc_( to_loc )
00475   {}
00476 
00477   //: obj func value
00478   void f(vnl_vector<double> const& x, vnl_vector<double>& fx);
00479 
00480   //: Jacobian
00481   void gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian);
00482 
00483  public:
00484   const rgrl_transformation* xform_;
00485   vnl_vector<double>   to_loc_;
00486 };
00487 
00488 
00489 void
00490 inverse_mapping_func::
00491 f(vnl_vector<double> const& x, vnl_vector<double>& fx)
00492 {
00493   assert( xform_ );
00494 
00495   // x is the From location
00496   // just need to apply forward transformation and
00497   xform_->map_location( x, fx );
00498 
00499   // get the difference
00500   fx -= to_loc_;
00501 }
00502 
00503 void
00504 inverse_mapping_func::
00505 gradf(vnl_vector<double> const& x, vnl_matrix<double>& jacobian)
00506 {
00507   assert( xform_ );
00508 
00509   // jacobian is just the jacobian of transformation w.r.t location
00510   // x is the From location
00511   xform_->jacobian_wrt_loc( jacobian, x );
00512 }
00513 
00514 //:  Inverse map with an initial guess
00515 void
00516 rgrl_transformation::
00517 inv_map( const vnl_vector<double>& to,
00518          bool initialize_next,
00519          const vnl_vector<double>& /*to_delta*/, // FIXME: unused
00520          vnl_vector<double>& from,
00521          vnl_vector<double>& from_next_est) const
00522 {
00523   // use different objects for different dimension
00524   if ( to.size() == 2 )
00525   {
00526     static inverse_mapping_func inv_map_func( this, to );
00527 
00528     // set transformation and the desired
00529     inv_map_func.to_loc_ = to;
00530     inv_map_func.xform_  = this;
00531 
00532     // solve for from location
00533     static vnl_levenberg_marquardt lm( inv_map_func );
00534 
00535     // lm.set_trace( true );
00536     // lm.set_check_derivatives( 5 );
00537     // we don't need it to be super accurate
00538     lm.set_f_tolerance( 1e-4 );
00539     lm.set_x_tolerance( 1e-3 );
00540     lm.set_max_function_evals( 100 );
00541 
00542     // run LM
00543     bool ret = lm.minimize_using_gradient( from );
00544 
00545     if ( !ret ) {
00546       WarningMacro( "Levenberg-Marquardt in rgrl_transformation::inv_map has failed!!!" );
00547       return;
00548     }
00549   }
00550   else if ( to.size() == 3 )
00551   {
00552     static inverse_mapping_func inv_map_func( this, to );
00553 
00554     // set transformation and the desired
00555     inv_map_func.to_loc_ = to;
00556     inv_map_func.xform_  = this;
00557 
00558     // solve for from location
00559     static vnl_levenberg_marquardt lm( inv_map_func );
00560 
00561     // lm.set_trace( true );
00562     // lm.set_check_derivatives( 5 );
00563     // we don't need it to be super accurate
00564     lm.set_f_tolerance( 1e-4 );
00565     lm.set_x_tolerance( 1e-3 );
00566     lm.set_max_function_evals( 100 );
00567 
00568     // run LM
00569     bool ret = lm.minimize_using_gradient( from );
00570 
00571     if ( !ret ) {
00572       WarningMacro( "Levenberg-Marquardt in rgrl_transformation::inv_map has failed!!!" );
00573       return;
00574     }
00575   }
00576   else
00577   {
00578     assert( ! "Other dimensioin is not implemented!" );
00579     return;
00580   }
00581 
00582   // initialize the next
00583   // NOTE:
00584   // no need to compute the inverse of jacobian here
00585   // because the inverse using SVD is about expensive as LM.
00586   // and there is extra memory allocation
00587   // Therefore, just initialize it as current from
00588   //
00589   if ( initialize_next ) {
00590     from_next_est = from;
00591   }
00592 }
00593 
00594 //:  Inverse map based on the transformation.
00595 //   This function only exist for certain transformations.
00596 void
00597 rgrl_transformation::
00598 inv_map( const vnl_vector<double>& to,
00599          vnl_vector<double>& from ) const
00600 {
00601   const bool initialize_next = false;
00602   vnl_vector<double> unused;
00603   inv_map( to, initialize_next, unused, from, unused );
00604 }
00605 
00606 //: Return an inverse transformation
00607 //  This function only exist for certain transformations.
00608 rgrl_transformation_sptr
00609 rgrl_transformation::
00610 inverse_transform() const
00611 {
00612   assert( !"Should never reach rgrl_transformation::inverse_transform()" );
00613   return 0;
00614 }
00615