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