1 //:
2 // \file
3 // \brief Tool to combine point annotations from three or more sources.
4 // \author Tim Cootes
5 // Reads in multiple annotations of the same set of images.
6 // Compares the points from different annotators.
7 // Use a robust estimate (trimmed mean) of true point
8 
9 #include <sstream>
10 #include <iostream>
11 #include <fstream>
12 #include <string>
13 #include <iterator>
14 #include <mbl/mbl_read_props.h>
15 #include <mbl/mbl_exception.h>
16 #include <mbl/mbl_parse_int_list.h>
17 #include <mbl/mbl_parse_string_list.h>
18 #include <mbl/mbl_parse_colon_pairs_list.h>
19 #include "vul/vul_arg.h"
20 #include "vul/vul_string.h"
21 #ifdef _MSC_VER
22 #  include "vcl_msvc_warnings.h"
23 #endif
24 #include "vsl/vsl_quick_file.h"
25 
26 #include <msm/msm_points.h>
27 #include <mbl/mbl_stats_nd.h>
28 #include <mbl/mbl_sample_stats_1d.h>
29 #include "vul/vul_file.h"
30 
31 /*
32 Points from different annotators are assumed to have the same filenames, but
33 live in separate directories.
34 
35 Parameter file format:
36 <START FILE>
37 // Directories containing points from each annotator
38 points_dirs: {
39 /home/points1/
40 /home/points2/
41 }
42 
43 // Text for key
44 key_text: { Marker1   Marker2 }
45 
46 //: Directory to which to save resulting point files
47 output_dir: average_results
48 
49 //: Number of points to keep to compute trimmed mean
50 n_to_keep=3;
51 
52 //: When defined, only work on listed points
53 points_to_use: { 3 7 12 15 }
54 
55 image_dir: /home/images/
56 images: {
57   image1.pts : image1.jpg
58   image2.pts : image2.jpg
59 }
60 
61 <END FILE>
62 */
63 
print_usage()64 void print_usage()
65 {
66   std::cout << "msm_robust_mean -p param_file\n"
67            << "Tool to combine point annotations from three or more sources..\n"
68            << std::endl;
69 
70   vul_arg_display_usage_and_exit();
71 }
72 
73 //: Structure to hold parameters
74 struct tool_params
75 {
76   //: Number of points to keep to compute trimmed mean
77   unsigned n_to_keep;
78 
79   //: When defined, only display listed points
80   std::vector<unsigned> points_to_use;
81 
82   //: Directory containing images
83   std::string image_dir;
84 
85   //: Directories containing points
86   std::vector<std::string> points_dir;
87 
88   //: Text for key
89   std::vector<std::string> key_text;
90 
91   //: Directory to which to save resulting point files
92   std::string output_dir;
93 
94   //: List of image names
95   std::vector<std::string> image_names;
96 
97   //: List of points file names
98   std::vector<std::string> points_names;
99 
100   //: Parse named text file to read in data
101   //  Throws a mbl_exception_parse_error if fails
102   void read_from_file(const std::string& path);
103 };
104 
105 //: Parse named text file to read in data
106 //  Throws a mbl_exception_parse_error if fails
read_from_file(const std::string & path)107 void tool_params::read_from_file(const std::string& path)
108 {
109   std::ifstream ifs(path.c_str());
110   if (!ifs)
111   {
112     std::string error_msg = "Failed to open file: "+path;
113     throw (mbl_exception_parse_error(error_msg));
114   }
115 
116   mbl_read_props_type props = mbl_read_props_ws(ifs);
117 
118   output_dir=props.get_optional_property("output_dir","robust_mean");
119   n_to_keep=vul_string_atoi(props.get_optional_property("n_to_keep","0"));
120 
121   std::string points_dirs_str=props.get_optional_property("points_dirs","");
122   if (!points_dirs_str.empty()) {
123     mbl_parse_string_list(points_dirs_str,points_dir);
124   } else {
125     // Assume exactly two directories.
126     points_dir.resize(2);
127     points_dir[0]=props.get_required_property("points_dir1");
128     points_dir[1]=props.get_required_property("points_dir2");
129   }
130 
131   std::string key_text_str=props.get_optional_property("key_text","");
132   if (!key_text_str.empty())
133     mbl_parse_string_list(key_text_str,key_text);
134 
135   if (!key_text.empty() && key_text.size() != points_dir.size())
136     std::cerr<<"WARNING: "
137       <<"Number of key text lines does not match number of data sets"<<std::endl;
138 
139   std::string points_to_use_str=props.get_optional_property("points_to_use","");
140   if (!points_to_use_str.empty()) {
141     std::stringstream ss(points_to_use_str);
142     mbl_parse_int_list(ss,std::back_inserter(points_to_use),unsigned());
143   }
144 
145   image_dir=props.get_optional_property("image_dir","./");
146 
147   mbl_parse_colon_pairs_list(props.get_required_property("images"),
148                              points_names,image_names);
149 
150   // Don't look for unused props so can use a single common parameter file.
151 }
152 
load_shapes(const std::string & points_dir,const std::vector<std::string> & filenames,std::vector<msm_points> & shapes)153 void load_shapes(const std::string& points_dir,
154                  const std::vector<std::string>& filenames,
155                  std::vector<msm_points>& shapes)
156 {
157   unsigned n=filenames.size();
158 
159   shapes.resize(n);
160   for (unsigned i=0;i<n;++i)
161   {
162     std::string path = points_dir+"/"+filenames[i];
163     if (!shapes[i].read_text_file(path))
164     {
165       mbl_exception_parse_error x("Failed to load points from "+path);
166       mbl_exception_error(x);
167     }
168 
169     if (shapes[i].size()!=shapes[0].size())
170     {
171       std::cerr<<"WARNING: "<<path<<" has different number of points ("
172               <<shapes[i].size()<<") to first set ("<<shapes[0].size()<<")"<<std::endl;
173     }
174   }
175 }
176 
177 //: Compute mean of points on j-th example
mean_shape(const std::vector<std::vector<msm_points>> & points,unsigned j)178 msm_points mean_shape(const std::vector<std::vector<msm_points> >& points, unsigned j)
179 {
180   unsigned n_sets = points.size();
181   msm_points mean=points[0][j];
182   for (unsigned i=1;i<n_sets;++i) mean.vector()+=points[i][j].vector();
183   mean.vector()/=n_sets;
184   return mean;
185 }
186 
187 //: Compute running estimate of mean and covariance matrix
188 class mbm_covar_stats_2d
189 {
190   public:
191     double sum1{0}, sum2{0};
192     double sum11{0}, sum12{0}, sum22{0};
193     unsigned n{0};
194 
195     mbm_covar_stats_2d() = default;
196 
197     //: Add 2D observation
obs(double x,double y)198     void obs(double x, double y) {
199       sum1 += x;
200       sum2 += y;
201       sum11 += x * x;
202       sum12 += x * y;
203       sum22 += y * y;
204       n++;
205   }
206 
obs(vgl_point_2d<double> p)207   void obs(vgl_point_2d<double> p) { obs(p.x(),p.y()); }
obs(vgl_vector_2d<double> p)208   void obs(vgl_vector_2d<double> p) { obs(p.x(),p.y()); }
209 
n_obs() const210   unsigned n_obs() const { return n; }
211 
mean_x() const212   double mean_x() const { return sum1/n; }
mean_y() const213   double mean_y() const { return sum2/n; }
mean() const214   vgl_point_2d<double> mean() const { return {mean_x(),mean_y()}; }
215 
var11() const216   double var11() const { return sum11/n-mean_x()*mean_x(); }
var12() const217   double var12() const { return sum12/n-mean_x()*mean_y(); }
var22() const218   double var22() const { return sum22/n-mean_y()*mean_y(); }
219 
220   //: Calculate eigenvalues of the covariance matrix and angle of evector 1
eigen_values(double & eval1,double & eval2,double & A) const221   void eigen_values(double& eval1, double& eval2, double& A) const
222   {
223     double dac=var11()-var22();
224     double v12=var12();
225     double d=0.5*std::sqrt(dac*dac+4*v12*v12);
226     double hac = 0.5*(var11()+var22());
227     eval1=hac+d;
228     eval2=hac-d;
229 
230     A = std::atan2(eval1-var11(),var12());
231   }
232 
det() const233   double det() const { return var11()*var22()-var12()*var12(); }
234 };
235 
wtd_mean(const std::vector<vgl_point_2d<double>> & pts,const std::vector<double> & w)236 vgl_point_2d<double> wtd_mean(const std::vector<vgl_point_2d<double> >& pts,
237                               const std::vector<double>& w)
238 {
239   double sum_x=0,sum_y=0,sum_w=0;
240   for (unsigned i=0;i<pts.size();++i)
241   {
242     sum_x += w[i]*pts[i].x();
243     sum_y += w[i]*pts[i].y();
244     sum_w += w[i];
245   }
246   return vgl_point_2d<double>(sum_x/sum_w,sum_y/sum_w);
247 }
248 
249 
robust_mean(const std::vector<vgl_point_2d<double>> & pts,unsigned n_to_discard)250 vgl_point_2d<double> robust_mean(const std::vector<vgl_point_2d<double> >& pts,
251                                  unsigned n_to_discard)
252 {
253   unsigned n=pts.size();
254   std::vector<double> w(n,1.0);
255 
256   vgl_point_2d<double> mean=wtd_mean(pts,w);
257 
258   // Remove the furthest points, one at a time (trimmed mean)
259   for (unsigned j=0;j<n_to_discard;++j)
260   {
261     unsigned ind;
262     double max_d2=0;
263     for (unsigned i=0;i<pts.size();++i)
264     {
265       if (w[i]==0) continue; // Ignore discarded points
266       double d2=(pts[i]-mean).sqr_length();
267       if (d2>max_d2) { max_d2=d2; ind=i; }
268     }
269 
270     w[ind]=0;
271     mean=wtd_mean(pts,w);
272   }
273 
274 
275   return mean;
276 }
277 
main(int argc,char ** argv)278 int main(int argc, char** argv)
279 {
280   vul_arg<std::string> param_path("-p","Parameter filename");
281   vul_arg<std::string> out_dir("-o","Output directory","");
282   vul_arg_parse(argc,argv);
283 
284   if (param_path().empty()) {
285     print_usage();
286     return 0;
287   }
288 
289   tool_params params;
290   try
291   {
292     params.read_from_file(param_path());
293   }
294   catch (mbl_exception_parse_error& e)
295   {
296     std::cerr<<"Error: "<<e.what()<<'\n';
297     return 1;
298   }
299 
300   if (!out_dir().empty())
301     params.output_dir=out_dir();
302 
303   unsigned n_sets = params.points_dir.size();
304 
305   // Load in all points
306   std::vector<std::vector<msm_points> > points(n_sets);
307   for (unsigned i=0;i<n_sets;++i)
308     load_shapes(params.points_dir[i],params.points_names,points[i]);
309 
310   unsigned n_egs=points[0].size();
311 
312   // Compute mean of points on first image to use as a reference.
313   msm_points ref_shape=mean_shape(points,0);
314 
315   unsigned n_pts = points[0][0].size();
316   std::vector<msm_points> mean_points(n_egs); // For results.
317 
318   std::vector<vgl_point_2d<double> > pts(n_sets);
319 
320   unsigned n_to_discard=0;
321   if (params.n_to_keep>0) n_to_discard=n_sets-params.n_to_keep;
322 
323   if (params.points_to_use.empty()) {
324     // Use all points
325     params.points_to_use.resize(n_pts);
326     for (unsigned k=0;k<n_pts;++k)
327       params.points_to_use[k]=k;
328   }
329 
330   unsigned n_used=params.points_to_use.size();
331 
332   for (unsigned j=0;j<n_egs;++j)
333   {
334     mean_points[j].set_size(n_used);
335     for (unsigned k=0;k<n_used;++k)
336     {
337       for (unsigned i=0;i<n_sets;++i)
338         pts[i]=points[i][j][params.points_to_use[k]];
339 
340       mean_points[j].set_point(k,robust_mean(pts,n_to_discard));
341     }
342   }
343 
344 
345     // check that output directory exists
346   if (!vul_file::is_directory(params.output_dir))
347   {
348     std::cout<<"Directory "<<params.output_dir
349             <<" does not exist. Creating it."<<std::endl;
350     if (!vul_file::make_directory_path(params.output_dir))
351     {
352       std::cerr<<"Unable to create it."<<std::endl;
353       return 1;
354     }
355   }
356 
357   for (unsigned j=0;j<n_egs;++j)
358   {
359     std::string pts_new_path = params.output_dir+"/"+
360                                params.points_names[j];
361     if (!mean_points[j].write_text_file(pts_new_path))
362     {
363         std::cerr<<"Cannot write updated points file to: "
364                  <<params.output_dir<<std::endl;
365         return 2;
366     }
367   }
368 
369   std::cout<<"Saved "<<n_egs<<" points files to "<<params.output_dir<<std::endl;
370 
371   return 0;
372 }
373