JaiaBot 2.7.0+40+ge813615b
JaiaBot micro-AUV software
 
Loading...
Searching...
No Matches
hampel_filter.h
Go to the documentation of this file.
1#ifndef JAIABOT_UTILS_HAMPEL_FILTER_H
2#define JAIABOT_UTILS_HAMPEL_FILTER_H
3
4#include <algorithm>
5#include <cmath>
6#include <cstddef>
7#include <deque>
8#include <vector>
9
10namespace jaiabot
11{
12namespace utils
13{
14// Live Hampel filter for detecting outliers in a sensor data
15// stream. It keeps a window of the most recent readings and flags a reading as
16// an outlier when it lies more than
17// num_mads * standard deviation scaling factor * median absolute deviation (MAD)
18// from the window median: |x - median| > num_mads * (1.4826 * MAD), where
19// MAD = median(|sensor_data - median|). The median and MAD are used instead of the mean
20// and standard deviation so a spike can't shift the statistics used to
21// judge it. The 1.4826 factor makes the MAD approximate a standard deviation.
23{
24 public:
25 static constexpr double MAD_TO_STDDEV_SCALE = 1.4826;
26
27 // window_size: recent readings used to estimate the median/MAD (min 1).
28 // num_mads: outlier threshold as a multiple of the scaled MAD.
29 explicit HampelFilter(std::size_t window_size = 7, double num_mads = 3.0)
30 : window_size_(window_size < 1 ? 1 : window_size), num_mads_(num_mads)
31 {
32 }
33
34 // Outcome of filtering a single reading.
35 struct Result
36 {
37 double value{0.0}; // The input value (never modified).
38 bool is_outlier{false}; // True if the value was flagged as an outlier.
39 bool has_estimate{false}; // False during warm-up (window not yet full).
40 double median{0.0}; // Window median used for the decision.
41 double scaled_mad{0.0}; // 1.4826 * MAD used for the decision.
42 };
43
44 // Add a reading to the window and evaluate whether it is an outlier.
45 Result filter(double value)
46 {
47 window_.push_back(value);
48 if (window_.size() > window_size_)
49 window_.pop_front();
50
51 Result result;
52 result.value = value;
53 result.median = value;
54
55 // Don't start the filter until we have a full data window.
56 if (window_.size() < window_size_)
57 return result;
58
59 // Find the typical (median) value of the window.
60 std::vector<double> window(window_.begin(), window_.end());
61 const double med = compute_median(window);
62
63 // Measure how spread out the window is: take how far each reading sits
64 // from the median, then the median of those distances (the MAD).
65 std::vector<double> deviations;
66 deviations.reserve(window.size());
67 for (const double v : window) deviations.push_back(std::fabs(v - med));
68 const double scaled_mad = MAD_TO_STDDEV_SCALE * compute_median(deviations);
69
70 result.median = med;
71 result.scaled_mad = scaled_mad;
72 result.has_estimate = true;
73
74 // If every sample in the window is the same, we can't distinguish a
75 // real sensor reading change from an outlier, so accept the value.
76 if (scaled_mad > 0.0 && std::fabs(value - med) > num_mads_ * scaled_mad)
77 result.is_outlier = true;
78
79 return result;
80 }
81
82 // Clear all stored history (e.g. between missions or on sensor restart).
83 void reset() { window_.clear(); }
84
85 // True once the window is full and the filter can flag outliers.
86 bool is_warmed_up() const { return window_.size() >= window_size_; }
87
88 std::size_t window_size() const { return window_size_; }
89 double num_mads() const { return num_mads_; }
90 std::size_t samples_buffered() const { return window_.size(); }
91
92 private:
93 // Find the median: sort the readings and take the middle value. Takes a
94 // copy because sorting reorders the input.
95 static double compute_median(std::vector<double> data)
96 {
97 std::sort(data.begin(), data.end());
98 const std::size_t n = data.size();
99 const std::size_t mid = n / 2;
100 if (n % 2 == 1)
101 return data[mid];
102
103 // Even number of readings: average the two middle values.
104 return 0.5 * (data[mid - 1] + data[mid]);
105 }
106
107 std::size_t window_size_;
108 double num_mads_;
109 std::deque<double> window_;
110};
111
112} // namespace utils
113} // namespace jaiabot
114
115#endif // JAIABOT_UTILS_HAMPEL_FILTER_H
static constexpr double MAD_TO_STDDEV_SCALE
std::size_t window_size() const
HampelFilter(std::size_t window_size=7, double num_mads=3.0)
Result filter(double value)
std::size_t samples_buffered() const