1 #include <cmath>
2 #include <sstream>
3 #include <iostream>
4 #include <algorithm>
5 #include "mfpf_hog_box_finder_builder.h"
6 //:
7 // \file
8 // \brief Builder for mfpf_hog_box_finder objects.
9 // \author Tim Cootes
10 
11 #include <mfpf/mfpf_hog_box_finder.h>
12 #include "vsl/vsl_binary_loader.h"
13 #include "vul/vul_string.h"
14 #include <cassert>
15 
16 #include <mbl/mbl_parse_block.h>
17 #include <mbl/mbl_read_props.h>
18 #if 0
19 #include <mbl/mbl_exception.h>
20 #endif
21 
22 #include "vil/vil_resample_bilin.h"
23 #include "vil/vil_image_view.h"
24 #include "vsl/vsl_vector_io.h"
25 #include "vsl/vsl_indent.h"
26 #ifdef _MSC_VER
27 #  include "vcl_msvc_warnings.h"
28 #endif
29 
30 #include <mipa/mipa_orientation_histogram.h>
31 #include <mipa/mipa_sample_histo_boxes.h>
32 #include <mipa/mipa_identity_normaliser.h>
33 #include <mipa/mipa_block_normaliser.h>
34 #include <mipa/mipa_ms_block_normaliser.h>
35 
36 //: Divide elements of v by sum of last nA elements
37 //  For histogram vectors these are the total sums
mfpf_norm_histo_vec(vnl_vector<double> & v,unsigned nA)38 inline void mfpf_norm_histo_vec(vnl_vector<double>& v, unsigned nA)
39 {
40   unsigned n=v.size();
41   double sum = 0.0;
42   for (unsigned i=1;i<=nA;++i) sum+=v[n-i];
43   v/=sum;
44 }
45 
46 //=======================================================================
47 // Dflt ctor
48 //=======================================================================
49 
mfpf_hog_box_finder_builder()50 mfpf_hog_box_finder_builder::mfpf_hog_box_finder_builder():normaliser_(mipa_identity_normaliser())
51 {
52   set_defaults();
53 }
54 
55 //: Define default values
set_defaults()56 void mfpf_hog_box_finder_builder::set_defaults()
57 {
58   step_size_=1.0;
59   search_ni_=5;
60   search_nj_=5;
61   nA_=0;
62   dA_=0.0;
63 
64   nA_bins_=8;
65   full360_=true;
66   nc_=2;
67   ni_=0;
68   nj_=0;
69   ref_x_=0;
70   ref_y_=0;
71   //norm_method_=0;
72   overlap_f_=1.0;
73 }
74 
75 //=======================================================================
76 // Destructor
77 //=======================================================================
78 
79 mfpf_hog_box_finder_builder::~mfpf_hog_box_finder_builder() = default;
80 
81 //: Create new mfpf_hog_box_finder on heap
new_finder() const82 mfpf_point_finder* mfpf_hog_box_finder_builder::new_finder() const
83 {
84   return new mfpf_hog_box_finder();
85 }
86 
set_angle_bins(unsigned nA_bins,bool full360,unsigned cell_size)87 void mfpf_hog_box_finder_builder::set_angle_bins(unsigned nA_bins,
88                                                  bool full360, unsigned cell_size)
89 {
90   nA_bins_ = nA_bins;
91   full360_  = full360;
92   nc_      = cell_size;
93   reconfigure_normaliser();
94 }
95 
96 //: Define region size in world co-ordinates
97 //  Sets up ROI to cover given box (with samples at step_size()),
98 //  with ref point at centre.
set_region_size(double wi,double wj)99 void mfpf_hog_box_finder_builder::set_region_size(double wi, double wj)
100 {
101   wi/=(2*nc_*step_size());
102   wj/=(2*nc_*step_size());
103   int ni = std::max(2,int(0.99+wi));
104   int nj = std::max(2,int(0.99+wj));
105   set_as_box(unsigned(ni),unsigned(nj),0.5*(ni-1),0.5*(nj-1));
106 }
107 
108 
109 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,double ref_x,double ref_y,const mfpf_vec_cost_builder & builder)110 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
111                                              double ref_x, double ref_y,
112                                              const mfpf_vec_cost_builder& builder)
113 {
114   set_as_box(ni,nj,ref_x,ref_y);
115   cost_builder_ = builder.clone();
116   reconfigure_normaliser();
117 }
118 
119 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,double ref_x,double ref_y)120 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
121                                              double ref_x, double ref_y)
122 {
123   ni_=ni; nj_=nj;
124 
125   ref_x_=ref_x;
126   ref_y_=ref_y;
127   reconfigure_normaliser();
128 }
129 
130 
131 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,const mfpf_vec_cost_builder & builder)132 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
133                                              const mfpf_vec_cost_builder& builder)
134 {
135   set_as_box(ni,nj, 0.5*(ni-1),0.5*(nj-1), builder);
136   reconfigure_normaliser();
137 }
138 
139 
140 //: Initialise building
141 // Must be called before any calls to add_example(...)
clear(unsigned n_egs)142 void mfpf_hog_box_finder_builder::clear(unsigned n_egs)
143 {
144   unsigned n_per_eg = (1+2*nA_);
145   cost_builder().clear(n_egs*n_per_eg);
146 }
147 
148 //: Add one example to the model
add_one_example(const vimt_image_2d_of<float> & image,const vgl_point_2d<double> & p,const vgl_vector_2d<double> & u)149 void mfpf_hog_box_finder_builder::add_one_example(
150                  const vimt_image_2d_of<float>& image,
151                  const vgl_point_2d<double>& p,
152                  const vgl_vector_2d<double>& u)
153 {
154   vgl_vector_2d<double> u1=step_size_*u;
155   vgl_vector_2d<double> v1(-u1.y(),u1.x());
156 
157   assert(image.image().nplanes()==1);
158 
159   // Set up sample area with 1 unit border
160   unsigned sni = 2+2*nc_*ni_;
161   unsigned snj = 2+2*nc_*nj_;
162   vil_image_view<float> sample(sni,snj);
163 
164   const vgl_point_2d<double> p0 = p-(1+nc_*ref_x_)*u1-(1+nc_*ref_y_)*v1;
165 
166   const vimt_transform_2d& s_w2i = image.world2im();
167   vgl_point_2d<double> im_p0 = s_w2i(p0);
168   vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
169   vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
170 
171   vil_resample_bilin(image.image(),sample,
172                      im_p0.x(),im_p0.y(),  im_u.x(),im_u.y(),
173                      im_v.x(),im_v.y(),
174                      sni,snj);
175 
176   vil_image_view<float> histo_im;
177   mipa_orientation_histogram(sample,histo_im,nA_bins_,nc_,full360_);
178 
179   vnl_vector<double> v;
180   mipa_sample_histo_boxes_3L(histo_im,0,0,v,ni_,nj_);
181 
182   normaliser_->normalise(v);
183   //if (norm_method_==1) mfpf_norm_histo_vec(v,nA_bins_);
184 
185   cost_builder().add_example(v);
186 }
187 
188 //: Add one example to the model
add_example(const vimt_image_2d_of<float> & image,const vgl_point_2d<double> & p,const vgl_vector_2d<double> & u)189 void mfpf_hog_box_finder_builder::add_example(const vimt_image_2d_of<float>& image,
190                                               const vgl_point_2d<double>& p,
191                                               const vgl_vector_2d<double>& u)
192 {
193   if (nA_==0)
194   {
195     add_one_example(image,p,u);
196     return;
197   }
198 
199   vgl_vector_2d<double> v(-u.y(),u.x());
200   for (int iA=-int(nA_);iA<=(int)nA_;++iA)
201   {
202     double A = iA*dA_;
203     vgl_vector_2d<double> uA = u*std::cos(A)+v*std::sin(A);
204     add_one_example(image,p,uA);
205   }
206 }
207 
208 //: Build this object from the data supplied in add_example()
build(mfpf_point_finder & pf)209 void mfpf_hog_box_finder_builder::build(mfpf_point_finder& pf)
210 {
211   assert(pf.is_a()=="mfpf_hog_box_finder");
212   auto& rp = static_cast<mfpf_hog_box_finder&>(pf);
213 
214   mfpf_vec_cost *cost = cost_builder().new_cost();
215 
216   cost_builder().build(*cost);
217 
218   rp.set(nA_bins_,full360_,ni_,nj_,nc_,
219          ref_x_,ref_y_,*cost,normaliser_);
220   set_base_parameters(rp);
221   rp.set_overlap_f(overlap_f_);
222 
223 std::cout<<"Model: "<<rp<<std::endl;
224 
225   // Tidy up
226   delete cost;
227 }
228 
229 
230 //=======================================================================
231 // Method: set_from_stream
232 //=======================================================================
233 //: Initialise from a string stream
set_from_stream(std::istream & is)234 bool mfpf_hog_box_finder_builder::set_from_stream(std::istream &is)
235 {
236   // Cycle through string and produce a map of properties
237   std::string s = mbl_parse_block(is);
238   std::istringstream ss(s);
239   mbl_read_props_type props = mbl_read_props_ws(ss);
240 
241   set_defaults();
242 
243   // Extract the properties
244   parse_base_props(props);
245 
246   nc_=vul_string_atoi(props.get_optional_property("nc","2"));
247   nA_bins_=vul_string_atoi(props.get_optional_property("nA_bins","8"));
248   full360_=vul_string_to_bool(props.get_optional_property("full360","true"));
249   ni_=vul_string_atoi(props.get_optional_property("ni","4"));
250   nj_=vul_string_atoi(props.get_optional_property("nj","4"));
251 
252   bool reonfigureNormaliser=false;
253   if (props.find("norm")!=props.end())
254   {
255     std::istringstream ss2(props["norm"]);
256     mbl_read_props_type dummy_extra_props;
257     std::unique_ptr<mipa_vector_normaliser> norm = mipa_vector_normaliser::new_normaliser_from_stream(ss2, dummy_extra_props);
258     normaliser_=norm.release();
259     reonfigureNormaliser=true;
260 #if 0
261     if (props["norm"]=="none") norm_method_=0;
262     else
263     if (props["norm"]=="linear") norm_method_=1;
264     else throw mbl_exception_parse_error("Unknown norm: "+props["norm"]);
265 #endif
266 
267     props.erase("norm");
268   }
269 
270   overlap_f_=vul_string_atof(props.get_optional_property("overlap_f","1.0"));
271 
272   if (props.find("ref_x")!=props.end())
273   {
274     ref_x_=vul_string_atof(props["ref_x"]);
275     props.erase("ref_x");
276   }
277   else ref_x_=0.5*(ni_-1);
278 
279   if (props.find("ref_y")!=props.end())
280   {
281     ref_y_=vul_string_atof(props["ref_y"]);
282     props.erase("ref_y");
283   }
284   else ref_y_=0.5*(nj_-1);
285 
286   if (props.find("nA")!=props.end())
287   {
288     nA_=vul_string_atoi(props["nA"]);
289     props.erase("nA");
290   }
291 
292   if (props.find("dA")!=props.end())
293   {
294     dA_=vul_string_atof(props["dA"]);
295     props.erase("dA");
296   }
297 
298   if (props.find("cost_builder")!=props.end())
299   {
300     std::istringstream b_ss(props["cost_builder"]);
301     std::unique_ptr<mfpf_vec_cost_builder> bb =
302       mfpf_vec_cost_builder::create_from_stream(b_ss);
303     cost_builder_ = bb->clone();
304     props.erase("cost_builder");
305   }
306 
307   //Some classes of normaliser may require reconfiguration (e.g. to pass on the region size)
308   if (reonfigureNormaliser)
309   {
310     reconfigure_normaliser();
311   }
312   // Check for unused props
313   mbl_read_props_look_for_unused_props(
314       "mfpf_hog_box_finder_builder::set_from_stream", props, mbl_read_props_type());
315   return true;
316 }
317 
reconfigure_normaliser()318 void mfpf_hog_box_finder_builder::reconfigure_normaliser()
319 {
320     mipa_vector_normaliser* pNormaliser=normaliser_.ptr();
321     auto* pBlockNormaliser= dynamic_cast<mipa_block_normaliser*>(pNormaliser);
322     if (pBlockNormaliser)
323     {
324       pBlockNormaliser->set_region(2*ni_,2*nj_);
325       pBlockNormaliser->set_nbins(nA_bins_);
326       //Also this builder always uses 2 SIFT scales and a final overall histogram
327       auto* pMSBlockNormaliser= dynamic_cast<mipa_ms_block_normaliser*>(pNormaliser);
328       if (pMSBlockNormaliser)
329       {
330         pMSBlockNormaliser->set_nscales(2);
331         pMSBlockNormaliser->set_include_overall_histogram(true);
332       }
333       else
334       {
335         std::cerr<<"WARNING from fpf_hog_box_finder_builder::reconfigure_normaliser...\n"
336                 <<"The normaliser may not be multi-scale but this HOG Builder uses multi-scale histograms\n";
337       }
338     }
339 }
340 
341 //=======================================================================
342 // Method: is_a
343 //=======================================================================
344 
is_a() const345 std::string mfpf_hog_box_finder_builder::is_a() const
346 {
347   return std::string("mfpf_hog_box_finder_builder");
348 }
349 
350 //: Create a copy on the heap and return base class pointer
clone() const351 mfpf_point_finder_builder* mfpf_hog_box_finder_builder::clone() const
352 {
353   return new mfpf_hog_box_finder_builder(*this);
354 }
355 
356 //=======================================================================
357 // Method: print
358 //=======================================================================
359 
print_summary(std::ostream & os) const360 void mfpf_hog_box_finder_builder::print_summary(std::ostream& os) const
361 {
362   os << "{ "<<'\n';
363   vsl_indent_inc(os);
364   os << vsl_indent()<<"size: " << ni_ << 'x' << nj_
365      << " nc: " << nc_ <<" nA_bins: "<<nA_bins_
366      << " ref_pt: (" << ref_x_ << ',' << ref_y_ << ')' <<'\n';
367   if (full360_) os<<vsl_indent()<<"Angle range: 0-360"<<'\n';
368   else          os<<vsl_indent()<<"Angle range: 0-180"<<'\n';
369   std::cout<<"The HOG's normaliser is:"<<'\n';
370   normaliser_->print_summary(os);
371   //if (norm_method_==0) os<<vsl_indent()<<"norm: none"<<'\n';
372   //else                 os<<vsl_indent()<<"norm: linear"<<'\n';
373   os <<vsl_indent()<< "cost_builder: ";
374   if (cost_builder_.ptr()==nullptr) os << '-'<<'\n';
375   else                       os << cost_builder_<<'\n';
376   os <<vsl_indent()<< "nA: " << nA_ << " dA: " << dA_ << ' '<<'\n'
377      <<vsl_indent();
378   mfpf_point_finder_builder::print_summary(os);
379   os <<'\n'
380      <<vsl_indent()<<"overlap_f: "<<overlap_f_<<'\n';
381   vsl_indent_dec(os);
382   os <<vsl_indent()<< '}';
383 }
384 
385 //: Version number for I/O
version_no() const386 short mfpf_hog_box_finder_builder::version_no() const
387 {
388   return 2;
389 }
390 
b_write(vsl_b_ostream & bfs) const391 void mfpf_hog_box_finder_builder::b_write(vsl_b_ostream& bfs) const
392 {
393   vsl_b_write(bfs,version_no());
394   mfpf_point_finder_builder::b_write(bfs);  // Save base class
395   vsl_b_write(bfs,nc_);
396   vsl_b_write(bfs,ni_);
397   vsl_b_write(bfs,nj_);
398   vsl_b_write(bfs,nA_bins_);
399   vsl_b_write(bfs,full360_);
400   vsl_b_write(bfs,ref_x_);
401   vsl_b_write(bfs,ref_y_);
402   vsl_b_write(bfs,nA_);
403   vsl_b_write(bfs,dA_);
404   vsl_b_write(bfs,cost_builder_);
405   vsl_b_write(bfs,normaliser_);
406   vsl_b_write(bfs,overlap_f_);
407 }
408 
409 //=======================================================================
410 // Method: load
411 //=======================================================================
412 
b_read(vsl_b_istream & bfs)413 void mfpf_hog_box_finder_builder::b_read(vsl_b_istream& bfs)
414 {
415   if (!bfs) return;
416   short version;
417   vsl_b_read(bfs,version);
418   switch (version)
419   {
420     case (1):
421     case (2):
422       mfpf_point_finder_builder::b_read(bfs);  // Load base class
423       vsl_b_read(bfs,nc_);
424       vsl_b_read(bfs,ni_);
425       vsl_b_read(bfs,nj_);
426       vsl_b_read(bfs,nA_bins_);
427       vsl_b_read(bfs,full360_);
428       vsl_b_read(bfs,ref_x_);
429       vsl_b_read(bfs,ref_y_);
430       vsl_b_read(bfs,nA_);
431       vsl_b_read(bfs,dA_);
432       vsl_b_read(bfs,cost_builder_);
433       vsl_b_read(bfs,normaliser_);
434       if (version==1) overlap_f_=1.0;
435       else            vsl_b_read(bfs,overlap_f_);
436       break;
437     default:
438       std::cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&)\n"
439                << "           Unknown version number "<< version << '\n';
440       bfs.is().clear(std::ios::badbit); // Set an unrecoverable IO error on stream
441       return;
442   }
443 }
444