contrib/brl/bseg/sdet/sdet_texture_classifier.cxx
Go to the documentation of this file.
00001 #include "sdet_texture_classifier.h"
00002 //
00003 #include <brip/brip_vil_float_ops.h>
00004 #include <brip/brip_filter_bank.h>
00005 #include <sdet/sdet_k_means.h>
00006 #include <vsl/vsl_map_io.h>
00007 #include <vsl/vsl_vector_io.h>
00008 #include <vsl/vsl_binary_io.h>
00009 #include <vnl/io/vnl_io_vector.h>
00010 #include <vnl/vnl_numeric_traits.h>
00011 #include <vnl/vnl_math.h>
00012 #include <vul/vul_timer.h>
00013 #include <vgl/vgl_polygon_scan_iterator.h>
00014 #include <vil/vil_math.h>
00015 #include <vil/vil_load.h>
00016 #include <vil/vil_save.h>
00017 #include <vil/vil_crop.h>
00018 #include <vbl/io/vbl_io_smart_ptr.h>
00019 #include <vsol/vsol_polygon_2d.h>
00020 #include <vsol/vsol_polygon_2d_sptr.h>
00021 #include <vsol/vsol_point_2d.h>
00022 #include <vsol/vsol_point_2d_sptr.h>
00023 #include <vsol/vsol_box_2d.h>
00024 #include <vsol/vsol_box_2d_sptr.h>
00025 #include <vcl_cstdlib.h> // for std::rand()
00026 #include <vcl_iostream.h>
00027 #include <vcl_algorithm.h>
00028 #include <vcl_cassert.h>
00029 
00030 static
00031 vcl_vector<vgl_polygon<double> > load_polys(vcl_string const& poly_path)
00032 {
00033   vcl_vector<vsol_spatial_object_2d_sptr> sos;
00034   vsl_b_ifstream istr(poly_path);
00035   if (!istr) {
00036     vcl_cout << "Failed to open input stream "
00037              << poly_path << vcl_endl;
00038     return vcl_vector<vgl_polygon<double> >();
00039   }
00040   vsl_b_read(istr, sos);
00041   if (!sos.size()) {
00042     vcl_cout << "no polys "
00043              << poly_path << vcl_endl;
00044     return vcl_vector<vgl_polygon<double> >();
00045   }
00046   vcl_vector<vgl_polygon<double> > vpolys;
00047   for (unsigned i = 0; i<sos.size(); ++i) {
00048     vsol_polygon_2d* poly = static_cast<vsol_polygon_2d*>(sos[i].ptr());
00049     vgl_polygon<double> vpoly; vpoly.new_sheet();
00050     unsigned nverts = poly->size();
00051     for (unsigned i = 0; i<nverts; ++i) {
00052       vsol_point_2d_sptr v = poly->vertex(i);
00053       vpoly.push_back(v->x(), v->y());
00054     }
00055     vpolys.push_back(vpoly);
00056   }
00057   return vpolys;
00058 }
00059 
00060 vil_image_view<float> sdet_texture_classifier::
00061 scale_image(vil_image_resource_sptr const& resc)
00062 {
00063   vil_pixel_format fmt = resc->pixel_format();
00064   vil_image_view<float> img = brip_vil_float_ops::convert_to_float(resc);
00065   if (fmt == VIL_PIXEL_FORMAT_BYTE)
00066     vil_math_scale_values(img,1.0/255.0);
00067   if (fmt == VIL_PIXEL_FORMAT_UINT_16)
00068     vil_math_scale_values(img,1.0/2048.0);
00069   return img;
00070 }
00071 
00072 static unsigned gauss_radius(float sigma, float cutoff_ratio)
00073 {
00074   double sigma_sq_inv = 1/(sigma*sigma);
00075   int r = static_cast<unsigned>(vcl_sqrt((-2.0*vcl_log(cutoff_ratio))/sigma_sq_inv)+0.5);
00076   return r;
00077 }
00078 
00079 // define a color map for texture categories. Defined for up to eight classes
00080 void sdet_texture_classifier::init_color_map()
00081 {
00082   vcl_vector<vnl_vector_fixed<float, 3> > colors(8);
00083   colors[0][0]=0.0f; colors[0][1]=0.0f; colors[0][2]=1.0f;
00084   colors[1][0]=0.0f; colors[1][1]=1.0f; colors[1][2]=0.0f;
00085   colors[2][0]=0.0f; colors[2][1]=0.5f; colors[2][2]=0.5f;
00086   colors[3][0]=1.0f; colors[3][1]=0.0f; colors[3][2]=0.0f;
00087   colors[4][0]=0.5f; colors[4][1]=0.0f; colors[4][2]=0.5f;
00088   colors[5][0]=0.5f; colors[5][1]=0.5f; colors[5][2]=0.0f;
00089   colors[6][0]=0.0f; colors[6][1]=0.25f; colors[6][2]=0.75f;
00090   colors[7][0]=0.25f; colors[7][1]=0.25f; colors[7][2]=0.5f;
00091   unsigned i = 0;
00092   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::const_iterator it= texton_dictionary_.begin();
00093   for (; it!= texton_dictionary_.end(); ++it,++i) {
00094     color_map_[(*it).first] = colors[i];
00095     vcl_cout << (*it).first <<":(" << colors[i][0] << ' '
00096              << colors[i][1] << ' ' << colors[i][2] << ")\n" << vcl_flush;
00097   }
00098   color_map_valid_ = true;
00099 }
00100 
00101 void sdet_texture_classifier::print_color_map() const
00102 {
00103   if (!color_map_valid_) return;
00104   vcl_cout << "Category Color Map\n";
00105   vcl_map< vcl_string, vnl_vector_fixed<float, 3> >::const_iterator it= color_map_.begin();
00106   for (; it!= color_map_.end(); ++it) {
00107     vnl_vector_fixed<float,3> const & colors = (*it).second;
00108     vcl_cout << (*it).first <<":(" << colors[0] << ' '
00109              << colors[1] << ' ' << colors[2] << ")\n" << vcl_flush;
00110   }
00111 }
00112 
00113 sdet_texture_classifier::
00114 sdet_texture_classifier(sdet_texture_classifier_params const& params)
00115 : sdet_texture_classifier_params(params),
00116   filter_responses_(brip_filter_bank(params.n_scales_,
00117                                      params.scale_interval_,
00118                                      params.lambda0_,
00119                                      params.lambda1_,
00120                                      params.angle_interval_,
00121                                      params.cutoff_per_)),
00122   distances_valid_(false), inter_prob_valid_(false), color_map_valid_(false),
00123   texton_index_valid_(false), texton_weights_valid_(false)
00124 {}
00125 
00126 
00127 bool sdet_texture_classifier::
00128 compute_filter_bank(vil_image_view<float> const& img)
00129 {
00130   vul_timer t;
00131   filter_responses_.set_image(img);
00132   bool bright = false;
00133   bool scale_invariant = true;
00134 
00135   vcl_cout<< "s = ("<< laplace_radius_<< ' ' << laplace_radius_ << ")\n"
00136           << vcl_flush;
00137 
00138   laplace_ = brip_vil_float_ops::fast_extrema(img, laplace_radius_,
00139                                               laplace_radius_, bright, mag_,
00140                                               false,false, signed_response_,
00141                                               scale_invariant, false,
00142                                               cutoff_per_);
00143 
00144   vcl_cout<< "s = ("<< gauss_radius_<< ' ' << gauss_radius_ << ")\n"
00145           << vcl_flush;
00146   gauss_ = brip_vil_float_ops::gaussian(img, gauss_radius_);
00147   for (unsigned j = 0; j<gauss_.nj(); ++j)
00148     for (unsigned i = 0; i<gauss_.ni(); ++i)
00149       gauss_(i,j) =gauss_(i,j)*0.03f;//HACK!! Need principled way to scale
00150   //filter responses
00151   vcl_cout << "Computed filter bank in " << t.real()/1000.0 << " secs.\n";
00152 #if 0
00153   //=============== temporary debug ====================
00154   vil_save(filter_responses_.response(0), "e:/images/TextureTraining/s0.tiff");
00155   vil_save(filter_responses_.response(1), "e:/images/TextureTraining/s1.tiff");
00156   vil_save(filter_responses_.response(2), "e:/images/TextureTraining/s2.tiff");
00157   vil_save(laplace_, "e:/images/TextureTraining/laplace.tiff");
00158   vil_save(gauss_, "e:/images/TextureTraining/gauss.tiff");
00159   //===========================================================
00160 #endif
00161   return true;
00162 }
00163 
00164 // Used to define the initial k means cluster centers
00165 // by random selection from the training data
00166 vcl_vector<vnl_vector<double> > sdet_texture_classifier::
00167 random_centers(vcl_vector<vnl_vector<double> > const& training_data,
00168                unsigned k) const{
00169   double n = training_data.size();
00170   vcl_vector<vnl_vector<double> > rand_centers(k);
00171   for (unsigned i = 0; i<k; ++i) {
00172     unsigned index = static_cast<unsigned>((n-1)*(vcl_rand()/(RAND_MAX+1.0)));
00173     rand_centers[i] = training_data[index];
00174   }
00175   return rand_centers;
00176 }
00177 
00178 // compute a vector of filter responses, which are sampled from the
00179 // response pixels for the input image
00180 bool sdet_texture_classifier::compute_training_data(vcl_string const& category)
00181 {
00182   // dimension of filter bank
00183   unsigned dim = filter_responses_.n_levels();
00184   if (!dim) {
00185     vcl_cout << "zero dimensional filter bank\n" << vcl_flush;
00186     return false;
00187   }
00188   vul_timer t;
00189   // collect set of points
00190   unsigned maxr = this->max_filter_radius();
00191   vcl_vector<vnl_vector<double> > training_data, sampled_data;
00192   // assume all filter response images are the same size;
00193   int ni = filter_responses_.ni()-maxr;
00194   int nj = filter_responses_.nj()-maxr;
00195   if (ni<=0||nj<=0) {
00196     vcl_cout << "training image too small ni or nj <= " << maxr << '\n';
00197     return false;
00198   }
00199   vcl_cout << " texton dimension: " << dim + 2 << '\n' << vcl_flush;
00200   for (int j = maxr; j<nj; ++j)
00201     for (int i = maxr; i<ni; ++i) {
00202       vnl_vector<double> tx(dim+2);
00203       for (unsigned f = 0; f<dim; ++f)
00204         tx[f]=filter_responses_.response(f)(i,j);
00205       tx[dim]=laplace_(i,j); tx[dim+1]=gauss_(i,j);
00206       training_data.push_back(tx);
00207     }
00208   // reduce the number of samples to specified size
00209   unsigned ns = training_data.size();
00210   if (ns>n_samples_) {
00211     for (unsigned i = 0; i<n_samples_; ++i) {
00212       unsigned s = static_cast<unsigned>((n_samples_-1)*(vcl_rand()/(RAND_MAX+1.0)));
00213       sampled_data.push_back(training_data[s]);
00214     }
00215     training_data.clear();
00216     training_data = sampled_data;
00217   }
00218   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::iterator dit;
00219   dit = training_data_.find(category);
00220   if (dit == training_data_.end()) {
00221     training_data_[category]=training_data;
00222   }
00223   else {
00224     training_data_[category].insert(training_data_[category].end(),
00225                                     training_data.begin(),
00226                                     training_data.end());
00227   }
00228   vcl_cout << "Collect texture samples in texture box region" << t.real()/1000.0 << " secs.\n";
00229   return true;
00230 }
00231 
00232 // compute a vector of filter responses, which are sampled from the
00233 // response pixels for the input image. Only responses within the
00234 // polygon are considered
00235 bool sdet_texture_classifier::compute_training_data(vcl_string const& category,
00236                                                     vgl_polygon<double> const& texture_region)
00237 {
00238   // dimension of filter bank
00239   unsigned dim = filter_responses_.n_levels();
00240   if (!dim) {
00241     vcl_cout << "zero dimensional filter bank\n" << vcl_flush;
00242     return false;
00243   }
00244   vul_timer t;
00245   // collect set of points
00246   unsigned maxr = this->max_filter_radius();
00247   vcl_vector<vnl_vector<double> > training_data, sampled_data;
00248   // assume all filter response images are the same size;
00249   int ni = filter_responses_.ni()-maxr;
00250   int nj = filter_responses_.nj()-maxr;
00251   if (ni<=0||nj<=0) {
00252     vcl_cout << "training image too small ni or nj <= " << maxr << '\n';
00253     return false;
00254   }
00255   vcl_cout << " texton dimension: " << dim + 2  << '\n' << vcl_flush;
00256   vgl_polygon_scan_iterator<double> psi(texture_region);
00257   for (psi.reset(); psi.next(); ) {
00258     int j = psi.scany();
00259     for (int i  = psi.startx(); i <= psi.endx(); ++i) {
00260       vnl_vector<double> tx(dim+2);
00261       for (unsigned f = 0; f<dim; ++f)
00262         tx[f]=filter_responses_.response(f)(i,j);
00263       tx[dim]=laplace_(i,j); tx[dim+1]=gauss_(i,j);
00264       training_data.push_back(tx);
00265     }
00266   }
00267   // reduce the number of samples to specified size
00268   unsigned ns = training_data.size();
00269   if (ns>n_samples_) {
00270     for (unsigned i = 0; i<n_samples_; ++i) {
00271       unsigned s = static_cast<unsigned>((n_samples_-1)*(vcl_rand()/(RAND_MAX+1.0)));
00272       sampled_data.push_back(training_data[s]);
00273     }
00274     training_data.clear();
00275     training_data = sampled_data;
00276   }
00277   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::iterator dit;
00278   dit = training_data_.find(category);
00279   if (dit == training_data_.end()) {
00280     training_data_[category]=training_data;
00281   }
00282   else {
00283     training_data_[category].insert(training_data_[category].end(),
00284                                     training_data.begin(),
00285                                     training_data.end());
00286   }
00287   vcl_cout << "Collect texture samples in texture polygon in " << t.real()/1000.0 << " secs.\n";
00288   return true;
00289 }
00290 
00291 // compute a vector of filter responses, which are sampled from the
00292 // response pixels for the input image. Only responses within the
00293 // the set of polygon regions are considered
00294 bool   sdet_texture_classifier::
00295 compute_training_data(vcl_string const& category,
00296                       vcl_vector<vgl_polygon<double> > const& texture_regions) {
00297   // dimension of filter bank
00298   unsigned dim = filter_responses_.n_levels();
00299   if (!dim) {
00300     vcl_cout << "zero dimensional filter bank\n" << vcl_flush;
00301     return false;
00302   }
00303   vul_timer t;
00304   vcl_cout << " texton dimension: " << dim + 2 << '\n' << vcl_flush;
00305 
00306   // collect set of points
00307   unsigned maxr = this->max_filter_radius();
00308   vcl_vector<vnl_vector<double> > training_data, sampled_data;
00309   // assume all filter response images are the same size;
00310   int ni = filter_responses_.ni()-maxr;
00311   int nj = filter_responses_.nj()-maxr;
00312   if (ni<=0||nj<=0) {
00313     vcl_cout << "training image too small ni or nj <= " << maxr << '\n';
00314     return false;
00315   }
00316   vcl_vector<vgl_polygon<double> >::const_iterator pit = texture_regions.begin();
00317   for (; pit != texture_regions.end(); ++pit) {
00318     vgl_polygon_scan_iterator<double> psi(*pit, false);
00319     for (psi.reset(); psi.next(); ) {
00320       int j = psi.scany();
00321       for (int i  = psi.startx(); i <= psi.endx(); ++i) {
00322         vnl_vector<double> tx(dim+2);
00323         for (unsigned f = 0; f<dim; ++f)
00324           tx[f]=filter_responses_.response(f)(i,j);
00325         double g = gauss_(i,j);
00326         tx[dim]=laplace_(i,j); tx[dim+1]=g;
00327         training_data.push_back(tx);
00328       }
00329     }
00330   }
00331   // reduce the number of samples to specified size
00332   unsigned ns = training_data.size();
00333   if (ns>n_samples_) {
00334     for (unsigned i = 0; i<n_samples_; ++i) {
00335       unsigned s = static_cast<unsigned>((n_samples_-1)*(vcl_rand()/(RAND_MAX+1.0)));
00336       sampled_data.push_back(training_data[s]);
00337     }
00338     training_data.clear();
00339     training_data = sampled_data;
00340   }
00341   vcl_cout<< "Is category " << category << " in dictionary?\n" << vcl_flush;
00342   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::iterator dit;
00343   dit = training_data_.find(category);
00344   if (dit == training_data_.end()) {
00345     vcl_cout << " No, so adding new map entry with " << training_data.size()
00346              << " samples\n" << vcl_flush;
00347     training_data_[category]=training_data;
00348   }
00349   else {
00350     vcl_cout << "Yes, starting with " << training_data_[category].size()
00351              << " samples\n" << vcl_flush;
00352     training_data_[category].insert(training_data_[category].end(),
00353                                     training_data.begin(),
00354                                     training_data.end());
00355     vcl_cout << "after addition, training size is "
00356              << training_data_[category].size() << '\n' << vcl_flush;
00357   }
00358   vcl_cout << "Collect texture samples in texture polygon in " << t.real()/1000.0 << " secs.\n";
00359   return true;
00360 }
00361 
00362 bool sdet_texture_classifier::compute_training_data(vcl_string const& category,
00363                                                     vcl_string const& poly_path)
00364 {
00365   vcl_vector<vgl_polygon<double> > polys;
00366   if (poly_path=="") {
00367     //create a polygon that is the bounding box of the image
00368     unsigned ni = laplace_.ni(), nj = laplace_.nj();
00369     vgl_polygon<double> temp; temp.new_sheet();
00370     temp.push_back(0.0, 0.0);
00371     temp.push_back(0.0, static_cast<double>(nj));
00372     temp.push_back(static_cast<double>(ni), static_cast<double>(nj));
00373     temp.push_back(static_cast<double>(ni), 0.0);
00374     polys.push_back(temp);
00375   }
00376   else {
00377    polys = load_polys(poly_path);
00378   }
00379   return this->compute_training_data(category, polys);
00380 }
00381 
00382 // execute the k means algorithm to form textons
00383 // assumes that training data has been initialized
00384 bool sdet_texture_classifier::compute_textons(vcl_string const& category)
00385 {
00386   vul_timer t;
00387   // run k_means
00388   vcl_vector<vnl_vector<double> >& train_data = training_data_[category];
00389   vcl_cout << "Start k means for category " << category << " with sample size "
00390            << train_data.size() << '\n' << vcl_flush;
00391   vcl_vector<vnl_vector<double> > centers = random_centers(train_data, k_);
00392 
00393   unsigned converged_k = k_;
00394   t.mark();
00395   unsigned n_iter = sdet_k_means(train_data, converged_k, &centers);
00396   vcl_cout << "After " << n_iter << " iterations found " << converged_k
00397            << " cluster centers\n";
00398   texton_dictionary_[category]=centers;
00399   vcl_cout << "Compute k means in " << t.real()/1000.0 << " secs.\n";
00400   return true;
00401 }
00402 
00403 bool sdet_texture_classifier::
00404 compute_textons(vcl_vector<vcl_string> const& image_paths,
00405                 vcl_string const& category,
00406                 vcl_vector<vcl_string> const& poly_paths)
00407 {
00408   if (!image_paths.size()) {
00409     vcl_cout << " no images to compute textons\n";
00410     return false;
00411   }
00412   //load images and polygons
00413   vcl_vector<vil_image_view<float> > imgs;
00414   vcl_vector<vcl_vector< vgl_polygon<double> > >polys;
00415   unsigned i = 0;
00416   for (vcl_vector<vcl_string>::const_iterator pit = image_paths.begin();
00417        pit!=image_paths.end(); ++pit, ++i) {
00418     vil_image_resource_sptr resc = vil_load_image_resource((*pit).c_str());
00419     vil_image_view<float> view = scale_image(resc);
00420     imgs.push_back(view);
00421     unsigned ni = view.ni(), nj = view.nj();
00422     if (!poly_paths.size()||poly_paths[i]=="") {
00423       //create a polygon that is the bounding box of the image
00424       vgl_polygon<double> temp; temp.new_sheet();
00425       temp.push_back(0.0, 0.0);
00426       temp.push_back(0.0, static_cast<double>(nj));
00427       temp.push_back(static_cast<double>(ni), static_cast<double>(nj));
00428       temp.push_back(static_cast<double>(ni), 0.0);
00429       vcl_vector< vgl_polygon<double> > vplys;
00430       vplys.push_back(temp);
00431       polys.push_back(vplys);
00432     }
00433     else {
00434       vcl_vector<vgl_polygon<double> > vpys = load_polys(poly_paths[i]);
00435       polys.push_back(vpys);
00436     }
00437   }
00438   unsigned n = i;//number of images
00439 
00440   //compute bounding boxes
00441   for (unsigned i = 0; i<n; ++i) {
00442     double ni = imgs[i].ni(), nj = imgs[i].nj();
00443     vsol_box_2d_sptr box = new vsol_box_2d();
00444     vcl_vector<vgl_polygon<double> >& plist = polys[i];
00445     for (vcl_vector<vgl_polygon<double> >::iterator pit = plist.begin();
00446          pit != plist.end(); ++pit) {
00447       vcl_vector<vgl_point_2d<double> > sht = (*pit)[0];
00448       for (vcl_vector<vgl_point_2d<double> >::iterator xit = sht.begin();
00449            xit != sht.end(); ++xit)
00450         box->add_point(xit->x(), xit->y());
00451     }
00452     //expand box by filter radius
00453     double margin = this->max_filter_radius();
00454     double xmin = box->get_min_x()-margin, xmax = box->get_max_x()+margin;
00455     if (xmin<0) xmin = 0.0;  if (xmax>=ni) xmax = ni-1.0;
00456     double ymin = box->get_min_y()-margin, ymax = box->get_max_y()+margin;
00457     if (ymin<0) ymin = 0.0;  if (ymax>=nj) ymax = nj-1.0;
00458     unsigned i0=static_cast<unsigned>(xmin), j0 = static_cast<unsigned>(ymin);
00459     unsigned cni = static_cast<unsigned>(xmax-xmin+1.0);
00460     unsigned cnj = static_cast<unsigned>(ymax-ymin+1.0);
00461     //crop image
00462     vil_image_view<float> cview = vil_crop(imgs[i], i0, cni, j0, cnj);
00463     // shift polys to cropped coordinate system
00464     vcl_vector<vgl_polygon<double> > cplist;
00465     for (vcl_vector<vgl_polygon<double> >::iterator pit = plist.begin();
00466          pit != plist.end(); ++pit) {
00467       vgl_polygon<double> tpoly; tpoly.new_sheet();
00468       vcl_vector<vgl_point_2d<double> > sht = (*pit)[0];
00469       for (vcl_vector<vgl_point_2d<double> >::iterator xit = sht.begin();
00470            xit != sht.end(); ++xit) {
00471         double xp = xit->x()-xmin, yp = xit->y() - ymin;
00472         tpoly.push_back(xp, yp);
00473       }
00474       cplist.push_back(tpoly);
00475     }
00476     vcl_cout << "processing image(" << cview.ni() << ' ' << cview.nj() << ")\n" << vcl_flush;
00477     this->compute_filter_bank(cview);
00478     this->compute_training_data(category, cplist);
00479   }
00480   this->compute_textons(category);
00481   return true;
00482 }
00483 
00484 bool sdet_texture_classifier::save_dictionary(vcl_string const& path) const
00485 {
00486   vsl_b_ofstream os(path.c_str());
00487   if (!os) {
00488     vcl_cout << "Can't open binary stream in save_dictionary\n";
00489     return false;
00490   }
00491   vcl_cout << "Save dictionary to " << path << '\n';
00492   sdet_texture_classifier_params const * tcp_ptr =
00493     dynamic_cast<sdet_texture_classifier_params const*>(this);
00494   vsl_b_write(os, *tcp_ptr);
00495   vsl_b_write(os, texton_dictionary_);
00496   vsl_b_write(os, category_histograms_);
00497   os.close();
00498   return true;
00499 }
00500 
00501 bool sdet_texture_classifier::load_dictionary(vcl_string const& path)
00502 {
00503   vsl_b_ifstream is(path.c_str());
00504   if (!is) {
00505     vcl_cout << "Can't open binary stream in load_dictionary in " << path << "\n";
00506     return false;
00507   }
00508   vcl_cout << "Loading texton dictionary: " << path << '\n' << vcl_flush;
00509   texton_dictionary_.clear();
00510   texton_index_.clear();
00511   category_histograms_.clear();
00512   sdet_texture_classifier_params* tcp_ptr
00513     = dynamic_cast<sdet_texture_classifier_params*>(this);
00514   vsl_b_read(is, *tcp_ptr);
00515   vsl_b_read(is, texton_dictionary_);
00516   this->compute_distances();
00517   this->compute_texton_index();
00518   vsl_b_read(is, category_histograms_);
00519   this->compute_texton_weights();
00520   this->compute_interclass_probs();
00521   is.close();
00522   return true;
00523 }
00524 
00525 void sdet_texture_classifier::print_dictionary() const
00526 {
00527   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::const_iterator it= texton_dictionary_.begin();
00528   for (; it!= texton_dictionary_.end(); ++it) {
00529     vcl_cout << " ===Category: " << (*it).first << "===\n";
00530     for (unsigned i = 0; i<(*it).second.size(); ++i) {
00531       unsigned dim = (*it).second[0].size();
00532       vcl_cout << "c[" << i << "]:(";
00533       for (unsigned f = 0; f<dim; ++f)
00534         vcl_cout << (*it).second[i][f] << ' ';
00535       vcl_cout << ")\n" << vcl_flush;
00536     }
00537   }
00538 }
00539 
00540 // compute the nearest distance between textons in different categories
00541 // for the same category the distance is defined as the maximum distance
00542 // between textons in the category
00543 void sdet_texture_classifier::compute_distances()
00544 {
00545   dist_.clear();
00546   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::const_iterator jt= texton_dictionary_.begin();
00547   for (; jt!= texton_dictionary_.end(); ++jt) {
00548     vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::const_iterator it= texton_dictionary_.begin();
00549     for (; it!= texton_dictionary_.end(); ++it)
00550       if ((*it).first == (*jt).first) {
00551         //compute max distance between cluster centers within the category
00552         double max_dist = 0.0;
00553         for (unsigned j = 0; j<(*it).second.size(); ++j)
00554           for (unsigned i = 0; i<(*it).second.size(); ++i) {
00555             double d = vnl_vector_ssd((*it).second[i], (*jt).second[j]);
00556             //root mean square dist (RMS)
00557             d = vcl_sqrt(d/(*it).second[i].size());
00558             if (d>max_dist) max_dist = d;
00559           }
00560         dist_[(*jt).first][(*it).first]=max_dist;
00561       }
00562       else {
00563         //compute min distance between cluster centers in different categories
00564         double min_dist = vnl_numeric_traits<double>::maxval;
00565         for (unsigned j = 0; j<(*jt).second.size(); ++j)
00566           for (unsigned i = 0; i<(*it).second.size(); ++i) {
00567             double d = vnl_vector_ssd((*it).second[i],(*jt).second[j]);
00568             //root mean square dist (RMS)
00569             d = vcl_sqrt(d/(*it).second[i].size());
00570             if (d<min_dist) min_dist = d;
00571           }
00572         dist_[(*jt).first][(*it).first]= min_dist;
00573       }
00574   }
00575   distances_valid_ = true;
00576 }
00577 
00578 // compute the probability of a category, given the textons of itself or other
00579 // categories. Provides a measure of the distinctiveness of a category.
00580 // The texton histogram probabilities are multiplied by a weight factor that
00581 // is based on how many categories in which a texton appears.
00582 void sdet_texture_classifier::compute_interclass_probs()
00583 {
00584   inter_prob_.clear();
00585   vcl_map< vcl_string, vcl_vector<float> >::const_iterator jt=
00586     category_histograms_.begin();
00587   for (; jt!= category_histograms_.end(); ++jt) {
00588     vcl_vector<float> const & histj = (*jt).second;
00589     unsigned n = histj.size();
00590     float prob_total = 0.0f;
00591     vcl_map<vcl_string, vcl_vector<float> >::const_iterator it=
00592       category_histograms_.begin();
00593     for (; it!= category_histograms_.end(); ++it) {
00594       float prob_sum = 0.0f;
00595       vcl_vector<float> const& histi = (*it).second;
00596       for (unsigned j = 0; j<n; ++j)
00597         if (histj[j]>0.0f && histi[j]>0.0f)
00598           prob_sum += texton_weights_[j]*histj[j];
00599       inter_prob_[(*jt).first][(*it).first] = prob_sum;
00600       prob_total += prob_sum;
00601     }
00602     it=category_histograms_.begin();
00603     for (; it!= category_histograms_.end(); ++it)
00604       inter_prob_[(*jt).first][(*it).first] /= prob_total;
00605   }
00606   inter_prob_valid_ = true;
00607 }
00608 
00609 // The weighting factor for textons based on
00610 // probability of belonging to multiple categories.
00611 // Here p = 1/Nc , where Nc is the number of categories in which
00612 // a texton appears. The factor off accounts for the singularity
00613 // of the log function and controls the rapidity of falloff in
00614 // weight with Nc.
00615 static float w(float p, float off)
00616 {
00617   float t0 = -vcl_log(off), t1 = vcl_log(1.0f+off);
00618   float res = -vcl_log(1.0f - p + off) + t1;
00619   res /= (t0 + t1);
00620   return res;
00621 }
00622 
00623 // Assign a weighting factor to each texton. The weight is 1 if the
00624 // texton appears in only one category and falls off as the number of
00625 // categories that share the texton increase
00626 void sdet_texture_classifier::compute_texton_weights()
00627 {
00628   if (texton_index_valid_) this->compute_texton_index();
00629   unsigned n = texton_index_.size();
00630   unsigned m = category_histograms_.size();
00631   texton_weights_.resize(n);
00632   vcl_vector<vcl_vector<float> > cross_category_probs(n);
00633   for (unsigned i = 0; i<n; ++i)
00634     cross_category_probs[i] = vcl_vector<float>(m, 0.0f);
00635 
00636   vcl_map<vcl_string, vcl_vector<float> >::iterator hit = category_histograms_.begin();
00637   unsigned c = 0;//category index
00638   for (; hit!=category_histograms_.end(); ++hit, ++c) {
00639     vcl_vector<float> const& h = (*hit).second;
00640     for (unsigned i = 0; i<n; ++i)
00641       if (h[i]>0.0f)
00642         cross_category_probs[i][c] += 1.0f;
00643   }
00644   //normalize the category probabilities
00645   for (unsigned i = 0; i<n; ++i) {
00646     float sum = 0.0f;
00647     for (unsigned c = 0; c<m; ++c)
00648       sum += cross_category_probs[i][c];
00649 
00650     for (unsigned c = 0; c<m; ++c)
00651       cross_category_probs[i][c] /= sum;
00652   }
00653   for (unsigned i = 0; i<n; ++i) {
00654     float maxp = 0.0f;
00655     for (unsigned c = 0; c<m; ++c)
00656       if (cross_category_probs[i][c]>maxp)
00657         maxp = cross_category_probs[i][c];
00658     texton_weights_[i]=w(maxp, weight_offset_);
00659   }
00660   texton_weights_valid_ = true;
00661 }
00662 
00663 void sdet_texture_classifier::print_distances() const
00664 {
00665   //don't really care if distance map is changed, since derived
00666   //from primary members that are const
00667   sdet_texture_classifier* tc = const_cast<sdet_texture_classifier*>(this);
00668   if (!distances_valid_) tc->compute_distances();
00669 
00670   vcl_cout << "Category distance matrix\n";
00671   vcl_map< vcl_string, vcl_map< vcl_string, double> >::const_iterator jt = dist_.begin();
00672   for (; jt != dist_.end(); ++jt) {
00673     vcl_cout << (*jt).first << " :\n";
00674     vcl_map< vcl_string, double>::const_iterator it = (*jt).second.begin();
00675     for (; it != (*jt).second.end(); ++it)
00676       vcl_cout << (*it).first << ":(" << (*it).second << ") ";
00677     vcl_cout << '\n';
00678   }
00679 }
00680 
00681 void sdet_texture_classifier::print_category_histograms() const
00682 {
00683   unsigned nt = texton_index_.size();
00684   unsigned nh = category_histograms_.size();
00685   vcl_vector<vcl_string> cat_names(nh);
00686   vcl_vector<vcl_vector<float> > hists(nt, vcl_vector<float>(nh));
00687   vcl_map<vcl_string, vcl_vector<float> >::const_iterator hit = category_histograms_.begin();
00688   unsigned j = 0;
00689   for (; hit != category_histograms_.end(); ++hit, ++j) {
00690     cat_names[j]=(*hit).first;
00691     const vcl_vector<float>& h = (*hit).second;
00692     for (unsigned i = 0; i<h.size(); ++i)
00693       hists[i][j] = h[i];
00694   }
00695   for (unsigned j = 0; j<nh; ++j)
00696     vcl_cout << cat_names[j] << ' ';
00697   vcl_cout << '\n';
00698   for (unsigned i = 0; i<nt; ++i) {
00699     for (unsigned j = 0; j<nh; ++j)
00700       vcl_cout << hists[i][j] << ' ';
00701     vcl_cout << '\n';
00702   }
00703 }
00704 
00705 void sdet_texture_classifier::print_interclass_probs() const
00706 {
00707   sdet_texture_classifier* ncthis = const_cast<sdet_texture_classifier*>(this);
00708   if (!inter_prob_valid_) ncthis->compute_interclass_probs();
00709   vcl_cout << "Interclass probabilities\n";
00710   vcl_map< vcl_string, vcl_map< vcl_string, double> >::const_iterator jt = inter_prob_.begin();
00711   for (; jt != inter_prob_.end(); ++jt) {
00712     vcl_cout << (*jt).first << " :\n";
00713     vcl_map< vcl_string, double>::const_iterator it = (*jt).second.begin();
00714     for (; it != (*jt).second.end(); ++it)
00715       vcl_cout << (*it).first << ":(" << (*it).second << ") ";
00716     vcl_cout << '\n';
00717   }
00718 }
00719 
00720 void sdet_texture_classifier::print_texton_weights() const
00721 {
00722   sdet_texture_classifier* ncthis = const_cast<sdet_texture_classifier*>(this);
00723   if (!texton_weights_valid_) ncthis->compute_texton_weights();
00724   vcl_cout << "Texton weights ===>\n";
00725   for (vcl_vector<float>::const_iterator wit = texton_weights_.begin();
00726        wit != texton_weights_.end(); ++wit)
00727     vcl_cout << (*wit) << '\n';
00728 }
00729 
00730 
00731 // transfer the texton dictionary to an efficient index for sorting
00732 // on Euclidean distance
00733 void sdet_texture_classifier::compute_texton_index()
00734 {
00735   texton_index_.clear();
00736   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::const_iterator it= texton_dictionary_.begin();
00737   for (; it!= texton_dictionary_.end(); ++it) {
00738     vcl_string const& cat = (*it).first;
00739     vcl_vector<vnl_vector<double> > const & centers = (*it).second;
00740     for (vcl_vector<vnl_vector<double> >::const_iterator cit = centers.begin();
00741          cit != centers.end(); ++cit)
00742       texton_index_.push_back(sdet_neighbor(cat, (*cit)));
00743   }
00744 }
00745 
00746 unsigned sdet_texture_classifier::nearest_texton_index(vnl_vector<double> const& query)
00747 {
00748   double min_dist = vnl_numeric_traits<double>::maxval;
00749   unsigned min_index = 0;
00750   unsigned n = texton_index_.size();
00751   for (unsigned i = 0; i<n; ++i) {
00752     double d = vnl_vector_ssd(texton_index_[i].k_mean_, query);
00753     if (d<min_dist) {
00754       min_dist = d;
00755       min_index = i;
00756     }
00757   }
00758   return min_index;
00759 }
00760 
00761 void sdet_texture_classifier::compute_category_histograms()
00762 {
00763   // assumes that training_data and the texton index are valid
00764   // the index of the texton_index vector forms the bin space of
00765   // the category histogram
00766   if (!texton_index_valid_) this->compute_texton_index();
00767   unsigned n = texton_index_.size();
00768   if (!n) {
00769     vcl_cout << "no textons to compute category histograms\n";
00770     return;
00771   }
00772   else
00773     vcl_cout << "computing category histograms with " << n << " textons\n";
00774   vcl_map< vcl_string, vcl_vector<vnl_vector<double> > >::iterator dit = training_data_.begin();
00775   for (; dit != training_data_.end(); ++dit) {
00776     //histogram for the given category
00777     vcl_vector<float> hist(n, 0.0f);
00778     const vcl_string& cat = (*dit).first;
00779     const vcl_vector<vnl_vector<double> >& tdata = (*dit).second;
00780     unsigned ndata  = tdata.size();
00781     vcl_cout << "h[" << cat << "](" << ndata << ") "<< vcl_flush;
00782     float weight = 1.0f/static_cast<float>(ndata);
00783     //insert texton counts into the histogram
00784     for (vcl_vector<vnl_vector<double> >::const_iterator vit = tdata.begin();
00785          vit != tdata.end(); ++vit)
00786       this->update_hist(*vit, weight, hist);
00787 
00788     category_histograms_[cat]=hist;
00789   }
00790   vcl_cout << '\n';
00791 }
00792 
00793 void sdet_texture_classifier::
00794 update_hist(vnl_vector<double> const& f, float weight, vcl_vector<float>& hist)
00795 {
00796   unsigned indx = this->nearest_texton_index(f);
00797   hist[indx]+=weight;// for example, counts are normalized to probability
00798 }
00799 
00800 vcl_map<vcl_string, float>  sdet_texture_classifier::
00801 texture_probabilities(vcl_vector<float> const& hist)
00802 {
00803   unsigned nt = texton_index_.size();
00804   vcl_map<vcl_string, float> probs;
00805   vcl_map<vcl_string, vcl_vector<float> >::iterator hit = category_histograms_.begin();
00806 
00807   for (; hit != category_histograms_.end(); ++hit) {
00808     const vcl_string& cat = (*hit).first;
00809     const vcl_vector<float>& hc = (*hit).second;
00810     float prob_sum = 0.0f;
00811     float np = 0.0f;
00812     for (unsigned i = 0; i<nt; ++i) {
00813       float w = texton_weights_[i];
00814       np += w;
00815       float vc = hc[i]*w, vh = hist[i]*w;
00816       prob_sum += (vc<=vh)?vc:vh;
00817     }
00818     prob_sum /= np;
00819     probs[cat] = prob_sum;
00820   }
00821   return probs;
00822 }
00823 
00824 void sdet_texture_classifier::
00825 category_color_mix(vcl_map<vcl_string, float>  & probs,
00826                    vnl_vector_fixed<float, 3>& color_mix) {
00827   //start with max prob color
00828   vcl_map<vcl_string, vcl_vector<float> >::iterator hit = category_histograms_.begin();
00829   float prob_sum = 0.0f;
00830   vnl_vector_fixed<float, 3> mix(0.0f);
00831   vcl_string max_cat;
00832   for (; hit != category_histograms_.end(); ++hit) {
00833     const vcl_string& cat = (*hit).first;
00834     float p = probs[cat];
00835     prob_sum += p;
00836     mix += p*color_map_[cat];
00837   }
00838   color_mix = mix/prob_sum;
00839 }
00840 
00841 
00842 #if 0 //=====debug====
00843 static bool required_block(int bidxu, int bidxv, int i,
00844                            int j, int block_size, int margin)
00845 {
00846   int idxu = (i-margin)/block_size, idxv = (j-margin)/block_size;
00847   return bidxu == idxu && bidxv == idxv;
00848 }
00849 #endif // 0
00850 
00851 vil_image_view<float> sdet_texture_classifier::
00852 classify_image_blocks(vcl_string const& img_path)
00853 {
00854   vul_timer t;
00855   vil_image_resource_sptr resc = vil_load_image_resource(img_path.c_str());
00856   vil_image_view<float> img = scale_image(resc);
00857   vcl_cout << "Classifying categories " << img_path << "\nsize(" << img.ni()
00858            << ' ' << img.nj() << ")pixels:[" << texton_dictionary_.size()
00859            << "]categories\n" << vcl_flush;
00860   if (!color_map_valid_)
00861     this->init_color_map();
00862   if (!texton_index_valid_)
00863     this->compute_texton_index();
00864   this->compute_filter_bank(img);
00865   unsigned dim = filter_responses_.n_levels();
00866   vcl_cout << "texton dimension " << dim +2<< '\n';
00867 
00868   int margin = static_cast<int>(this->max_filter_radius());
00869   vcl_cout << "filter kernel margin " << margin << '\n';
00870   int ni = static_cast<int>(img.ni());
00871   int nj = static_cast<int>(img.nj());
00872   if ((ni-margin)<=0 || (nj-margin)<=0) {
00873     vcl_cout << "Image smaller than filter margin\n";
00874     return vil_image_view<float>(0, 0);
00875   }
00876   unsigned block_area = block_size_*block_size_;
00877   float weight = 1.0f/static_cast<float>(block_area);
00878   vil_image_view<float> prob(ni, nj, 3);
00879   prob.fill(0.5f);
00880   unsigned nh = texton_index_.size();
00881   int bidxv = 0;
00882   for (int j = margin; j<(nj-margin); j+=block_size_, ++bidxv) {
00883     int bidxu = 0;
00884     for (int i = margin; i<(ni-margin); i+=block_size_, ++bidxu) {
00885       vcl_vector<float> h(nh, 0.0f);
00886       for (unsigned r = 0; r<block_size_; ++r)
00887         for (unsigned c = 0; c<block_size_; ++c) {
00888           vnl_vector<double> temp(dim+2);
00889           for (unsigned f = 0; f<dim; ++f)
00890             temp[f]=filter_responses_.response(f)(i+c,j+r);
00891           temp[dim]=laplace_(i+c,j+r); temp[dim+1]=gauss_(i+c,j+r);
00892           //hist bins are probabilities
00893           //i.e., sum h[i] = 1.0
00894           this->update_hist(temp, weight, h);
00895         }
00896       //finished a block
00897       vcl_map<vcl_string, float> texture_probs = this->texture_probabilities(h);
00898 
00899 #if 0 //=====debug====
00900       int ii = 7518, jj = 2909;
00901       if (required_block(bidxu, bidxv, ii, jj, block_size_, margin)) {
00902         vcl_cout << "probs(" << i << ' ' << j << ")\n";
00903         float psum = 0.0;
00904         for (vcl_map<vcl_string, float>::iterator cit = texture_probs.begin();
00905              cit != texture_probs.end(); ++cit)
00906           psum += (*cit).second;
00907 
00908         for (vcl_map<vcl_string, float>::iterator cit =texture_probs.begin();
00909              cit != texture_probs.end(); ++cit)
00910           vcl_cout << (*cit).first << ' ' << ((*cit).second)/psum << '\n';
00911 #ifdef DEBUG
00912         vcl_cout << " hist\n";
00913         for (unsigned i = 0; i<nh; ++i)
00914           vcl_cout << h[i]<< '\n';
00915 #endif
00916       }
00917 #endif
00918       vnl_vector_fixed<float, 3> color;
00919       //colorize output
00920       this->category_color_mix(texture_probs, color);
00921       for (unsigned r = 0; r<block_size_; ++r)
00922         for (unsigned c = 0; c<block_size_; ++c)
00923           for (unsigned b = 0; b<3; ++b)
00924             prob(i+c,j+r,b) = color[b];
00925     }
00926     vcl_cout << '.' << vcl_flush;
00927   }
00928   vcl_cout << "\nBlock classification took " << t.real()/1000.0 << " seconds\n" << vcl_flush;
00929   return prob;
00930 }
00931 
00932 unsigned sdet_texture_classifier::max_filter_radius() const
00933 {
00934   unsigned maxr = filter_responses_.invalid_border();
00935   unsigned lapr = gauss_radius(laplace_radius_, cutoff_per_);
00936   unsigned gr = gauss_radius(gauss_radius_, cutoff_per_);
00937   if (lapr>maxr) maxr = lapr;
00938   if (gr>maxr) maxr = gr;
00939   return maxr;
00940 }
00941 
00942 // === Binary I/O ===
00943 
00944 //dummy vsl io functions to allow sdet_texture_classifier to be inserted into
00945 //brdb as a dbvalue
00946 void vsl_b_write(vsl_b_ostream & os, sdet_texture_classifier const &tc)
00947 { /* do nothing */ }
00948 void vsl_b_read(vsl_b_istream & is, sdet_texture_classifier &tc)
00949 { /* do nothing */ }
00950 void vsl_print_summary(vcl_ostream &os, const sdet_texture_classifier &tc)
00951 { /* do nothing */ }
00952 void vsl_b_read(vsl_b_istream& is, sdet_texture_classifier* tc)
00953 { /* do nothing */ }
00954 void vsl_b_write(vsl_b_ostream& os, const sdet_texture_classifier* &tc)
00955 { /* do nothing */ }
00956 void vsl_print_summary(vcl_ostream& os, const sdet_texture_classifier* &tc)
00957 { /* do nothing */ }
00958 void vsl_b_read(vsl_b_istream& is, sdet_texture_classifier_sptr& tc)
00959 { /* do nothing */ }
00960 void vsl_b_write(vsl_b_ostream& os, const sdet_texture_classifier_sptr &tc)
00961 { /* do nothing */ }
00962 void vsl_print_summary(vcl_ostream& os, const sdet_texture_classifier_sptr &tc)
00963 { /* do nothing */ }