1 #include <iostream>
2 #include <cstdlib>
3 #include "msm_shape_model_builder.h"
4 //:
5 // \file
6 // \brief Object to build a msm_shape_model
7 // \author Tim Cootes
8 
9 #include "vsl/vsl_indent.h"
10 #include "vsl/vsl_binary_io.h"
11 #include <vnl/io/vnl_io_vector.h>
12 #include <vnl/io/vnl_io_matrix.h>
13 #ifdef _MSC_VER
14 #  include "vcl_msvc_warnings.h"
15 #endif
16 #include <mbl/mbl_data_array_wrapper.h>
17 #include <mcal/mcal_pca.h>
18 #include <mcal/mcal_extract_mode.h>
19 #include <mbl/mbl_exception.h>
20 #include <mbl/mbl_read_props.h>
21 #include <mbl/mbl_parse_block.h>
22 #include "vul/vul_string.h"
23 
24 //=======================================================================
25 // Dflt ctor
26 //=======================================================================
27 
28 msm_shape_model_builder::msm_shape_model_builder()
29 
30     = default;
31 
32 //=======================================================================
33 // Destructor
34 //=======================================================================
35 
36 msm_shape_model_builder::~msm_shape_model_builder() = default;
37 
38 //: Set up model
set_aligner(const msm_aligner & aligner)39 void msm_shape_model_builder::set_aligner(
40            const msm_aligner& aligner)
41 {
42   aligner_ = aligner;
43 }
44 
45 //: Define parameter limiter.
set_param_limiter(const msm_param_limiter & p)46 void msm_shape_model_builder::set_param_limiter(const msm_param_limiter& p)
47 {
48   param_limiter_=p;
49 }
50 
set_mode_choice(unsigned min,unsigned max,double var_proportion)51 void msm_shape_model_builder::set_mode_choice(unsigned min, unsigned max,
52                                               double var_proportion)
53 {
54   min_modes_ = min;
55   max_modes_ = max;
56   var_prop_ = var_proportion;
57 }
58 
59 //: Define how to compute alignment of reference shape
set_ref_pose_source(msm_aligner::ref_pose_source rps)60 void msm_shape_model_builder::set_ref_pose_source(msm_aligner::ref_pose_source rps)
61 {
62   ref_pose_source_=rps;
63 }
64 
65 //: Builds the model from the supplied examples
build_model(const std::vector<msm_points> & shapes,msm_shape_model & shape_model)66 void msm_shape_model_builder::build_model(
67                    const std::vector<msm_points>& shapes,
68                    msm_shape_model& shape_model)
69 {
70   // Align shapes and estimate mean pose
71   msm_points ref_mean_shape;
72   std::vector<vnl_vector<double> > pose_to_ref;
73   vnl_vector<double> average_pose;
74   aligner().align_set(shapes,ref_mean_shape,pose_to_ref,average_pose,ref_pose_source_);
75 
76   // Generate vectors corresponding to aligned shapes
77   unsigned n = shapes.size();
78   std::vector<vnl_vector<double> > aligned_shape_vec(n);
79   msm_points aligned_shape;
80   for (unsigned i=0;i<n;++i)
81   {
82     aligner().apply_transform(shapes[i],pose_to_ref[i],aligned_shape);
83     aligned_shape_vec[i]=aligned_shape.vector();
84   }
85 
86   mcal_pca pca;
87   pca.set_mode_choice(min_modes_,max_modes_,var_prop_);
88 
89   vnl_matrix<double> modes;
90   vnl_vector<double> mode_var;
91 
92   mbl_data_array_wrapper<vnl_vector<double> >
93     data(&aligned_shape_vec[0],n);
94 
95   pca.build_about_mean(data,ref_mean_shape.vector(),
96                        modes,mode_var);
97 
98   param_limiter_->set_param_var(mode_var);
99 
100   shape_model.set(ref_mean_shape,modes,mode_var,average_pose,
101                   aligner(),param_limiter());
102 }
103 
104 //: Each point p controls two elements 2p,2p+1
msm_elements_from_pts_used(const std::vector<std::vector<unsigned>> & pts_used,std::vector<std::vector<unsigned>> & used)105 inline void msm_elements_from_pts_used(
106                    const std::vector<std::vector<unsigned> >& pts_used,
107                    std::vector<std::vector<unsigned> >& used)
108 {
109   used.resize(pts_used.size());
110   for (unsigned i=0;i<pts_used.size();++i)
111   {
112     used[i].empty();
113     used[i].reserve(2*pts_used[i].size());
114     for (unsigned j=0;j<pts_used[i].size();++j)
115     {
116       used[i].push_back(2*pts_used[i][j]);
117       used[i].push_back(2*pts_used[i][j]+1);
118     }
119   }
120 }
121 
122 //: Builds the model, using subsets of elements for some modes
123 //  Builds a shape model, allowing control of which elements may
124 //  be varied in some of the modes.  This allows construction
125 //  of models where some groups of points are semi-independent
126 //  of the others.
127 //  \param pts_used[i] indicates the set of elements to be used for
128 //  mode i (or all if \p pts_used[i] is empty).
129 //  Modes beyond \p pts_used.size() will use all elements.
130 //  Builds at least \p pts_used.size() modes. Number defined by
131 //  max_modes and var_prop.
build_model(const std::vector<msm_points> & shapes,const std::vector<std::vector<unsigned>> & pts_used,msm_shape_model & shape_model)132 void msm_shape_model_builder::build_model(
133                   const std::vector<msm_points>& shapes,
134                   const std::vector<std::vector<unsigned> >& pts_used,
135                   msm_shape_model& shape_model)
136 {
137   // Align shapes and estimate mean pose
138   msm_points ref_mean_shape;
139   std::vector<vnl_vector<double> > pose_to_ref;
140   vnl_vector<double> average_pose;
141   aligner().align_set(shapes,ref_mean_shape,pose_to_ref,average_pose,ref_pose_source_);
142 
143   // Generate vectors corresponding to aligned shapes
144   unsigned n = shapes.size();
145   std::vector<vnl_vector<double> > dshape_vec(n);
146   msm_points aligned_shape;
147   for (unsigned i=0;i<n;++i)
148   {
149     aligner().apply_transform(shapes[i],pose_to_ref[i],aligned_shape);
150     dshape_vec[i]=aligned_shape.vector()-ref_mean_shape.vector();
151   }
152 
153   // Set up indication for which elements to be used
154   // pt i corresponds to elements 2i,2i+1
155   std::vector<std::vector<unsigned> > used;
156   msm_elements_from_pts_used(pts_used,used);
157 
158   vnl_matrix<double> modes;
159   vnl_vector<double> mode_var;
160 
161   mcal_extract_modes(dshape_vec,used,
162                      max_modes_,var_prop_,
163                      modes,mode_var);
164 
165   param_limiter_->set_param_var(mode_var);
166 
167   shape_model.set(ref_mean_shape,modes,mode_var,average_pose,
168                   aligner(),param_limiter());
169 }
170 
171 //: Builds the model from the points loaded from given files
build_from_files(const std::string & points_dir,const std::vector<std::string> & filenames,msm_shape_model & shape_model)172 void msm_shape_model_builder::build_from_files(
173                    const std::string& points_dir,
174                    const std::vector<std::string>& filenames,
175                    msm_shape_model& shape_model)
176 {
177   std::vector<msm_points> shapes(filenames.size());
178   msm_load_shapes(points_dir,filenames,shapes);
179   build_model(shapes,shape_model);
180 }
181 
182 //: Counts number of examples with each class ID
183 //  Assumes IDs run from 0.  Ignores any elements with \p id[i]<0.
184 //  On exit n_per_class[j] indicates number of class j
185 //  It is resized to cope with the largest ID number. Some elements
186 //  may be zero.
msm_count_classes(const std::vector<int> & id,std::vector<unsigned> & n_per_class)187 static void msm_count_classes(const std::vector<int>& id,
188                               std::vector<unsigned>& n_per_class)
189 {
190   int max_id = 0;
191   for (int i : id)
192     if (i>max_id) max_id=i;
193 
194   n_per_class.resize(1+max_id,0u);
195 
196   for (int i : id)
197     if (i>=0) n_per_class[i]++;
198 }
199 
200 
201 //: Builds shape model from within-class variation
202 //  \param shape[i] belongs to class \p id[i].
203 //  Aligns all shapes to a common mean.
204 //  Computes the average covariance about each class mean,
205 //  and builds shape modes from this.
206 //
207 //  If \p id[i]<0, then shape is
208 //  used for building global mean, but not for within class model.
build_within_class_model(const std::vector<msm_points> & shapes,const std::vector<int> & id,msm_shape_model & shape_model)209 void msm_shape_model_builder::build_within_class_model(
210                    const std::vector<msm_points>& shapes,
211                    const std::vector<int>& id,
212                    msm_shape_model& shape_model)
213 {
214   // Align shapes and estimate mean pose
215   msm_points ref_mean_shape;
216   std::vector<vnl_vector<double> > pose_to_ref;
217   vnl_vector<double> average_pose;
218   aligner().align_set(shapes,ref_mean_shape,pose_to_ref,average_pose,ref_pose_source_);
219 
220   std::vector<unsigned> n_per_class;
221   msm_count_classes(id,n_per_class);
222   std::vector<vnl_vector<double> > class_mean(n_per_class.size());
223 
224   // Initialise sums for class means
225   for (unsigned j=0;j<n_per_class.size();++j)
226     if (n_per_class[j]>0)
227     {
228       class_mean[j].set_size(2*ref_mean_shape.size());
229       class_mean[j].fill(0.0);
230     }
231 
232   // Generate vectors corresponding to aligned shapes
233   unsigned n = shapes.size();
234   std::vector<vnl_vector<double> > dshape_vec;
235   std::vector<int> valid_id;
236   dshape_vec.reserve(n);
237   valid_id.reserve(n);
238 
239   msm_points aligned_shape;
240   for (unsigned i=0;i<n;++i)
241   {
242     if (id[i]<0) continue;  // Ignore unknown class id
243     aligner().apply_transform(shapes[i],pose_to_ref[i],aligned_shape);
244     dshape_vec.push_back(aligned_shape.vector());
245     valid_id.push_back(id[i]);
246 
247     class_mean[id[i]]+=aligned_shape.vector();
248   }
249 
250   // Compute the mean for each class from the sums
251   for (unsigned j=0;j<n_per_class.size();++j)
252     if (n_per_class[j]>0) class_mean[j]/=n_per_class[j];
253 
254   // Remove mean from each example
255   for (unsigned i=0;i<dshape_vec.size();++i)
256     dshape_vec[i]-=class_mean[valid_id[i]];
257 
258   // Vectors are now about a zero mean.
259   // Apply PCA to this data to compute the modes.
260   mcal_pca pca;
261   pca.set_mode_choice(min_modes_,max_modes_,var_prop_);
262 
263   vnl_matrix<double> modes;
264   vnl_vector<double> mode_var;
265 
266   mbl_data_array_wrapper<vnl_vector<double> > data(dshape_vec);
267   vnl_vector<double> zero_mean(2*ref_mean_shape.size(),0.0);
268 
269   pca.build_about_mean(data,zero_mean, modes,mode_var);
270 
271   param_limiter_->set_param_var(mode_var);
272 
273   shape_model.set(ref_mean_shape,modes,mode_var,average_pose,
274                   aligner(),param_limiter());
275 }
276 
277 //: Builds shape model from within-class variation
278 //  \param shape[i] belongs to class \p id[i].
279 //  Aligns all shapes to a common mean.
280 //  Computes the average covariance about each class mean,
281 //  and builds shape modes from this.
282 //
283 //  If \p id[i]<0, then shape is
284 //  used for building global mean, but not for within class model.
285 //
286 //  \param pts_used[i] indicates which points will be controlled by mode i.
build_within_class_model(const std::vector<msm_points> & shapes,const std::vector<int> & id,const std::vector<std::vector<unsigned>> & pts_used,msm_shape_model & shape_model)287 void msm_shape_model_builder::build_within_class_model(
288                    const std::vector<msm_points>& shapes,
289                    const std::vector<int>& id,
290                    const std::vector<std::vector<unsigned> >& pts_used,
291                    msm_shape_model& shape_model)
292 {
293   // Align shapes and estimate mean pose
294   msm_points ref_mean_shape;
295   std::vector<vnl_vector<double> > pose_to_ref;
296   vnl_vector<double> average_pose;
297   aligner().align_set(shapes,ref_mean_shape,pose_to_ref,average_pose,ref_pose_source_);
298 
299   std::vector<unsigned> n_per_class;
300   msm_count_classes(id,n_per_class);
301   std::vector<vnl_vector<double> > class_mean(n_per_class.size());
302 
303   // Initialise sums for class means
304   for (unsigned j=0;j<n_per_class.size();++j)
305     if (n_per_class[j]>0)
306     {
307       class_mean[j].set_size(2*ref_mean_shape.size());
308       class_mean[j].fill(0.0);
309     }
310 
311   // Generate vectors corresponding to aligned shapes
312   unsigned n = shapes.size();
313   std::vector<vnl_vector<double> > dshape_vec;
314   std::vector<int> valid_id;
315   dshape_vec.reserve(n);
316   valid_id.reserve(n);
317 
318   msm_points aligned_shape;
319   for (unsigned i=0;i<n;++i)
320   {
321     if (id[i]<0) continue;  // Ignore unknown class id
322     aligner().apply_transform(shapes[i],pose_to_ref[i],aligned_shape);
323     dshape_vec.push_back(aligned_shape.vector());
324     valid_id.push_back(id[i]);
325 
326     class_mean[id[i]]+=aligned_shape.vector();
327   }
328 
329   // Compute the mean for each class from the sums
330   for (unsigned j=0;j<n_per_class.size();++j)
331     if (n_per_class[j]>0) class_mean[j]/=n_per_class[j];
332 
333   // Remove mean from each example
334   for (unsigned i=0;i<dshape_vec.size();++i)
335     dshape_vec[i]-=class_mean[valid_id[i]];
336 
337   // Set up indication for which elements to be used
338   // pt i corresponds to elements 2i,2i+1
339   std::vector<std::vector<unsigned> > used;
340   msm_elements_from_pts_used(pts_used,used);
341 
342   vnl_matrix<double> modes;
343   vnl_vector<double> mode_var;
344 
345   mcal_extract_modes(dshape_vec,used,
346                      max_modes_,var_prop_,
347                      modes,mode_var);
348 
349   param_limiter_->set_param_var(mode_var);
350 
351   shape_model.set(ref_mean_shape,modes,mode_var,average_pose,
352                   aligner(),param_limiter());
353 }
354 
355 
356 //: Loads all shapes from \p points_dir/filenames[i].
357 //  Throws mbl_exception_parse_error if fails.
msm_load_shapes(const std::string & points_dir,const std::vector<std::string> & filenames,std::vector<msm_points> & shapes)358 void msm_load_shapes(const std::string& points_dir,
359                      const std::vector<std::string>& filenames,
360                      std::vector<msm_points>& shapes)
361 {
362   unsigned n=filenames.size();
363   shapes.resize(n);
364   for (unsigned i=0;i<n;++i)
365   {
366     std::string path = points_dir+"/"+filenames[i];
367     if (!shapes[i].read_text_file(path))
368     {
369       mbl_exception_parse_error x("Failed to load points from "+path);
370       mbl_exception_error(x);
371     }
372   }
373 }
374 
375 //: Initialise from a text stream.
config_from_stream(std::istream & is)376 void msm_shape_model_builder::config_from_stream(std::istream &is)
377 {
378   std::string s = mbl_parse_block(is);
379 
380   std::istringstream ss(s);
381   mbl_read_props_type props = mbl_read_props_ws(ss);
382 
383   std::string aligner_str = props.get_required_property("aligner");
384   std::stringstream aligner_ss(aligner_str);
385   std::unique_ptr<msm_aligner> aligner=msm_aligner::create_from_stream(aligner_ss);
386   aligner_=aligner->clone();
387 
388   std::string param_limiter_str
389       = props.get_optional_property("param_limiter",
390                                     "msm_ellipsoid_limiter { accept_prop: 0.98 }");
391   if (param_limiter_str!="-")
392   {
393     std::stringstream ss(param_limiter_str);
394 
395     std::unique_ptr<msm_param_limiter> param_limiter;
396     param_limiter = msm_param_limiter::create_from_stream(ss);
397     param_limiter_ = param_limiter->clone();
398   }
399 
400   min_modes_=vul_string_atoi(props.get_optional_property("min_modes","0"));
401   max_modes_=vul_string_atoi(props.get_optional_property("max_modes","999"));
402   var_prop_=vul_string_atof(props.get_optional_property("var_prop","0.98"));
403 
404   std::string rps_str = props.get_optional_property("ref_pose_source","first_shape");
405   if (rps_str=="first_shape") ref_pose_source_=msm_aligner::first_shape;
406   else
407   if (rps_str=="mean_pose") ref_pose_source_=msm_aligner::mean_pose;
408   else
409   {
410     mbl_exception_parse_error x("Unknown ref_pose_source: "+rps_str);
411     mbl_exception_error(x);
412   }
413 
414   mbl_read_props_look_for_unused_props(
415       "msm_shape_model_builder::config_from_stream", props, mbl_read_props_type());
416 }
417 
418 
419 //=======================================================================
420 // Method: version_no
421 //=======================================================================
422 
version_no() const423 short msm_shape_model_builder::version_no() const
424 {
425   return 2;
426 }
427 
428 //=======================================================================
429 // Method: is_a
430 //=======================================================================
431 
is_a() const432 std::string msm_shape_model_builder::is_a() const
433 {
434   return std::string("msm_shape_model_builder");
435 }
436 
437 //=======================================================================
438 // Method: print
439 //=======================================================================
440 
441   // required if data is present in this class
print_summary(std::ostream & os) const442 void msm_shape_model_builder::print_summary(std::ostream& os) const
443 {
444   os<<'\n'<<vsl_indent()<<"aligner: ";
445   if (aligner_.isDefined()) os<<aligner_; else os<<"-";
446   os<<vsl_indent()<< "param_limiter: ";
447   if (param_limiter_.isDefined())
448     os<<param_limiter_; else os<<"-";
449   os<<vsl_indent()<<"min_modes: "<<min_modes_<<'\n'
450     <<vsl_indent()<<"max_modes: "<<max_modes_<<'\n'
451     <<vsl_indent()<<"var_prop: "<<var_prop_<<'\n';
452   os<<vsl_indent()<<"ref_pose_source: ";
453   switch (ref_pose_source_)
454   {
455     case (msm_aligner::first_shape): os<<"first_shape"; break;
456     case (msm_aligner::mean_pose): os<<"mean_pose"; break;
457     default: os<<"unknown";
458   }
459 }
460 
461 //=======================================================================
462 // Method: save
463 //=======================================================================
464 
465   // required if data is present in this class
b_write(vsl_b_ostream & bfs) const466 void msm_shape_model_builder::b_write(vsl_b_ostream& bfs) const
467 {
468   vsl_b_write(bfs,version_no());
469   vsl_b_write(bfs,aligner_);
470   vsl_b_write(bfs,param_limiter_);
471   vsl_b_write(bfs,min_modes_);
472   vsl_b_write(bfs,max_modes_);
473   vsl_b_write(bfs,var_prop_);
474   vsl_b_write(bfs,short(ref_pose_source_));
475 }
476 
477 //=======================================================================
478 // Method: load
479 //=======================================================================
480 
481   // required if data is present in this class
b_read(vsl_b_istream & bfs)482 void msm_shape_model_builder::b_read(vsl_b_istream& bfs)
483 {
484   short version;
485   vsl_b_read(bfs,version);
486   short rps;
487   switch (version)
488   {
489     case (1):
490     case (2):
491       vsl_b_read(bfs,aligner_);
492       vsl_b_read(bfs,param_limiter_);
493       vsl_b_read(bfs,min_modes_);
494       vsl_b_read(bfs,max_modes_);
495       vsl_b_read(bfs,var_prop_);
496       if (version==1) ref_pose_source_=msm_aligner::first_shape;
497       else
498       {
499         vsl_b_read(bfs,rps); ref_pose_source_=msm_aligner::ref_pose_source(rps);
500       }
501       break;
502     default:
503       std::cerr << "msm_shape_model_builder::b_read() :\n"
504                << "Unexpected version number " << version << std::endl;
505       bfs.is().clear(std::ios::badbit); // Set an unrecoverable IO error on stream
506       return;
507   }
508 }
509 
510 
511 //=======================================================================
512 // Associated function: operator<<
513 //=======================================================================
514 
vsl_b_write(vsl_b_ostream & bfs,const msm_shape_model_builder & b)515 void vsl_b_write(vsl_b_ostream& bfs, const msm_shape_model_builder& b)
516 {
517   b.b_write(bfs);
518 }
519 
520 //=======================================================================
521 // Associated function: operator>>
522 //=======================================================================
523 
vsl_b_read(vsl_b_istream & bfs,msm_shape_model_builder & b)524 void vsl_b_read(vsl_b_istream& bfs, msm_shape_model_builder& b)
525 {
526   b.b_read(bfs);
527 }
528 
529 //=======================================================================
530 // Associated function: operator<<
531 //=======================================================================
532 
operator <<(std::ostream & os,const msm_shape_model_builder & b)533 std::ostream& operator<<(std::ostream& os,const msm_shape_model_builder& b)
534 {
535   os << b.is_a() << ": ";
536   vsl_indent_inc(os);
537   b.print_summary(os);
538   vsl_indent_dec(os);
539   return os;
540 }
541 
542 //: Stream output operator for class reference
vsl_print_summary(std::ostream & os,const msm_shape_model_builder & b)543 void vsl_print_summary(std::ostream& os,const msm_shape_model_builder& b)
544 {
545  os << b;
546 }
547