Visual Servoing Platform  version 3.4.0
vpMbtTukeyEstimator.h
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Tukey M-estimator.
33  *
34  *****************************************************************************/
35 
36 #ifndef _vpMbtTukeyEstimator_h_
37 #define _vpMbtTukeyEstimator_h_
38 
39 #include <vector>
40 #include <visp3/core/vpColVector.h>
41 
42 #ifndef DOXYGEN_SHOULD_SKIP_THIS
43 
44 template <typename T> class vpMbtTukeyEstimator
45 {
46 public:
47  void MEstimator(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
48  void MEstimator(const vpColVector &residues, vpColVector &weights, const double NoiseThreshold);
49 
50 private:
51  T getMedian(std::vector<T> &vec);
52  void MEstimator_impl(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
53  void MEstimator_impl_ssse3(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
54  void psiTukey(const T sig, std::vector<T> &x, std::vector<T> &weights);
55  void psiTukey(const T sig, std::vector<T> &x, vpColVector &weights);
56 
57  std::vector<T> m_normres;
58  std::vector<T> m_residues;
59 };
60 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
61 
62 
63 /*
64  * The code bellow previously in vpMbtTuckeyEstimator.cpp produced
65  * a link issue with MinGW-W64 x86_64-8.1.0-posix-seh-rt_v6-rev0 (g++ 8.1.0)
66  * libvisp_mbt.so.3.1.0: undefined reference to
67  * `vpMbtTukeyEstimator<double>::MEstimator(std::vector<double,
68  * std::allocator<double> > const&, std::vector<double, std::allocator<double>
69  * >&, double)'
70  * Note that with the previous MinGW-W64 version x86_64-7.3.0-posix-seh-rt_v6-rev0 (g++ 7.3.0)
71  * the build succeed.
72  *
73  * To remove this link issue the solution was to move the content of vpMbtTuckeyEstimator.cpp
74  * before remove.
75  */
76 #include <algorithm>
77 #include <cmath>
78 #include <iostream>
79 
80 #include <visp3/core/vpCPUFeatures.h>
81 
82 #define USE_TRANSFORM 1
83 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && USE_TRANSFORM
84 #define HAVE_TRANSFORM 1
85 #endif
86 
87 #if HAVE_TRANSFORM
88 #include <functional>
89 #endif
90 
91 #if defined __SSE2__ || defined _M_X64 || (defined _M_IX86_FP && _M_IX86_FP >= 2)
92 #include <emmintrin.h>
93 #define VISP_HAVE_SSE2 1
94 
95 #if defined __SSE3__ || (defined _MSC_VER && _MSC_VER >= 1500)
96 #include <pmmintrin.h>
97 #define VISP_HAVE_SSE3 1
98 #endif
99 #if defined __SSSE3__ || (defined _MSC_VER && _MSC_VER >= 1500)
100 #include <tmmintrin.h>
101 #define VISP_HAVE_SSSE3 1
102 #endif
103 #endif
104 
105 #ifndef DOXYGEN_SHOULD_SKIP_THIS
106 
107 #if HAVE_TRANSFORM
108 namespace
109 {
110 template <typename T> struct AbsDiff : public std::binary_function<T, T, T> {
111  T operator()(const T a, const T b) const { return std::fabs(a - b); }
112 };
113 }
114 #endif
115 
116 template class vpMbtTukeyEstimator<float>;
117 template class vpMbtTukeyEstimator<double>;
118 
119 #if VISP_HAVE_SSSE3
120 namespace
121 {
122 inline __m128 abs_ps(__m128 x)
123 {
124  static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31
125  return _mm_andnot_ps(sign_mask, x);
126 }
127 }
128 #endif
129 
130 template <typename T> T vpMbtTukeyEstimator<T>::getMedian(std::vector<T> &vec)
131 {
132  // Not the exact median when even number of elements
133  int index = (int)(ceil(vec.size() / 2.0)) - 1;
134  std::nth_element(vec.begin(), vec.begin() + index, vec.end());
135  return vec[index];
136 }
137 
138 // Without MEstimator_impl, error with g++4.6, ok with gcc 5.4.0
139 // Ubuntu-12.04-Linux-i386-g++4.6-Dyn-RelWithDebInfo-dc1394-v4l2-X11-OpenCV2.3.1-lapack-gsl-Coin-jpeg-png-xml-pthread-OpenMP-dmtx-zbar-Wov-Weq-Moment:
140 // libvisp_mbt.so.3.1.0: undefined reference to
141 // `vpMbtTukeyEstimator<double>::MEstimator(std::vector<double,
142 // std::allocator<double> > const&, std::vector<double, std::allocator<double>
143 // >&, double)'
144 template <typename T>
145 void vpMbtTukeyEstimator<T>::MEstimator_impl(const std::vector<T> &residues, std::vector<T> &weights,
146  const T NoiseThreshold)
147 {
148  if (residues.empty()) {
149  return;
150  }
151 
152  m_residues = residues;
153 
154  T med = getMedian(m_residues);
155  m_normres.resize(residues.size());
156 
157 #if HAVE_TRANSFORM
158  std::transform(residues.begin(), residues.end(), m_normres.begin(),
159  std::bind(AbsDiff<T>(), std::placeholders::_1, med));
160 #else
161  for (size_t i = 0; i < m_residues.size(); i++) {
162  m_normres[i] = (std::fabs(residues[i] - med));
163  }
164 #endif
165 
166  m_residues = m_normres;
167  T normmedian = getMedian(m_residues);
168 
169  // 1.48 keeps scale estimate consistent for a normal probability dist.
170  T sigma = static_cast<T>(1.4826 * normmedian); // median Absolute Deviation
171 
172  // Set a minimum threshold for sigma
173  // (when sigma reaches the level of noise in the image)
174  if (sigma < NoiseThreshold) {
175  sigma = NoiseThreshold;
176  }
177 
178  psiTukey(sigma, m_normres, weights);
179 }
180 
181 template <>
182 inline void vpMbtTukeyEstimator<float>::MEstimator_impl_ssse3(const std::vector<float> &residues, std::vector<float> &weights,
183  const float NoiseThreshold)
184 {
185 #if VISP_HAVE_SSSE3
186  if (residues.empty()) {
187  return;
188  }
189 
190  m_residues = residues;
191 
192  float med = getMedian(m_residues);
193  m_normres.resize(residues.size());
194 
195  size_t i = 0;
196  __m128 med_128 = _mm_set_ps1(med);
197 
198  if (m_residues.size() >= 4) {
199  for (i = 0; i <= m_residues.size() - 4; i += 4) {
200  __m128 residues_128 = _mm_loadu_ps(residues.data() + i);
201  _mm_storeu_ps(m_normres.data() + i, abs_ps(_mm_sub_ps(residues_128, med_128)));
202  }
203  }
204 
205  for (; i < m_residues.size(); i++) {
206  m_normres[i] = (std::fabs(residues[i] - med));
207  }
208 
209  m_residues = m_normres;
210  float normmedian = getMedian(m_residues);
211 
212  // 1.48 keeps scale estimate consistent for a normal probability dist.
213  float sigma = 1.4826f * normmedian; // median Absolute Deviation
214 
215  // Set a minimum threshold for sigma
216  // (when sigma reaches the level of noise in the image)
217  if (sigma < NoiseThreshold) {
218  sigma = NoiseThreshold;
219  }
220 
221  psiTukey(sigma, m_normres, weights);
222 #else
223  (void)residues;
224  (void)weights;
225  (void)NoiseThreshold;
226 #endif
227 }
228 
232 template <>
233 inline void vpMbtTukeyEstimator<double>::MEstimator_impl_ssse3(const std::vector<double> &residues,
234  std::vector<double> &weights, const double NoiseThreshold)
235 {
236 #if VISP_HAVE_SSSE3
237  if (residues.empty()) {
238  return;
239  }
240 
241  m_residues = residues;
242 
243  double med = getMedian(m_residues);
244  m_normres.resize(residues.size());
245 
246 #if HAVE_TRANSFORM
247  std::transform(residues.begin(), residues.end(), m_normres.begin(),
248  std::bind(AbsDiff<double>(), std::placeholders::_1, med));
249 #else
250  for (size_t i = 0; i < m_residues.size(); i++) {
251  m_normres[i] = (std::fabs(residues[i] - med));
252  }
253 #endif
254 
255  m_residues = m_normres;
256  double normmedian = getMedian(m_residues);
257 
258  // 1.48 keeps scale estimate consistent for a normal probability dist.
259  double sigma = 1.4826 * normmedian; // median Absolute Deviation
260 
261  // Set a minimum threshold for sigma
262  // (when sigma reaches the level of noise in the image)
263  if (sigma < NoiseThreshold) {
264  sigma = NoiseThreshold;
265  }
266 
267  psiTukey(sigma, m_normres, weights);
268 #else
269  (void)residues;
270  (void)weights;
271  (void)NoiseThreshold;
272 #endif
273 }
274 
278 template <>
279 inline void vpMbtTukeyEstimator<float>::MEstimator(const std::vector<float> &residues, std::vector<float> &weights,
280  const float NoiseThreshold)
281 {
283 #if !VISP_HAVE_SSSE3
284  checkSSSE3 = false;
285 #endif
286 
287  if (checkSSSE3)
288  MEstimator_impl_ssse3(residues, weights, NoiseThreshold);
289  else
290  MEstimator_impl(residues, weights, NoiseThreshold);
291 }
292 
296 template <>
297 inline void vpMbtTukeyEstimator<double>::MEstimator(const std::vector<double> &residues, std::vector<double> &weights,
298  const double NoiseThreshold)
299 {
300  bool checkSSSE3 = vpCPUFeatures::checkSSSE3();
301 #if !VISP_HAVE_SSSE3
302  checkSSSE3 = false;
303 #endif
304 
305  if (checkSSSE3)
306  MEstimator_impl_ssse3(residues, weights, NoiseThreshold);
307  else
308  MEstimator_impl(residues, weights, NoiseThreshold);
309 }
310 
314 template <typename T> void vpMbtTukeyEstimator<T>::psiTukey(const T sig, std::vector<T> &x, vpColVector &weights)
315 {
316  double C = sig * 4.6851;
317 
318  // Here we consider that sig cannot be equal to 0
319  for (unsigned int i = 0; i < (unsigned int)x.size(); i++) {
320  double xi = x[i] / C;
321  xi *= xi;
322 
323  if (xi > 1.) {
324  weights[i] = 0;
325  } else {
326  xi = 1 - xi;
327  xi *= xi;
328  weights[i] = xi;
329  }
330  }
331 }
332 
336 template <>
337 inline void vpMbtTukeyEstimator<double>::MEstimator(const vpColVector &residues, vpColVector &weights,
338  const double NoiseThreshold)
339 {
340  if (residues.size() == 0) {
341  return;
342  }
343 
344  m_residues.resize(0);
345  m_residues.reserve(residues.size());
346  m_residues.insert(m_residues.end(), &residues.data[0], &residues.data[residues.size()]);
347 
348  double med = getMedian(m_residues);
349 
350  m_normres.resize(residues.size());
351  for (size_t i = 0; i < m_residues.size(); i++) {
352  m_normres[i] = std::fabs(residues[(unsigned int)i] - med);
353  }
354 
355  m_residues = m_normres;
356  double normmedian = getMedian(m_residues);
357 
358  // 1.48 keeps scale estimate consistent for a normal probability dist.
359  double sigma = 1.4826 * normmedian; // median Absolute Deviation
360 
361  // Set a minimum threshold for sigma
362  // (when sigma reaches the level of noise in the image)
363  if (sigma < NoiseThreshold) {
364  sigma = NoiseThreshold;
365  }
366 
367  psiTukey(sigma, m_normres, weights);
368 }
369 
373 template <>
374 inline void vpMbtTukeyEstimator<float>::MEstimator(const vpColVector &residues, vpColVector &weights,
375  const double NoiseThreshold)
376 {
377  if (residues.size() == 0) {
378  return;
379  }
380 
381  m_residues.resize(0);
382  m_residues.reserve(residues.size());
383  for (unsigned int i = 0; i < residues.size(); i++) {
384  m_residues.push_back((float)residues[i]);
385  }
386 
387  float med = getMedian(m_residues);
388 
389  m_normres.resize(residues.size());
390  for (size_t i = 0; i < m_residues.size(); i++) {
391  m_normres[i] = (float)std::fabs(residues[(unsigned int)i] - med);
392  }
393 
394  m_residues = m_normres;
395  float normmedian = getMedian(m_residues);
396 
397  // 1.48 keeps scale estimate consistent for a normal probability dist.
398  float sigma = 1.4826f * normmedian; // median Absolute Deviation
399 
400  // Set a minimum threshold for sigma
401  // (when sigma reaches the level of noise in the image)
402  if (sigma < NoiseThreshold) {
403  sigma = (float)NoiseThreshold;
404  }
405 
406  psiTukey(sigma, m_normres, weights);
407 }
408 
412 template <class T> void vpMbtTukeyEstimator<T>::psiTukey(const T sig, std::vector<T> &x, std::vector<T> &weights)
413 {
414  T C = static_cast<T>(4.6851) * sig;
415  weights.resize(x.size());
416 
417  // Here we consider that sig cannot be equal to 0
418  for (size_t i = 0; i < x.size(); i++) {
419  T xi = x[i] / C;
420  xi *= xi;
421 
422  if (xi > 1.) {
423  weights[i] = 0;
424  } else {
425  xi = 1 - xi;
426  xi *= xi;
427  weights[i] = xi;
428  }
429  }
430 }
431 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
432 
433 #endif
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:145
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:291
VISP_EXPORT bool checkSSSE3()
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:310
Implementation of column vector and the associated operations.
Definition: vpColVector.h:130