contrib/mul/msm/msm_ellipsoid_limiter.cxx
Go to the documentation of this file.
00001 #include "msm_ellipsoid_limiter.h"
00002 //:
00003 // \file
00004 // \author Tim Cootes
00005 // \brief Force param.s to lie in ellipsoid defined by variances.
00006 
00007 #include <vnl/io/vnl_io_vector.h>
00008 #include <vsl/vsl_binary_loader.h>
00009 #include <mbl/mbl_parse_block.h>
00010 #include <mbl/mbl_read_props.h>
00011 #include <vul/vul_string.h>
00012 #include <vcl_sstream.h>
00013 #include <vcl_cassert.h>
00014 
00015 //=======================================================================
00016 msm_ellipsoid_limiter::msm_ellipsoid_limiter()
00017   : M_max_(3.0),accept_prop_(0.98)
00018 {
00019 }
00020 
00021 //: Define number of SDs to limit at
00022 void msm_ellipsoid_limiter::set_n_sds(double n_sds)
00023 {
00024   M_max_ = n_sds*n_sds;
00025 }
00026 
00027 //: Define variance on each parameter
00028 void msm_ellipsoid_limiter::set_param_var(const vnl_vector<double>& v)
00029 {
00030   mode_var_=v;
00031   set_acceptance(accept_prop_);
00032 }
00033 
00034 //: Set the limits so that a given proportion pass
00035 //  Where the parameters are described by a pdf, choose
00036 //  limits so that on average a proportion prop (in [0,1])
00037 //  are acceptable when using n_modes modes. If n_modes==0,
00038 //  then assume all available modes to be used.
00039 void msm_ellipsoid_limiter::set_acceptance(double prop,
00040                                            unsigned n_modes)
00041 {
00042   assert(prop>=0 && prop<=1.0);
00043   accept_prop_ = prop;
00044   if (n_modes==0) n_modes=mode_var_.size();
00045   M_max_ = msm_chi2_for_cum_prob(prop,n_modes);
00046 }
00047 
00048 //: Return square of Mahalanobis distance to origin
00049 double msm_ellipsoid_limiter::mahalanobis(const vnl_vector<double>& b) const
00050 {
00051   // Compute Mahalanobis distance
00052   double M=0.0;
00053   for (unsigned i=0;i<b.size();++i)
00054     M += b[i]*b[i]/mode_var_[i];
00055   return M;
00056 }
00057 
00058 //: Given initial b on ellipsoid, move on surface towards y
00059 //  Finds closest point to y in tangent plane at initial b
00060 //  Replaces b with normalised version of this.
00061 double msm_ellipsoid_limiter::slide_closer(vnl_vector<double>& b,
00062                                            const vnl_vector<double>& y) const
00063 {
00064   unsigned n=b.size();
00065   vnl_vector<double> u(n);  // u is normal vector at b
00066   for (unsigned i=0;i<n;++i) u[i]=b[i]/mode_var_[i];
00067 
00068   double s = (dot_product(u,y)-M_max_)/u.squared_magnitude();
00069 
00070   // Calculate point on tangent plane closest to y
00071   vnl_vector<double> b1 = y-s*u;
00072 
00073   // Scale it so that it is on the ellipsoid
00074   b1 *= vcl_sqrt(M_max_/mahalanobis(b1));
00075 
00076   double d2=vnl_vector_ssd(b,b1)/n;
00077 
00078   b=b1;
00079   return d2/mode_var_[n-1];  // Scaled relative to smallest variance
00080 }
00081 
00082 
00083 //: Apply limit to parameter vector b
00084 void msm_ellipsoid_limiter::apply_limit(vnl_vector<double>& b) const
00085 {
00086   // Compute Mahalanobis distance
00087   double M=mahalanobis(b);
00088 
00089   if (M<M_max_) return;  // Inside ellipsoid, so return.
00090 
00091   // b is outside ellipsoid, so need to move to closest point
00092   // on the ellipsoid.
00093 
00094   vnl_vector<double> b0=b;
00095 
00096   // Initial estimate: shrink towards origin so that M=M_max_
00097   b*=vcl_sqrt(M_max_/M);
00098 
00099   // Now slide around ellipsoid by moving to nearest point
00100   // to b0 on the tangent plane at current b, then normalising
00101   // onto the ellipsoid
00102 
00103   unsigned n_its=0;
00104   while (slide_closer(b,b0)>1e-6 && n_its<10) { n_its++; };
00105 }
00106 
00107 //=======================================================================
00108 //: Print class to os
00109 void msm_ellipsoid_limiter::print_summary(vcl_ostream& os) const
00110 {
00111   os<<" { M_max: "<<M_max_ <<" accept_prop: "<<accept_prop_<<" } ";
00112 }
00113 
00114 const static short version_no = 1;
00115 
00116 //: Save class to binary file stream
00117 void msm_ellipsoid_limiter::b_write(vsl_b_ostream& bfs) const
00118 {
00119   vsl_b_write(bfs,version_no);
00120   vsl_b_write(bfs,mode_var_);
00121   vsl_b_write(bfs,accept_prop_);
00122   vsl_b_write(bfs,M_max_);
00123 }
00124 
00125 
00126 //: Load class from binary file stream
00127 void msm_ellipsoid_limiter::b_read(vsl_b_istream& bfs)
00128 {
00129   short version;
00130   vsl_b_read(bfs,version);
00131   switch (version)
00132   {
00133     case (1):
00134       vsl_b_read(bfs,mode_var_);
00135       vsl_b_read(bfs,accept_prop_);
00136       vsl_b_read(bfs,M_max_);
00137       break;
00138     default:
00139       vcl_cerr << "msm_ellipsoid_limiter::b_read() :\n"
00140                << "Unexpected version number " << version << '\n';
00141       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00142       return;
00143   }
00144 }
00145 
00146 vcl_string msm_ellipsoid_limiter::is_a() const
00147 {
00148   return vcl_string("msm_ellipsoid_limiter");
00149 }
00150 
00151 //: Create a copy on the heap and return base class pointer
00152 msm_param_limiter* msm_ellipsoid_limiter::clone() const
00153 {
00154   return new msm_ellipsoid_limiter(*this);
00155 }
00156 
00157 //: Initialise from a text stream.
00158 // The default implementation is for attribute-less normalisers,
00159 // and throws if it finds any data in the stream.
00160 void msm_ellipsoid_limiter::config_from_stream(vcl_istream &is)
00161 {
00162   vcl_string s = mbl_parse_block(is);
00163 
00164   vcl_istringstream ss(s);
00165   mbl_read_props_type props = mbl_read_props_ws(ss);
00166 
00167   accept_prop_=vul_string_atof(props.get_optional_property("accept_prop","0.98"));
00168 
00169   mbl_read_props_look_for_unused_props(
00170       "msm_ellipsoid_limiter::config_from_stream", props, mbl_read_props_type());
00171 }
00172