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