contrib/mul/vpdfl/vpdfl_pc_gaussian.cxx
Go to the documentation of this file.
00001 // This is mul/vpdfl/vpdfl_pc_gaussian.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 // \file
00007 // \brief Implementation of Multi-variate principal Component Gaussian PDF.
00008 // \author Tim Cootes
00009 // \date 21-Jul-2000
00010 //
00011 // \verbatim
00012 //  Modifications
00013 //    IMS   Converted to VXL 23 April 2000
00014 // \endverbatim
00015 
00016 #include "vpdfl_pc_gaussian.h"
00017 #include <vcl_string.h>
00018 #include <vcl_cstdlib.h> // for vcl_abort()
00019 #include <vcl_cassert.h>
00020 #include <vsl/vsl_binary_loader.h>
00021 #include <vsl/vsl_indent.h>
00022 #include <vnl/vnl_math.h>
00023 #include <mbl/mbl_matxvec.h>
00024 #include <vpdfl/vpdfl_pc_gaussian_builder.h>
00025 #include <vpdfl/vpdfl_gaussian_sampler.h>
00026 #include <vpdfl/vpdfl_gaussian.h>
00027 
00028 //=======================================================================
00029 
00030 //: Dflt ctor
00031 vpdfl_pc_gaussian::vpdfl_pc_gaussian()
00032   : partition_(0), log_k_principal_(0.0), partition_chooser_(0)
00033 {
00034 }
00035 //=======================================================================
00036 
00037 //: Destructor
00038 vpdfl_pc_gaussian::~vpdfl_pc_gaussian()
00039 {
00040   delete partition_chooser_;
00041 }
00042 
00043 //=======================================================================
00044 
00045 //: Calculate the log probability density at position x.
00046 // You could use vpdfl_gaussian::log_p() which would give the same answer,
00047 // but this method, only rotates into the principal components, not the entire rotated space,
00048 // so saving considerable time.
00049 double vpdfl_pc_gaussian::log_p(const vnl_vector<double>& x) const
00050 {
00051   unsigned int m = n_principal_components();
00052   unsigned int n = n_dims();
00053   assert(x.size() == n);
00054 
00055   if (m+1>=n) // it is probably not worth the speed up unless we avoid calculating more than one basis vector.
00056     return vpdfl_gaussian::log_p(x);
00057 
00058   double mahalDIFS, euclidDFFS;
00059   get_distances(mahalDIFS, euclidDFFS, x);
00060 
00061   return log_k() - log_k_principal() -euclidDFFS/(2 * (eigenvals()(m+1))) - mahalDIFS;
00062 }
00063 
00064 //=======================================================================
00065 
00066 //: Return Mahalanobis and Euclidean distances from centroid to input.
00067 // Strictly it is the normalised Mahalanobis distance (-log_p()) from the input projected into the
00068 // principal space to the centroid, and the Euclidean distance from the input
00069 // to the input projected into the principal space.
00070 // Also, the two values are the squares of the distances.
00071 void vpdfl_pc_gaussian::get_distances(double &mahalDIFS, double &euclidDFFS, const vnl_vector<double>& x) const
00072 {
00073   const unsigned int m = n_principal_components();
00074   const unsigned int n = n_dims();
00075 
00076   assert(x.size() == n);
00077 
00078   dx_ = x;
00079   dx_ -= mean();
00080 
00081   if (b_.size()!=m) b_.set_size(m);
00082 
00083 
00084   // Rotate dx_ into co-ordinate frame of axes of Gaussian
00085   // b_ = dx_' * P
00086   // This function will only use the first b_.size() columns of eigenvecs();
00087   mbl_matxvec_prod_vm(dx_, eigenvecs(), b_);
00088 
00089   const double* b_data = b_.data_block();
00090   const double* v_data = eigenvals().data_block();
00091 
00092   double sum=0.0;
00093 
00094   int i=m;
00095   double db, sumBSq=0.0;
00096   while (i--)
00097   {
00098     db = b_data[i];
00099     sum+=(db*db)/v_data[i];
00100     sumBSq+=(db*db);
00101   }
00102 
00103   mahalDIFS = - log_k_principal() + 0.5*sum;
00104   if ( n!=m)
00105   {
00106     i=n;
00107     const double* dx_data = dx_.data_block() ;
00108     double ddx, sumDxSq=0.0;
00109     while (i--)
00110     {
00111       ddx = dx_data[i];
00112       sumDxSq+=(ddx*ddx);
00113     }
00114 
00115     //By Pythagoras sum(squares(b_i)) + sum(squares(c_i)) = sum(squares(d_i)),
00116     // where b_ is d_ projected into the principal component space
00117     // and c_ is d_ projected into the complementary component space
00118     // because b_ and c_ are perpendicular.
00119     euclidDFFS = sumDxSq - sumBSq;
00120     if (euclidDFFS < 0) euclidDFFS = 0;
00121   }
00122   else
00123     euclidDFFS = 0.0;
00124 }
00125 
00126 
00127 //: Pre-calculate the constant needed when evaluating Mahalanobis Distance
00128 void vpdfl_pc_gaussian::calcPartLogK()
00129 {
00130   const double *v_data = eigenvals().data_block();
00131   double log_v_sum = 0.0;
00132   const unsigned& n = partition_;
00133 
00134   for (unsigned int i=0;i<n;i++) log_v_sum+=vcl_log(v_data[i]);
00135 
00136   log_k_principal_ = -0.5 * (n*vcl_log(2 * vnl_math::pi) + log_v_sum);
00137 }
00138 
00139 
00140 //: Initialise safely
00141 // The partition between principal components space and complementary space is
00142 // defined by the length of the Eigenvalues vector (evals.)
00143 // Calculates the variance, and checks that
00144 // the Eigenvalues are ordered and the Eigenvectors are unit normal
00145 // Turn off assertions to remove error checking.
00146 void vpdfl_pc_gaussian::set(const vnl_vector<double>& mean,
00147                             const vnl_matrix<double>& evecs,
00148                             const vnl_vector<double>& evals,
00149                             double complementEVal)
00150 {
00151   partition_ = evals.size();
00152   // The partition from full covariance to spherical must be between 0 and the total number of dimensions
00153   assert (partition_ <= evecs.cols());
00154   // The Eigenvector matrix should be square (and full rank but we don't test for that)
00155   assert (evecs.cols() == evecs.rows());
00156 
00157   unsigned int n = evecs.cols();
00158 
00159   vnl_vector<double> allEVals(n);
00160 
00161   // Fill in the complementary space Eigenvalues
00162   for (unsigned int i = 0; i < partition_; i++)
00163   {
00164     allEVals(i) = evals(i);
00165   }
00166   for (unsigned int i = partition_; i < n; i++)
00167   {
00168     allEVals(i) = complementEVal;
00169   }
00170   vpdfl_gaussian::set(mean, evecs, allEVals);
00171 
00172   calcPartLogK();
00173 }
00174 
00175 //=======================================================================
00176 
00177 //: Initialise safely as you would a vpdfl_gaussian.
00178 // Calculates the variance, and checks that
00179 // the Eigenvalues are ordered and the Eigenvectors are unit normal
00180 // Turn off assertions to remove error checking.
00181 void vpdfl_pc_gaussian::set(const vnl_vector<double>& mean,  const vnl_matrix<double>& evecs, const vnl_vector<double>& evals)
00182 {
00183 #ifndef NDEBUG
00184   if (!partition_chooser_)
00185   {
00186     vcl_cerr << "ERROR: vpdfl_pc_gaussian::set()\nUsing this function requires"
00187              << " partition_chooser_ to be set to a real builder\n\n";
00188     vcl_abort();
00189   }
00190 #endif
00191 
00192   int n_principal_components = partition_chooser_->decide_partition(evals);
00193   int n = mean.size();
00194   vnl_vector<double> principalEVals(n_principal_components);
00195 
00196   // Apply threshold to variance
00197   for (int i=0;i<n_principal_components;++i)
00198     principalEVals(i)=evals(i);
00199 
00200   double eVsum = 0.0; // The sum of the complementary space eigenvalues.
00201   for (int i=n_principal_components; i < n; i++)
00202     eVsum += evals(i);
00203 
00204     // The Eigenvalue of the complementary space basis vectors
00205   double complementaryEVals = eVsum / (n - n_principal_components);
00206 
00207   set(mean, evecs, principalEVals, complementaryEVals);
00208 }
00209 
00210 //=======================================================================
00211 
00212 //: Return instance of this PDF
00213 vpdfl_sampler_base* vpdfl_pc_gaussian::sampler() const
00214 {
00215   vpdfl_gaussian_sampler *i = new vpdfl_gaussian_sampler;
00216   i->set_model(*this);
00217   return i;
00218 }
00219 
00220 //=======================================================================
00221 
00222 vcl_string vpdfl_pc_gaussian::is_a() const
00223 {
00224   return vcl_string("vpdfl_pc_gaussian");
00225 }
00226 
00227 //=======================================================================
00228 
00229 bool vpdfl_pc_gaussian::is_class(vcl_string const& s) const
00230 {
00231   return vpdfl_gaussian::is_class(s) || s==vpdfl_pc_gaussian::is_a();
00232 }
00233 
00234 //=======================================================================
00235 
00236 short vpdfl_pc_gaussian::version_no() const
00237 {
00238   return 2;
00239 }
00240 
00241 //=======================================================================
00242 
00243 vpdfl_pdf_base* vpdfl_pc_gaussian::clone() const
00244 {
00245   return new vpdfl_pc_gaussian(*this);
00246 }
00247 
00248 //=======================================================================
00249 
00250 void vpdfl_pc_gaussian::print_summary(vcl_ostream& os) const
00251 {
00252   os << '\n' << vsl_indent() << "Partition at: " << partition_
00253      << "  Log(k) for principal space: "<< log_k_principal_ << '\n'
00254      << vsl_indent() <<  "Partition Chooser: " ;
00255   if (partition_chooser_) os << *partition_chooser_ << '\n';
00256   else os << "NULL\n";
00257   os << vsl_indent();
00258   vpdfl_gaussian::print_summary(os);
00259 }
00260 
00261 //=======================================================================
00262 
00263 void vpdfl_pc_gaussian::b_write(vsl_b_ostream& bfs) const
00264 {
00265   vsl_b_write(bfs,is_a());
00266   vsl_b_write(bfs,version_no());
00267   vpdfl_gaussian::b_write(bfs);
00268   vsl_b_write(bfs,partition_);
00269   vsl_b_write(bfs,log_k_principal_);
00270   vsl_b_write(bfs,static_cast<vpdfl_builder_base *>(partition_chooser_));
00271 }
00272 
00273 //=======================================================================
00274 
00275 void vpdfl_pc_gaussian::b_read(vsl_b_istream& bfs)
00276 {
00277   if (!bfs) return;
00278 
00279   vcl_string name;
00280   vsl_b_read(bfs,name);
00281   if (name != is_a())
00282   {
00283     vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&, vpdfl_pc_gaussian &)\n"
00284              << "           Attempted to load object of type "
00285              << name <<" into object of type " << is_a() << vcl_endl;
00286     bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00287     return;
00288   }
00289 
00290   short version;
00291   vsl_b_read(bfs,version);
00292   switch (version)
00293   {
00294     case (1):
00295       vpdfl_gaussian::b_read(bfs);
00296       vsl_b_read(bfs,partition_);
00297       vsl_b_read(bfs,log_k_principal_);
00298       partition_chooser_ = 0;
00299       break;
00300     case (2):
00301       vpdfl_gaussian::b_read(bfs);
00302       vsl_b_read(bfs,partition_);
00303       vsl_b_read(bfs,log_k_principal_);
00304       {
00305         vpdfl_builder_base * c = 0;
00306         vsl_b_read(bfs,c);
00307         assert(!c || c->is_class("vpdfl_pc_gaussian_builder"));
00308         partition_chooser_ = static_cast<vpdfl_pc_gaussian_builder *>(c);
00309       }
00310       break;
00311     default:
00312       vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&, vpdfl_pc_gaussian &)\n"
00313                << "           Unknown version number "<< version << vcl_endl;
00314       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00315       return;
00316   }
00317 }
00318 
00319 //=======================================================================
00320 
00321 const vpdfl_pc_gaussian_builder * vpdfl_pc_gaussian::partition_chooser() const
00322 {
00323   return partition_chooser_;
00324 }
00325 
00326 //=======================================================================
00327 
00328 void vpdfl_pc_gaussian::set_partition_chooser(
00329   const vpdfl_pc_gaussian_builder * partition_chooser)
00330 {
00331   delete partition_chooser_;
00332   if (partition_chooser)
00333     partition_chooser_ = static_cast<vpdfl_pc_gaussian_builder *>(partition_chooser->clone());
00334   else
00335     partition_chooser_ = 0;
00336 }