1 #include <string>
2 #include <vector>
3 #include <iostream>
4 #include "testlib/testlib_test.h"
5 #include "vpdl/vpdl_mixture.h"
6 #include "vpdl/vpdl_gaussian.h"
7 #include "vpdl/vpdl_gaussian_sphere.h"
8 #include "vpdl/vpdl_gaussian_indep.h"
9 #include "vnl/vnl_random.h"
10 #ifdef _MSC_VER
11 #  include "vcl_msvc_warnings.h"
12 #endif
13 
14 
15 template <class T>
16 vpdl_gaussian<T, 3>
fit_gaussian(typename std::vector<vnl_vector_fixed<T,3>>::const_iterator begin,typename std::vector<vnl_vector_fixed<T,3>>::const_iterator end)17 fit_gaussian(typename std::vector<vnl_vector_fixed<T, 3>>::const_iterator begin,
18              typename std::vector<vnl_vector_fixed<T, 3>>::const_iterator end)
19 {
20   vnl_vector_fixed<T, 3> mean(T(0));
21   vnl_matrix_fixed<T, 3, 3> covar(T(0));
22   unsigned int count = 0;
23   typedef typename std::vector<vnl_vector_fixed<T, 3>>::const_iterator vitr;
24   for (vitr i = begin; i != end; ++i)
25   {
26     mean += *i;
27     covar += outer_product(*i, *i);
28     ++count;
29   }
30   mean /= T(count);
31   covar /= T(count);
32   covar -= outer_product(mean, mean);
33   return vpdl_gaussian<T, 3>(mean, covar);
34 }
35 
36 
37 // function to sort distributions by increasing weight
38 template <class T, unsigned int n>
39 bool
weight_less(const vpdl_distribution<T,n> &,const T & w1,const vpdl_distribution<T,n> &,const T & w2)40 weight_less(const vpdl_distribution<T, n> & /*d1*/, const T & w1, const vpdl_distribution<T, n> & /*d2*/, const T & w2)
41 {
42   return w1 < w2;
43 }
44 
45 // test the computation of covariance and mean of a mixture
46 // by using mixtures of gaussians obtained from data points
47 template <class T>
48 void
test_covar_mean(T epsilon,const std::string & type_name)49 test_covar_mean(T epsilon, const std::string & type_name)
50 {
51   // an arbitrary collection of data points
52   std::vector<vnl_vector_fixed<T, 3>> data;
53   vnl_random rnd;
54   for (unsigned int i = 0; i < 10; ++i)
55     data.push_back(vnl_vector_fixed<T, 3>(T(rnd.normal64() + 1), T(2 * rnd.normal64()), T(rnd.normal64())));
56   for (unsigned int i = 0; i < 15; ++i)
57     data.push_back(vnl_vector_fixed<T, 3>(T(3 * rnd.normal64() - 1), T(rnd.normal64() + 3), T(1.5 * rnd.normal64())));
58   for (unsigned int i = 0; i < 10; ++i)
59     data.push_back(vnl_vector_fixed<T, 3>(T(rnd.normal64()), T(rnd.normal64() + 3), T(rnd.normal64() + 3)));
60 
61   // make a gaussian for each group of data
62   vpdl_gaussian<T, 3> gauss1(fit_gaussian<T>(data.begin(), data.begin() + 10));
63   vpdl_gaussian<T, 3> gauss2(fit_gaussian<T>(data.begin() + 10, data.begin() + 25));
64   vpdl_gaussian<T, 3> gauss3(fit_gaussian<T>(data.begin() + 25, data.begin() + 35));
65   // make a gaussian for all of the data together
66   vpdl_gaussian<T, 3> gaussT(fit_gaussian<T>(data.begin(), data.begin() + 35));
67 
68   vpdl_mixture<T, 3> mixture;
69   mixture.insert(gauss1, T(10.0 / 35));
70   mixture.insert(gauss2, T(15.0 / 35));
71   mixture.insert(gauss3, T(10.0 / 35));
72 
73   vnl_vector_fixed<T, 3> mean;
74   mixture.compute_mean(mean);
75   TEST_NEAR(("compute_mean <" + type_name + ">").c_str(), (gaussT.mean() - mean).inf_norm(), T(0), epsilon);
76 
77   vnl_matrix_fixed<T, 3, 3> covar;
78   mixture.compute_covar(covar);
79   TEST_NEAR(
80     ("compute_covar <" + type_name + ">").c_str(), (gaussT.covariance() - covar).array_inf_norm(), T(0), epsilon);
81 }
82 
83 
84 template <class T>
85 void
test_mixture_type(T epsilon,const std::string & type_name)86 test_mixture_type(T epsilon, const std::string & type_name)
87 {
88   // create a few sample distributions
89   vnl_vector_fixed<T, 3> mean1(T(1), T(1), T(1));
90   T var1 = T(0.5);
91 
92   vnl_vector_fixed<T, 3> mean2(T(2), T(0), T(-3));
93   vnl_vector_fixed<T, 3> var2(T(1), T(0.25), T(2));
94 
95   vnl_vector_fixed<T, 3> mean3(T(1), T(0), T(0));
96   vnl_vector_fixed<T, 3> var3(T(0.5), T(1.5), T(0.25));
97 
98   std::cout << "=================== fixed<3> =======================" << std::endl;
99   {
100     vpdl_gaussian_sphere<T, 3> gauss1(mean1, var1);
101     vpdl_gaussian_indep<T, 3> gauss2(mean2, var2);
102     vpdl_gaussian_indep<T, 3> gauss3(mean3, var3);
103 
104     vpdl_mixture<T, 3> mixture;
105     TEST(("initial num_components <" + type_name + ">").c_str(), mixture.num_components(), 0);
106 
107     mixture.insert(gauss1, T(0.2));
108     bool valid = mixture.num_components() == 1;
109     mixture.insert(gauss2, T(0.5));
110     valid = valid && mixture.num_components() == 2;
111     TEST(("num_components after insert <" + type_name + ">").c_str(), valid, true);
112 
113     const vpdl_distribution<T, 3> & dist1 = mixture.distribution(0);
114     const vpdl_gaussian_sphere<T, 3> * dist1_ptr = dynamic_cast<const vpdl_gaussian_sphere<T, 3> *>(&dist1);
115     TEST(("distribution access <" + type_name + ">").c_str(),
116          dist1_ptr && dist1_ptr->mean() == gauss1.mean() && dist1_ptr->covariance() == gauss1.covariance(),
117          true);
118     TEST(("weight access <" + type_name + ">").c_str(), mixture.weight(1), T(0.5));
119 
120     valid = mixture.remove_last();
121     TEST(("remove last <" + type_name + ">").c_str(), valid && mixture.num_components() == 1, true);
122 
123     mixture.insert(gauss2, T(0.3));
124     mixture.insert(gauss3, T(0.15));
125     mixture.set_weight(0, T(0.05));
126     TEST(("set_weight <" + type_name + ">").c_str(), mixture.weight(0), T(0.05));
127 
128     mixture.sort();
129     TEST(("default sort <" + type_name + ">").c_str(),
130          mixture.weight(0) == T(0.3) && mixture.weight(1) == T(0.15) && mixture.weight(2) == T(0.05) &&
131            &mixture.distribution(2) == &dist1,
132          true);
133 
134     vpdl_mixture<T, 3> mixture2(mixture);
135     mixture.remove_last();
136     mixture.remove_last();
137     mixture.remove_last();
138     const vpdl_gaussian_indep<T, 3> * gauss_indep =
139       dynamic_cast<const vpdl_gaussian_indep<T, 3> *>(&mixture2.distribution(0));
140     TEST(("copy constructor <" + type_name + ">").c_str(),
141          mixture2.num_components() == 3 && mixture2.weight(1) == T(0.15) && gauss_indep != nullptr,
142          true);
143 
144     vnl_vector_fixed<T, 3> pt(T(0), T(1.5), T(1));
145     T prob = T(0.1 * gauss1.prob_density(pt) + 0.6 * gauss2.prob_density(pt) + 0.3 * gauss3.prob_density(pt));
146     TEST_NEAR(("probability density <" + type_name + ">").c_str(), mixture2.prob_density(pt), prob, epsilon);
147 
148     // test gradient virtual functions against numerical difference
149     vnl_vector_fixed<T, 3> g3;
150     T dp = std::sqrt(epsilon);
151     T den = mixture2.density(pt);
152     T den_x = mixture2.density(pt + vnl_vector_fixed<T, 3>(dp, 0, 0));
153     T den_y = mixture2.density(pt + vnl_vector_fixed<T, 3>(0, dp, 0));
154     T den_z = mixture2.density(pt + vnl_vector_fixed<T, 3>(0, 0, dp));
155     vnl_vector_fixed<T, 3> grad(den_x - den, den_y - den, den_z - den);
156     grad /= dp;
157     T density = mixture2.gradient_density(pt, g3);
158     TEST_NEAR(("gradient density <" + type_name + ">").c_str(), (g3 - grad).inf_norm(), 0, dp);
159     TEST_NEAR(("density <" + type_name + ">").c_str(), density, den, epsilon);
160 
161     prob = T(0.1 * gauss1.cumulative_prob(pt) + 0.6 * gauss2.cumulative_prob(pt) + 0.3 * gauss3.cumulative_prob(pt));
162     TEST_NEAR(("cumulative probability <" + type_name + ">").c_str(), mixture2.cumulative_prob(pt), prob, epsilon);
163 
164     vnl_vector_fixed<T, 3> pt2(T(10), T(5), T(8));
165     prob = T(0.1 * gauss1.box_prob(pt, pt2) + 0.6 * gauss2.box_prob(pt, pt2) + 0.3 * gauss3.box_prob(pt, pt2));
166     TEST_NEAR(("box probability <" + type_name + ">").c_str(), mixture2.box_prob(pt, pt2), prob, epsilon);
167 
168     mixture2.sort(weight_less<T, 3>);
169     TEST(("sort by increasing weight <" + type_name + ">").c_str(),
170          mixture2.weight(0) == T(0.05) && mixture2.weight(1) == T(0.15) && mixture2.weight(2) == T(0.3),
171          true);
172 
173     mixture2.normalize_weights();
174     TEST(("normalize <" + type_name + ">").c_str(),
175          mixture2.weight(0) == T(0.1) && mixture2.weight(1) == T(0.3) && mixture2.weight(2) == T(0.6),
176          true);
177   }
178 
179   // test the covariance and mean computations
180   test_covar_mean(epsilon, type_name);
181 
182   std::cout << "=================== scalar =======================" << std::endl;
183   {
184     vpdl_gaussian<T, 1> gauss1(mean1[0], var1);
185     vpdl_gaussian<T, 1> gauss2(mean2[0], var2[0]);
186     vpdl_gaussian<T, 1> gauss3(mean3[0], var3[0]);
187 
188     vpdl_mixture<T, 1> mixture;
189     TEST(("initial num_components <" + type_name + ">").c_str(), mixture.num_components(), 0);
190 
191     mixture.insert(gauss1, T(0.2));
192     bool valid = mixture.num_components() == 1;
193     mixture.insert(gauss2, T(0.5));
194     valid = valid && mixture.num_components() == 2;
195     TEST(("num_components after insert <" + type_name + ">").c_str(), valid, true);
196 
197     const vpdl_distribution<T, 1> & dist1 = mixture.distribution(0);
198     const vpdl_gaussian<T, 1> * dist1_ptr = dynamic_cast<const vpdl_gaussian<T, 1> *>(&dist1);
199     TEST(("distribution access <" + type_name + ">").c_str(),
200          dist1_ptr && dist1_ptr->mean() == gauss1.mean() && dist1_ptr->covariance() == gauss1.covariance(),
201          true);
202     TEST(("weight access <" + type_name + ">").c_str(), mixture.weight(1), T(0.5));
203 
204     valid = mixture.remove_last();
205     TEST(("remove last <" + type_name + ">").c_str(), valid && mixture.num_components() == 1, true);
206 
207     mixture.insert(gauss2, T(0.3));
208     mixture.insert(gauss3, T(0.15));
209     mixture.set_weight(0, T(0.05));
210     TEST(("set_weight <" + type_name + ">").c_str(), mixture.weight(0), T(0.05));
211 
212     mixture.sort();
213     TEST(("default sort <" + type_name + ">").c_str(),
214          mixture.weight(0) == T(0.3) && mixture.weight(1) == T(0.15) && mixture.weight(2) == T(0.05) &&
215            &mixture.distribution(2) == &dist1,
216          true);
217 
218     vpdl_mixture<T, 1> mixture2(mixture);
219     mixture.remove_last();
220     mixture.remove_last();
221     mixture.remove_last();
222     const vpdl_gaussian<T, 1> * gaussian = dynamic_cast<const vpdl_gaussian<T, 1> *>(&mixture2.distribution(0));
223     TEST(("copy constructor <" + type_name + ">").c_str(),
224          mixture2.num_components() == 3 && mixture2.weight(1) == T(0.15) && gaussian != nullptr,
225          true);
226 
227     T pt = T(1);
228     T prob = T(0.1 * gauss1.prob_density(pt) + 0.6 * gauss2.prob_density(pt) + 0.3 * gauss3.prob_density(pt));
229     TEST_NEAR(("probability density <" + type_name + ">").c_str(), mixture2.prob_density(pt), prob, epsilon);
230 
231     // test gradient virtual functions against numerical difference
232     T g;
233     T dp = std::sqrt(epsilon);
234     T den = mixture2.density(pt);
235     T den_x = mixture2.density(pt + dp);
236     T grad = (den_x - den) / dp;
237     T density = mixture2.gradient_density(pt, g);
238     TEST_NEAR(("gradient density <" + type_name + ">").c_str(), g, grad, dp);
239     TEST_NEAR(("density <" + type_name + ">").c_str(), density, den, epsilon);
240 
241     prob = T(0.1 * gauss1.cumulative_prob(pt) + 0.6 * gauss2.cumulative_prob(pt) + 0.3 * gauss3.cumulative_prob(pt));
242     TEST_NEAR(("cumulative probability <" + type_name + ">").c_str(), mixture2.cumulative_prob(pt), prob, epsilon);
243 
244     T pt2 = T(10);
245     prob = T(0.1 * gauss1.box_prob(pt, pt2) + 0.6 * gauss2.box_prob(pt, pt2) + 0.3 * gauss3.box_prob(pt, pt2));
246     TEST_NEAR(("box probability <" + type_name + ">").c_str(), mixture2.box_prob(pt, pt2), prob, epsilon);
247 
248     mixture2.sort(weight_less<T, 1>);
249     TEST(("sort by increasing weight <" + type_name + ">").c_str(),
250          mixture2.weight(0) == T(0.05) && mixture2.weight(1) == T(0.15) && mixture2.weight(2) == T(0.3),
251          true);
252 
253     mixture2.normalize_weights();
254     TEST(("normalize <" + type_name + ">").c_str(),
255          mixture2.weight(0) == T(0.1) && mixture2.weight(1) == T(0.3) && mixture2.weight(2) == T(0.6),
256          true);
257 
258     T mean = T(0.1 * gauss1.mean() + 0.6 * gauss2.mean() + 0.3 * gauss3.mean());
259     T cmp_mean;
260     mixture2.compute_mean(cmp_mean);
261     TEST_NEAR(("compute_mean <" + type_name + ">").c_str(), cmp_mean, mean, epsilon);
262 
263     T var = T(0.1 * gauss1.covariance() + 0.6 * gauss2.covariance() + 0.3 * gauss3.covariance());
264     var += T(0.1 * gauss1.mean() * gauss1.mean() + 0.6 * gauss2.mean() * gauss2.mean() +
265              0.3 * gauss3.mean() * gauss3.mean());
266     var -= mean * mean;
267     T cmp_var;
268     mixture2.compute_covar(cmp_var);
269     TEST_NEAR(("compute_covar <" + type_name + ">").c_str(), cmp_var, var, epsilon);
270   }
271 
272   std::cout << "=================== variable =======================" << std::endl;
273   {
274     vpdl_gaussian_sphere<T> gauss1(mean1.as_ref(), var1);
275     vpdl_gaussian_indep<T> gauss2(mean2.as_ref(), var2.as_ref());
276     vpdl_gaussian_indep<T> gauss3(mean3.as_ref(), var3.as_ref());
277 
278     vpdl_mixture<T> mixture;
279     TEST(("initial num_components <" + type_name + ">").c_str(), mixture.num_components(), 0);
280     TEST(("initial dimension <" + type_name + ">").c_str(), mixture.dimension(), 0);
281 
282     mixture.insert(gauss1, T(0.2));
283     bool valid = mixture.num_components() == 1;
284     mixture.insert(gauss2, T(0.5));
285     valid = valid && mixture.num_components() == 2;
286     TEST(("num_components after insert <" + type_name + ">").c_str(), valid, true);
287     TEST(("dimension after insert <" + type_name + ">").c_str(), mixture.dimension(), 3);
288 
289     const vpdl_distribution<T> & dist1 = mixture.distribution(0);
290     const vpdl_gaussian_sphere<T> * dist1_ptr = dynamic_cast<const vpdl_gaussian_sphere<T> *>(&dist1);
291     TEST(("distribution access <" + type_name + ">").c_str(),
292          dist1_ptr && dist1_ptr->mean() == gauss1.mean() && dist1_ptr->covariance() == gauss1.covariance(),
293          true);
294     TEST(("weight access <" + type_name + ">").c_str(), mixture.weight(1), T(0.5));
295 
296     valid = mixture.remove_last();
297     TEST(("remove last <" + type_name + ">").c_str(), valid && mixture.num_components() == 1, true);
298 
299     mixture.insert(gauss2, T(0.3));
300     mixture.insert(gauss3, T(0.15));
301     mixture.set_weight(0, T(0.05));
302     TEST(("set_weight <" + type_name + ">").c_str(), mixture.weight(0), T(0.05));
303 
304     mixture.sort();
305     TEST(("default sort <" + type_name + ">").c_str(),
306          mixture.weight(0) == T(0.3) && mixture.weight(1) == T(0.15) && mixture.weight(2) == T(0.05) &&
307            &mixture.distribution(2) == &dist1,
308          true);
309 
310     vpdl_mixture<T> mixture2(mixture);
311     mixture.remove_last();
312     mixture.remove_last();
313     mixture.remove_last();
314     const vpdl_gaussian_indep<T> * gauss_indep =
315       dynamic_cast<const vpdl_gaussian_indep<T> *>(&mixture2.distribution(0));
316     TEST(("copy constructor <" + type_name + ">").c_str(),
317          mixture2.num_components() == 3 && mixture2.weight(1) == T(0.15) && mixture2.dimension() == 3 && gauss_indep,
318          true);
319 
320     vnl_vector_fixed<T, 3> pt(T(0), T(1.5), T(1));
321     T prob = T(0.1 * gauss1.prob_density(pt.as_ref()) + 0.6 * gauss2.prob_density(pt.as_ref()) +
322                0.3 * gauss3.prob_density(pt.as_ref()));
323     TEST_NEAR(("probability density <" + type_name + ">").c_str(), mixture2.prob_density(pt.as_ref()), prob, epsilon);
324 
325     // test gradient virtual functions against numerical difference
326     vnl_vector<T> g;
327     T dp = std::sqrt(epsilon);
328     T den = mixture2.density(pt.as_ref());
329     T den_x = mixture2.density((pt + vnl_vector_fixed<T, 3>(dp, 0, 0)).as_ref());
330     T den_y = mixture2.density((pt + vnl_vector_fixed<T, 3>(0, dp, 0)).as_ref());
331     T den_z = mixture2.density((pt + vnl_vector_fixed<T, 3>(0, 0, dp)).as_ref());
332     vnl_vector_fixed<T, 3> grad(den_x - den, den_y - den, den_z - den);
333     grad /= dp;
334     T density = mixture2.gradient_density(pt.as_ref(), g);
335     TEST_NEAR(("gradient density <" + type_name + ">").c_str(), (g - grad).inf_norm(), 0, dp);
336     TEST_NEAR(("density <" + type_name + ">").c_str(), density, den, epsilon);
337 
338     prob = T(0.1 * gauss1.cumulative_prob(pt.as_ref()) + 0.6 * gauss2.cumulative_prob(pt.as_ref()) +
339              0.3 * gauss3.cumulative_prob(pt.as_ref()));
340     TEST_NEAR(
341       ("cumulative probability <" + type_name + ">").c_str(), mixture2.cumulative_prob(pt.as_ref()), prob, epsilon);
342 
343     vnl_vector_fixed<T, 3> pt2(T(10), T(5), T(8));
344     prob = T(0.1 * gauss1.box_prob(pt.as_ref(), pt2.as_ref()) + 0.6 * gauss2.box_prob(pt.as_ref(), pt2.as_ref()) +
345              0.3 * gauss3.box_prob(pt.as_ref(), pt2.as_ref()));
346     TEST_NEAR(
347       ("box probability <" + type_name + ">").c_str(), mixture2.box_prob(pt.as_ref(), pt2.as_ref()), prob, epsilon);
348 
349     mixture2.sort(weight_less<T, 0>);
350     TEST(("sort by increasing weight <" + type_name + ">").c_str(),
351          mixture2.weight(0) == T(0.05) && mixture2.weight(1) == T(0.15) && mixture2.weight(2) == T(0.3),
352          true);
353 
354     mixture2.normalize_weights();
355     TEST(("normalize <" + type_name + ">").c_str(),
356          mixture2.weight(0) == T(0.1) && mixture2.weight(1) == T(0.3) && mixture2.weight(2) == T(0.6),
357          true);
358   }
359 }
360 
361 
362 static void
test_mixture()363 test_mixture()
364 {
365   test_mixture_type(1e-5f, "float");
366   test_mixture_type(1e-13, "double");
367 }
368 
369 TESTMAIN(test_mixture);
370