Skip to content

Commit 516a539

Browse files
authored
Merge pull request #142 from Hyp-ed/nav-encoder_outlier_detection
Nav: Wheel encoder outlier detection
2 parents 45cf630 + a02697b commit 516a539

File tree

2 files changed

+103
-13
lines changed

2 files changed

+103
-13
lines changed

src/navigation/navigation.cpp

+79-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Navigation::Navigation(const std::uint32_t axis /*=0*/)
1818
current_measurements_(0),
1919
is_imu_reliable_{{true, true, true, true}},
2020
num_outlier_imus_(0),
21+
imu_outlier_counter_{{0, 0, 0, 0}},
22+
is_encoder_reliable_{{true, true, true, true}},
23+
num_outlier_encoders_(0),
24+
encoder_outlier_counter_{{0, 0, 0, 0}},
2125
acceleration_(0, 0.),
2226
velocity_(0, 0.),
2327
displacement_(0, 0.),
@@ -196,13 +200,17 @@ void Navigation::queryWheelEncoders()
196200
{
197201
const auto encoder_data = data_.getSensorsWheelEncoderData();
198202

199-
data::nav_t sum = 0;
203+
EncoderArray encoder_data_array;
204+
uint32_t sum = 0;
200205
for (size_t i = 0; i < encoder_data.size(); ++i) {
201-
sum += static_cast<data::nav_t>(encoder_data.at(i).value);
206+
sum += encoder_data.at(i).value;
207+
encoder_data_array.at(i) = encoder_data.at(i).value;
202208
}
203209

204-
const data::nav_t average = sum / encoder_data.size();
210+
const data::nav_t average = static_cast<data::nav_t>(sum / encoder_data.size());
205211
encoder_displacement_.value = average * data::Navigation::kWheelCircumfrence;
212+
213+
wheelEncoderOutlierDetection(encoder_data_array);
206214
}
207215

208216
void Navigation::queryImus()
@@ -225,7 +233,7 @@ void Navigation::queryImus()
225233
log_.debug("Raw acceleration values: %.3f, %.3f, %.3f, %.3f", raw_acceleration_moving[0],
226234
raw_acceleration_moving[1], raw_acceleration_moving[2], raw_acceleration_moving[3]);
227235
// Run outlier detection on moving axis
228-
imuOutlierDetection(raw_acceleration_moving, kInterQuartileScaler);
236+
imuOutlierDetection(raw_acceleration_moving);
229237
// TODO(Justus) how to run outlier detection on non-moving axes without affecting "reliable"
230238
// Current idea: outlier function takes reliability write flag, on hold until z-score impl.
231239

@@ -334,15 +342,15 @@ void Navigation::logWrite()
334342
write_to_file_ = true;
335343
}
336344

337-
Navigation::QuartileBounds Navigation::calculateImuQuartiles(NavigationArray &data_array)
345+
Navigation::QuartileBounds Navigation::calculateImuQuartiles(const NavigationArray &data_array)
338346
{
339347
std::vector<data::nav_t> data_vector;
348+
std::array<data::nav_t, 3> quartile_bounds;
340349

341350
for (size_t i = 0; i < data::Sensors::kNumImus; ++i) {
342351
if (is_imu_reliable_.at(i)) { data_vector.push_back(data_array.at(i)); }
343352
}
344353
std::sort(data_vector.begin(), data_vector.end());
345-
std::array<data::nav_t, 3> quartile_bounds;
346354

347355
quartile_bounds.at(0) = (data_vector.at(0) + data_vector.at(1)) / 2.;
348356
quartile_bounds.at(2)
@@ -360,9 +368,36 @@ Navigation::QuartileBounds Navigation::calculateImuQuartiles(NavigationArray &da
360368
return quartile_bounds;
361369
}
362370

363-
void Navigation::imuOutlierDetection(NavigationArray &data_array, const data::nav_t threshold)
371+
Navigation::QuartileBounds Navigation::calculateEncoderQuartiles(const EncoderArray &data_array)
364372
{
365-
std::array<data::nav_t, 3> quartile_bounds = calculateImuQuartiles(data_array);
373+
std::vector<uint32_t> data_vector;
374+
375+
for (size_t i = 0; i < data::Sensors::kNumEncoders; ++i) {
376+
if (is_encoder_reliable_.at(i)) { data_vector.push_back(data_array.at(i)); }
377+
}
378+
std::sort(data_vector.begin(), data_vector.end());
379+
380+
std::array<data::nav_t, 3> quartile_bounds;
381+
quartile_bounds.at(0) = (data_vector.at(0) + data_vector.at(1)) / 2.;
382+
quartile_bounds.at(2)
383+
= (data_vector.at(data_vector.size() - 2) + data_vector.at(data_vector.size() - 1)) / 2.;
384+
if (num_outlier_encoders_ == 0) {
385+
quartile_bounds.at(1) = (data_vector.at(1) + data_vector.at(2)) / 2.;
386+
} else if (num_outlier_encoders_ == 1) {
387+
quartile_bounds.at(1) = data_vector.at(1);
388+
} else {
389+
auto navigation_data = data_.getNavigationData();
390+
navigation_data.module_status = data::ModuleStatus::kCriticalFailure;
391+
data_.setNavigationData(navigation_data);
392+
log_.error("At least two Encoders no longer reliable, entering CriticalFailure.");
393+
}
394+
return quartile_bounds;
395+
}
396+
397+
void Navigation::imuOutlierDetection(NavigationArray &data_array)
398+
{
399+
const QuartileBounds quartile_bounds = calculateImuQuartiles(data_array);
400+
static constexpr uint8_t threshold = kInterQuartileScaler;
366401

367402
// find the thresholds
368403
// clip IQR to upper bound to avoid issues with very large outliers
@@ -371,7 +406,7 @@ void Navigation::imuOutlierDetection(NavigationArray &data_array, const data::na
371406
const auto lower_limit = quartile_bounds.at(0) - threshold * iqr;
372407
// replace any outliers with the median
373408
for (std::size_t i = 0; i < data::Sensors::kNumImus; ++i) {
374-
const auto exceeds_limits = data_array.at(i) < lower_limit || data_array.at(i) > upper_limit;
409+
const bool exceeds_limits = data_array.at(i) < lower_limit || data_array.at(i) > upper_limit;
375410
if (exceeds_limits && is_imu_reliable_.at(i)) {
376411
log_.debug("Outlier detected in IMU %d, reading: %.3f not in [%.3f, %.3f]. Updated to %.3f",
377412
i + 1, data_array.at(i), lower_limit, upper_limit, quartile_bounds.at(1));
@@ -392,6 +427,40 @@ void Navigation::imuOutlierDetection(NavigationArray &data_array, const data::na
392427
}
393428
}
394429

430+
void Navigation::wheelEncoderOutlierDetection(EncoderArray &data_array)
431+
{
432+
const QuartileBounds quartile_bounds = calculateEncoderQuartiles(data_array);
433+
static constexpr uint8_t threshold = kInterQuartileScaler;
434+
435+
// find the thresholds
436+
// clip IQR to upper bound to avoid issues with very large outliers
437+
const auto iqr = std::min(quartile_bounds.at(2) - quartile_bounds.at(0), kMaxInterQuartileRange);
438+
const auto upper_limit = quartile_bounds.at(2) + threshold * iqr;
439+
const auto lower_limit = quartile_bounds.at(0) - threshold * iqr;
440+
// replace any outliers with the median
441+
for (std::size_t i = 0; i < data::Sensors::kNumEncoders; ++i) {
442+
const bool exceeds_limits = data_array.at(i) < lower_limit || data_array.at(i) > upper_limit;
443+
if (exceeds_limits && is_encoder_reliable_.at(i)) {
444+
log_.debug(
445+
"Outlier detected in Encoder %d, reading: %.3f not in [%.3f, %.3f]. Updated to %.3f", i + 1,
446+
data_array.at(i), lower_limit, upper_limit, quartile_bounds.at(1));
447+
data_array.at(i) = quartile_bounds.at(1);
448+
encoder_outlier_counter_.at(i)++;
449+
// If this counter exceeds some threshold then that encoder is deemed unreliable
450+
if (encoder_outlier_counter_.at(i) > 1000 && is_encoder_reliable_.at(i)) {
451+
is_encoder_reliable_.at(i) = false;
452+
++num_outlier_encoders_;
453+
}
454+
if (num_outlier_encoders_ > 1) {
455+
status_ = data::ModuleStatus::kCriticalFailure;
456+
log_.error("At least two Wheel Encoders no longer reliable, entering CriticalFailure.");
457+
}
458+
} else {
459+
encoder_outlier_counter_.at(i) = 0;
460+
}
461+
}
462+
}
463+
395464
void Navigation::updateData()
396465
{
397466
data::Navigation nav_data;
@@ -439,4 +508,4 @@ void Navigation::initialiseTimestamps()
439508
log_.debug("Initial timestamp:%d", initial_timestamp_);
440509
previous_timestamp_ = initial_timestamp;
441510
}
442-
} // namespace hyped::navigation
511+
} // namespace hyped::navigation

src/navigation/navigation.hpp

+24-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Navigation {
2727
using NavigationArrayOneFaulty = std::array<data::nav_t, data::Sensors::kNumImus - 1>;
2828
using FilterArray = std::array<KalmanFilter, data::Sensors::kNumImus>;
2929
using QuartileBounds = std::array<data::nav_t, 3>;
30+
using EncoderArray = std::array<uint32_t, data::Sensors::kNumEncoders>;
3031

3132
/**
3233
* @brief Construct a new Navigation object
@@ -95,14 +96,27 @@ class Navigation {
9596
*
9697
* @return quartiles of reliable IMU readings of form (q1, q2(median), q3)
9798
*/
98-
QuartileBounds calculateImuQuartiles(NavigationArray &data_array);
99+
QuartileBounds calculateImuQuartiles(const NavigationArray &data_array);
100+
/**
101+
* @brief Calculate quartiles for an array of readings. Updates quartile_bounds array
102+
*
103+
* @param pointer to array of original acceleration readings
104+
*
105+
* @return quartiles of reliable IMU readings of form (q1, q2(median), q3)
106+
*/
107+
QuartileBounds calculateEncoderQuartiles(const EncoderArray &data_array);
99108
/**
100109
* @brief Apply scaled interquartile range bounds on an array of readings
101110
*
102111
* @param pointer to array of original acceleration readings
103-
* @param threshold value
104112
*/
105-
void imuOutlierDetection(NavigationArray &data_array, const data::nav_t threshold);
113+
void imuOutlierDetection(NavigationArray &data_array);
114+
/**
115+
* @brief Apply scaled interquartile range bounds on an array of readings
116+
*
117+
* @param pointer to array of original encoder readings
118+
*/
119+
void wheelEncoderOutlierDetection(EncoderArray &data_array);
106120
/**
107121
* @brief Update central data structure
108122
*/
@@ -186,6 +200,13 @@ class Navigation {
186200
// Counter of how many IMUs have failed
187201
uint32_t num_outlier_imus_;
188202

203+
// Counter for consecutive outlier output from each wheel encoder
204+
std::array<uint32_t, data::Sensors::kNumEncoders> encoder_outlier_counter_;
205+
// Array of booleans to signify which encoders are reliable or faulty
206+
std::array<bool, data::Sensors::kNumEncoders> is_encoder_reliable_;
207+
// Counter of how many encoders have failed
208+
uint32_t num_outlier_encoders_;
209+
189210
// To store estimated values
190211
ImuDataPointArray sensor_readings_;
191212
data::DataPoint<data::nav_t> encoder_displacement_;

0 commit comments

Comments
 (0)