contrib/mul/mfpf/mfpf_lin_clsfy_finder_builder.cxx
Go to the documentation of this file.
00001 #include "mfpf_lin_clsfy_finder_builder.h"
00002 //:
00003 // \file
00004 // \brief Builds mfpf_region_finder objects which use a linear classifier
00005 // \author Tim Cootes
00006 
00007 #include <mfpf/mfpf_region_finder.h>
00008 #include <vsl/vsl_binary_loader.h>
00009 #include <vul/vul_string.h>
00010 #include <vcl_cmath.h>
00011 #include <vcl_sstream.h>
00012 #include <vcl_cassert.h>
00013 
00014 #include <mbl/mbl_parse_block.h>
00015 #include <mbl/mbl_read_props.h>
00016 #include <mbl/mbl_exception.h>
00017 
00018 #include <vil/vil_resample_bilin.h>
00019 #include <vsl/vsl_vector_io.h>
00020 #include <vsl/vsl_indent.h>
00021 
00022 #include <vgl/vgl_point_2d.h>
00023 #include <vgl/vgl_vector_2d.h>
00024 #include <mfpf/mfpf_sample_region.h>
00025 #include <mfpf/mfpf_norm_vec.h>
00026 #include <mfpf/mfpf_log_lin_class_cost.h>
00027 
00028 #include <clsfy/clsfy_binary_hyperplane.h>
00029 #include <clsfy/clsfy_binary_hyperplane_logit_builder.h>
00030 #include <mbl/mbl_data_array_wrapper.h>
00031 
00032 //=======================================================================
00033 // Dflt ctor
00034 //=======================================================================
00035 
00036 mfpf_lin_clsfy_finder_builder::mfpf_lin_clsfy_finder_builder()
00037 {
00038   set_defaults();
00039 }
00040 
00041 //: Define default values
00042 void mfpf_lin_clsfy_finder_builder::set_defaults()
00043 {
00044   r1_=1.8;
00045   r2_=2.9;
00046   r3_=7.1;
00047   step_size_=1.0;
00048   search_ni_=5;
00049   search_nj_=5;
00050   n_pixels_=0;
00051   roi_.resize(0);
00052   roi_ni_=0;
00053   roi_nj_=0;
00054   ref_x_=0;
00055   ref_y_=0;
00056   nA_=0;
00057   dA_=0.0;
00058   norm_method_=1;
00059   overlap_f_=1.0;
00060   var_min_ = 1.0E-6;
00061   tvar_min_=1.0E30;
00062   num_examples_=0;
00063   estimate_var_min_=true;
00064 }
00065 
00066 //=======================================================================
00067 // Destructor
00068 //=======================================================================
00069 
00070 mfpf_lin_clsfy_finder_builder::~mfpf_lin_clsfy_finder_builder()
00071 {
00072 }
00073 
00074 //: Create new mfpf_region_finder on heap
00075 mfpf_point_finder* mfpf_lin_clsfy_finder_builder::new_finder() const
00076 {
00077   return new mfpf_region_finder();
00078 }
00079 
00080 //: Define model region using description in form
00081 //  Assumes form defined in world-coords.
00082 //  Assumes step_size() pixel units (ie dimensions
00083 //  are divided by step_size() to map to reference frame).
00084 void mfpf_lin_clsfy_finder_builder::set_region(const mfpf_region_form& form)
00085 {
00086   step_size_ = form.pose().scale();
00087 
00088   if (form.form()=="box")
00089   {
00090     int ni = vcl_max(1,int(0.99+form.wi()));
00091     int nj = vcl_max(1,int(0.99+form.wj()));
00092     set_as_box(unsigned(ni),unsigned(nj),0.5*ni,0.5*nj);
00093   }
00094   else
00095   if (form.form()=="ellipse")
00096   {
00097     double ri = vcl_max(1.0,form.wi());
00098     double rj = vcl_max(1.0,form.wj());
00099     set_as_ellipse(ri,rj);
00100   }
00101   else
00102   {
00103     vcl_cerr<<"mfpf_lin_clsfy_finder_builder::set_region : Unknown form: "<<form<<vcl_endl;
00104     vcl_abort();
00105   }
00106 }
00107 
00108 //: Define region size in world co-ordinates
00109 //  Sets up ROI to cover given box (with samples at step_size()),
00110 //  with ref point at centre.
00111 void mfpf_lin_clsfy_finder_builder::set_region_size(double wi, double wj)
00112 {
00113   wi/=step_size();
00114   wj/=step_size();
00115   int ni = vcl_max(1,int(0.99+wi));
00116   int nj = vcl_max(1,int(0.99+wj));
00117   set_as_box(unsigned(ni),unsigned(nj),0.5*(ni-1),0.5*(nj-1));
00118 }
00119 
00120 
00121 //: Define model region as an ni x nj box
00122 void mfpf_lin_clsfy_finder_builder::set_as_box(unsigned ni, unsigned nj)
00123 {
00124   set_as_box(ni,nj,0.5*(ni-1),0.5*(nj-1));
00125 }
00126 
00127 //: Define model region as an ni x nj box
00128 void mfpf_lin_clsfy_finder_builder::set_as_box(unsigned ni, unsigned nj,
00129                                                double ref_x, double ref_y)
00130 {
00131   roi_ni_=ni; roi_nj_=nj;
00132   n_pixels_ = ni*nj;
00133 
00134   // Set ROI to be a box
00135   roi_.resize(nj);
00136   for (unsigned j=0;j<nj;++j) roi_[j]=mbl_chord(0,ni-1,j);
00137 
00138   ref_x_=ref_x;
00139   ref_y_=ref_y;
00140 }
00141 
00142 //: Define model region as an ellipse with radii ri, rj
00143 //  Ref. point in centre.
00144 void mfpf_lin_clsfy_finder_builder::set_as_ellipse(double ri, double rj)
00145 {
00146   ri+=1e-6; rj+=1e-6;
00147   int ni=int(ri);
00148   int nj=int(rj);
00149   roi_.resize(0);
00150   n_pixels_=0;
00151   for (int j = -nj;j<=nj;++j)
00152   {
00153     // Find start and end of line of pixels inside disk
00154     int x = int(ri*vcl_sqrt(1.0-j*j/(rj*rj)));
00155     roi_.push_back(mbl_chord(ni-x,ni+x,nj+j));
00156     n_pixels_+=2*x+1;
00157   }
00158 
00159   ref_x_=ni;
00160   ref_y_=nj;
00161   roi_ni_=2*ni+1;
00162   roi_nj_=2*nj+1;
00163 }
00164 
00165 //: Which normalisation to use (0=none, 1=linear)
00166 void mfpf_lin_clsfy_finder_builder::set_norm_method(short norm_method)
00167 {
00168   norm_method_=norm_method;
00169 }
00170 
00171 //: Number of dimensions in the model
00172 unsigned mfpf_lin_clsfy_finder_builder::model_dim()
00173 {
00174   return n_pixels_;
00175 }
00176 
00177 //: Initialise building
00178 // Must be called before any calls to add_example(...)
00179 void mfpf_lin_clsfy_finder_builder::clear(unsigned n_egs)
00180 {
00181   num_examples_=0;
00182 }
00183 
00184 //: Add one example to the model
00185 //  Sample regions in a grid centred on supplied point.
00186 //  Record those close to the centre as positive examples, and those
00187 //  further away as negative examples.
00188 void mfpf_lin_clsfy_finder_builder::add_one_example(
00189                  const vimt_image_2d_of<float>& image,
00190                  const vgl_point_2d<double>& p,
00191                  const vgl_vector_2d<double>& u)
00192 {
00193   // Determine axes
00194   vgl_vector_2d<double> u1=step_size_*u;
00195   vgl_vector_2d<double> v1(-u1.y(),u1.x());
00196 
00197   // Compute size of region to sample
00198   int r = int(r3_+1.0);
00199   int nsi = 2*r + roi_ni_;
00200   int nsj = 2*r + roi_nj_;
00201 
00202 
00203   unsigned np=image.image().nplanes();
00204   // Set up sample area with interleaved planes (ie planestep==1)
00205   vil_image_view<float> sample(nsi,nsj,1,np);
00206 
00207   const vgl_point_2d<double> p0 = p-(r+ref_x_)*u1-(r+ref_y_)*v1;
00208 
00209   const vimt_transform_2d& s_w2i = image.world2im();
00210   vgl_point_2d<double> im_p0 = s_w2i(p0);
00211   vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
00212   vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
00213 
00214   vil_resample_bilin(image.image(),sample,
00215                      im_p0.x(),im_p0.y(),  im_u.x(),im_u.y(),
00216                      im_v.x(),im_v.y(),nsi,nsj);
00217 
00218   vnl_vector<double> v(n_pixels_*sample.nplanes());
00219 
00220   const float* s = sample.top_left_ptr();
00221   vcl_ptrdiff_t s_istep = sample.istep();
00222   vcl_ptrdiff_t s_jstep = sample.jstep();
00223 
00224   for (int j=-r;j<=r;++j,s+=s_jstep)
00225   {
00226     const float *sp = s;
00227     for (int i=-r;i<=r;++i,sp+=s_istep)
00228     {
00229       double d = vcl_sqrt(static_cast<double>(i*i+j*j));
00230       if (d<=r3_)
00231       {
00232         mfpf_sample_region(sp,sample.jstep(),np,roi_,v);
00233         if (norm_method_==1)
00234         {
00235             double var=var_min_;
00236             mfpf_norm_vec(v,var_min_,&var);
00237             if (var<tvar_min_) tvar_min_ = var;
00238         }
00239 
00240         if (d<=r1_)
00241         {
00242           samples_.push_back(v); class_id_.push_back(1);  // Positive example
00243         }
00244         else
00245         if (d>=r2_)
00246         {
00247           samples_.push_back(v); class_id_.push_back(0);  // Negative example
00248         }
00249       }
00250     }
00251   }
00252 
00253   ++num_examples_;
00254 }
00255 
00256 //: Add one example to the model
00257 void mfpf_lin_clsfy_finder_builder::add_example(const vimt_image_2d_of<float>& image,
00258                                                 const vgl_point_2d<double>& p,
00259                                                 const vgl_vector_2d<double>& u)
00260 {
00261   if (nA_==0)
00262   {
00263     add_one_example(image,p,u);
00264     return;
00265   }
00266 
00267   vgl_vector_2d<double> v(-u.y(),u.x());
00268   for (int iA=-int(nA_);iA<=(int)nA_;++iA)
00269   {
00270     double A = iA*dA_;
00271     vgl_vector_2d<double> uA = u*vcl_cos(A)+v*vcl_sin(A);
00272     add_one_example(image,p,uA);
00273   }
00274 }
00275 
00276 //: Build this object from the data supplied in add_example()
00277 void mfpf_lin_clsfy_finder_builder::build(mfpf_point_finder& pf)
00278 {
00279   assert(pf.is_a()=="mfpf_region_finder");
00280   mfpf_region_finder& rp = static_cast<mfpf_region_finder&>(pf);
00281 
00282   double min_p = 0.001;  // Smallest prob to be returned by classifier
00283   mbl_data_array_wrapper<vnl_vector<double> > sample_data(samples_);
00284   clsfy_binary_hyperplane_logit_builder builder;
00285   builder.set_alpha(1e-6);
00286   builder.set_min_p(min_p);
00287   clsfy_binary_hyperplane classifier;
00288   double mean_error = builder.build(classifier,sample_data,class_id_);
00289   vcl_cout<<"Classifier Mean Error: "<<mean_error<<vcl_endl;
00290 
00291   mfpf_log_lin_class_cost log_lin_cost;
00292   log_lin_cost.set(classifier.weights(),classifier.bias(),min_p);
00293   rp.set(roi_,ref_x_,ref_y_,log_lin_cost,norm_method_);
00294   set_base_parameters(rp);
00295   rp.set_overlap_f(overlap_f_);
00296 
00297   if (estimate_var_min_ && norm_method_==1 && num_examples_>0)
00298   {
00299     //Assume applied var_min is r* min in training set, where r->1 as n->infinity
00300     //Set r=0.98 for n around 50
00301     double dn=double(num_examples_);
00302     if (dn>0.0)
00303     {
00304       double r=0.925; //so r attains 0.98 around n=50
00305       double alpha=1.0;
00306       if (dn>50.0)
00307         alpha=0.98;
00308       else
00309       {
00310         alpha=1-vcl_pow(r,dn);
00311         alpha=vcl_min(alpha,0.98);
00312       }
00313       tvar_min_ *= alpha;
00314       var_min_ = vcl_max(var_min_,tvar_min_);
00315     }
00316     rp.set_var_min(var_min_);
00317   }
00318 }
00319 
00320 //: Parse stream to set up as a box shape.
00321 // Expects: "{ ni: 3 nj: 5 ref_x: 1.0 ref_y: 2.0 }
00322 void mfpf_lin_clsfy_finder_builder::config_as_box(vcl_istream &is)
00323 {
00324   // Cycle through string and produce a map of properties
00325   vcl_string s = mbl_parse_block(is);
00326   vcl_istringstream ss(s);
00327   mbl_read_props_type props = mbl_read_props_ws(ss);
00328 
00329   unsigned ni=5,nj=5;
00330 
00331   if (props.find("ni")!=props.end())
00332   {
00333     ni=vul_string_atoi(props["ni"]);
00334     props.erase("ni");
00335   }
00336   if (props.find("nj")!=props.end())
00337   {
00338     nj=vul_string_atoi(props["nj"]);
00339     props.erase("nj");
00340   }
00341 
00342   if (props.find("ref_x")!=props.end())
00343   {
00344     ref_x_=vul_string_atof(props["ref_x"]);
00345     props.erase("ref_x");
00346   }
00347   else ref_x_=0.5*(ni-1.0);
00348 
00349   if (props.find("ref_y")!=props.end())
00350   {
00351     ref_y_=vul_string_atof(props["ref_y"]);
00352     props.erase("ref_y");
00353   }
00354   else ref_y_=0.5*(nj-1.0);
00355 
00356   // Check for unused props
00357   mbl_read_props_look_for_unused_props(
00358       "mfpf_lin_clsfy_finder_builder::config_as_box",
00359       props, mbl_read_props_type());
00360 
00361   set_as_box(ni,nj,ref_x_,ref_y_);
00362 }
00363 
00364 //: Parse stream to set up as an ellipse shape.
00365 // Expects: "{ ri: 2.1 rj: 5.2 }
00366 void mfpf_lin_clsfy_finder_builder::config_as_ellipse(vcl_istream &is)
00367 {
00368   // Cycle through string and produce a map of properties
00369   vcl_string s = mbl_parse_block(is);
00370   vcl_istringstream ss(s);
00371   mbl_read_props_type props = mbl_read_props_ws(ss);
00372 
00373   double ri=3.1,rj=3.1;
00374   if (props.find("ri")!=props.end())
00375   {
00376     ri=vul_string_atof(props["ri"]);
00377     props.erase("ri");
00378   }
00379 
00380   if (props.find("rj")!=props.end())
00381   {
00382     rj=vul_string_atof(props["rj"]);
00383     props.erase("rj");
00384   }
00385 
00386   // Check for unused props
00387   mbl_read_props_look_for_unused_props(
00388       "mfpf_lin_clsfy_finder_builder::config_as_ellipse",
00389       props, mbl_read_props_type());
00390 
00391   set_as_ellipse(ri,rj);
00392 }
00393 
00394 
00395 //=======================================================================
00396 // Method: set_from_stream
00397 //=======================================================================
00398 //: Initialise from a string stream
00399 bool mfpf_lin_clsfy_finder_builder::set_from_stream(vcl_istream &is)
00400 {
00401   // Cycle through string and produce a map of properties
00402   vcl_string s = mbl_parse_block(is);
00403   vcl_istringstream ss(s);
00404   mbl_read_props_type props = mbl_read_props_ws(ss);
00405 
00406   set_defaults();
00407 
00408   // Extract the properties
00409   parse_base_props(props);
00410 
00411   if (props.find("shape")!=props.end())
00412   {
00413     vcl_istringstream shape_s(props["shape"]);
00414     shape_s>>shape_;
00415     if (shape_=="box")
00416     {
00417       // Parse parameters after box
00418       config_as_box(shape_s);
00419     }
00420     else
00421     if (shape_=="ellipse")
00422     {
00423       // Parse parameters after ellipse
00424       config_as_ellipse(shape_s);
00425     }
00426     else throw mbl_exception_parse_error("Unknown shape: "+shape_);
00427 
00428     props.erase("shape");
00429   }
00430 
00431   if (props.find("norm")!=props.end())
00432   {
00433     if (props["norm"]=="none") norm_method_=0;
00434     else
00435     if (props["norm"]=="linear") norm_method_=1;
00436     else throw mbl_exception_parse_error("Unknown norm: "+props["norm"]);
00437 
00438     props.erase("norm");
00439   }
00440   if (props.find("estimate_var_min") !=props.end())
00441   {
00442     vcl_string strEstimate=props["estimate_var_min"];
00443     if (strEstimate[0]=='f' || strEstimate[0]=='F' || strEstimate[0]=='0')
00444         estimate_var_min_=false;
00445     else
00446         estimate_var_min_=true;
00447 
00448     props.erase("estimate_var_min");
00449   }
00450 
00451   overlap_f_=vul_string_atof(props.get_optional_property("overlap_f",
00452                                                          "1.0"));
00453   r1_=vul_string_atof(props.get_optional_property("r1","1.7"));
00454   r2_=vul_string_atof(props.get_optional_property("r2","2.9"));
00455   r3_=vul_string_atof(props.get_optional_property("r3","7.1"));
00456 
00457   if (props.find("nA")!=props.end())
00458   {
00459     nA_=vul_string_atoi(props["nA"]);
00460     props.erase("nA");
00461   }
00462 
00463   if (props.find("dA")!=props.end())
00464   {
00465     dA_=vul_string_atof(props["dA"]);
00466     props.erase("dA");
00467   }
00468 
00469   // Check for unused props
00470   mbl_read_props_look_for_unused_props(
00471       "mfpf_lin_clsfy_finder_builder::set_from_stream", props, mbl_read_props_type());
00472   return true;
00473 }
00474 
00475 //=======================================================================
00476 // Method: is_a
00477 //=======================================================================
00478 
00479 vcl_string mfpf_lin_clsfy_finder_builder::is_a() const
00480 {
00481   return vcl_string("mfpf_lin_clsfy_finder_builder");
00482 }
00483 
00484 //: Create a copy on the heap and return base class pointer
00485 mfpf_point_finder_builder* mfpf_lin_clsfy_finder_builder::clone() const
00486 {
00487   return new mfpf_lin_clsfy_finder_builder(*this);
00488 }
00489 
00490 //=======================================================================
00491 // Method: print
00492 //=======================================================================
00493 
00494 void mfpf_lin_clsfy_finder_builder::print_summary(vcl_ostream& os) const
00495 {
00496   os << "{ size: " << roi_ni_ << 'x' << roi_nj_
00497      << " n_pixels: " << n_pixels_
00498      << " ref_pt: (" << ref_x_ << ',' << ref_y_ << ')' <<'\n';
00499   vsl_indent_inc(os);
00500   if (norm_method_==0) os<<vsl_indent()<<"norm: none"<<'\n';
00501   else                 os<<vsl_indent()<<"norm: linear"<<'\n';
00502   os <<vsl_indent()<< "r1: "<<r1_<<" r2: "<<r2_<<" r3: "<<r3_<<'\n'
00503      <<vsl_indent()<< "nA: " << nA_ << " dA: " << dA_ << ' '<<'\n'
00504      <<vsl_indent();
00505   mfpf_point_finder_builder::print_summary(os);
00506   os <<'\n' <<vsl_indent()<<"overlap_f: "<<overlap_f_<<'\n';
00507   vsl_indent_dec(os);
00508   os <<vsl_indent()<< '}';
00509 }
00510 
00511 void mfpf_lin_clsfy_finder_builder::print_shape(vcl_ostream& os) const
00512 {
00513   vil_image_view<vxl_byte> im(roi_ni_,roi_nj_);
00514   im.fill(0);
00515   for (unsigned k=0;k<roi_.size();++k)
00516     for (int i=roi_[k].start_x();i<=roi_[k].end_x();++i)
00517       im(i,roi_[k].y())=1;
00518   for (unsigned j=0;j<im.nj();++j)
00519   {
00520     for (unsigned i=0;i<im.ni();++i)
00521       if (im(i,j)==0) os<<' ';
00522       else            os<<'X';
00523     os<<'\n';
00524   }
00525 }
00526 
00527 //: Version number for I/O
00528 short mfpf_lin_clsfy_finder_builder::version_no() const
00529 {
00530   return 1;
00531 }
00532 
00533 void mfpf_lin_clsfy_finder_builder::b_write(vsl_b_ostream& bfs) const
00534 {
00535   vsl_b_write(bfs,version_no());
00536   mfpf_point_finder_builder::b_write(bfs);  // Save base class
00537   vsl_b_write(bfs,roi_);
00538   vsl_b_write(bfs,roi_ni_);
00539   vsl_b_write(bfs,roi_nj_);
00540   vsl_b_write(bfs,n_pixels_);
00541   vsl_b_write(bfs,ref_x_);
00542   vsl_b_write(bfs,ref_y_);
00543   vsl_b_write(bfs,nA_);
00544   vsl_b_write(bfs,dA_);
00545   vsl_b_write(bfs,r1_);
00546   vsl_b_write(bfs,r2_);
00547   vsl_b_write(bfs,r3_);
00548   vsl_b_write(bfs,norm_method_);
00549   vsl_b_write(bfs,overlap_f_);
00550   vsl_b_write(bfs,var_min_);
00551   vsl_b_write(bfs,samples_);
00552   vsl_b_write(bfs,class_id_);
00553 }
00554 
00555 //=======================================================================
00556 // Method: load
00557 //=======================================================================
00558 
00559 void mfpf_lin_clsfy_finder_builder::b_read(vsl_b_istream& bfs)
00560 {
00561   if (!bfs) return;
00562   short version;
00563   vsl_b_read(bfs,version);
00564   switch (version)
00565   {
00566     case (1):
00567       mfpf_point_finder_builder::b_read(bfs);  // Load base class
00568       vsl_b_read(bfs,roi_);
00569       vsl_b_read(bfs,roi_ni_);
00570       vsl_b_read(bfs,roi_nj_);
00571       vsl_b_read(bfs,n_pixels_);
00572       vsl_b_read(bfs,ref_x_);
00573       vsl_b_read(bfs,ref_y_);
00574       vsl_b_read(bfs,nA_);
00575       vsl_b_read(bfs,dA_);
00576       vsl_b_read(bfs,r1_);
00577       vsl_b_read(bfs,r2_);
00578       vsl_b_read(bfs,r3_);
00579       vsl_b_read(bfs,norm_method_);
00580       vsl_b_read(bfs,overlap_f_);
00581       vsl_b_read(bfs,var_min_);
00582       vsl_b_read(bfs,samples_);
00583       vsl_b_read(bfs,class_id_);
00584       break;
00585     default:
00586       vcl_cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&)\n"
00587                << "           Unknown version number "<< version << vcl_endl;
00588       bfs.is().clear(vcl_ios::badbit); // Set an unrecoverable IO error on stream
00589       return;
00590   }
00591 }