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