Skip to content

New filter for distance sensor validation #24106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions msg/EstimatorStatusFlags.msg
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bool cs_valid_fake_pos # 41 - true if a valid constant position is bein
bool cs_constant_pos # 42 - true if the vehicle is at a constant position
bool cs_baro_fault # 43 - true when the current baro has been declared faulty and is no longer being used
bool cs_gnss_vel # 44 - true if GNSS velocity measurement fusion is intended
bool cs_rng_kin_unknown # 45 - true when the range finder kinematic consistency check is not running

# fault status
uint32 fault_status_changes # number of filter fault status (fs) changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,56 +36,128 @@
*/

#include <aid_sources/range_finder/range_finder_consistency_check.hpp>
#include "ekf_derivation/generated/range_validation_filter_h.h"
#include "ekf_derivation/generated/range_validation_filter_P_init.h"

void RangeFinderConsistencyCheck::update(float dist_bottom, float dist_bottom_var, float vz, float vz_var,
bool horizontal_motion, uint64_t time_us)
using namespace matrix;

void RangeFinderConsistencyCheck::init(const float z, const float z_var, const float dist_bottom,
const float dist_bottom_var)
{
if (horizontal_motion) {
_time_last_horizontal_motion = time_us;
}
_P = sym::RangeValidationFilterPInit(z_var, dist_bottom_var);
_Ht = sym::RangeValidationFilterH<float>();

_x(RangeFilter::z.idx) = z;
_x(RangeFilter::terrain.idx) = z - dist_bottom;
_initialized = true;
_test_ratio_lpf.reset(0.f);
_t_since_first_sample = 0.f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_t_since_first_sample = 0.f;
_t_since_first_sample = 0.f;
_state = KinematicState::UNKNOWN;

}

void RangeFinderConsistencyCheck::update(const float z, const float z_var, const float vz, const float vz_var,
const float dist_bottom, const float dist_bottom_var, const uint64_t time_us)
{
const float dt = static_cast<float>(time_us - _time_last_update_us) * 1e-6f;

if ((_time_last_update_us == 0)
|| (dt < 0.001f) || (dt > 0.5f)) {
if (dt > 1.f) {
_time_last_update_us = time_us;
_dist_bottom_prev = dist_bottom;
_initialized = false;
return;

} else if (!_initialized) {
init(z, z_var, dist_bottom, dist_bottom_var);
return;
}

const float vel_bottom = (dist_bottom - _dist_bottom_prev) / dt;
_innov = -vel_bottom - vz; // vel_bottom is +up while vz is +down
// prediction step
_time_last_update_us = time_us;
_x(RangeFilter::z.idx) -= dt * vz;
_P(RangeFilter::z.idx, RangeFilter::z.idx) += dt * dt * vz_var;
_P(RangeFilter::terrain.idx, RangeFilter::terrain.idx) += _terrain_process_noise;

// Variance of the time derivative of a random variable: var(dz/dt) = 2*var(z) / dt^2
const float var = 2.f * dist_bottom_var / (dt * dt);
_innov_var = var + vz_var;
// iterate through both measurements (z and dist_bottom)
const Vector2f measurements{z, dist_bottom};

const float normalized_innov_sq = (_innov * _innov) / _innov_var;
_test_ratio = normalized_innov_sq / (_gate * _gate);
_signed_test_ratio_lpf.setParameters(dt, _signed_test_ratio_tau);
const float signed_test_ratio = matrix::sign(_innov) * _test_ratio;
_signed_test_ratio_lpf.update(signed_test_ratio);
for (int measurement_idx = 0; measurement_idx < 2; measurement_idx++) {

updateConsistency(vz, time_us);
// dist_bottom
Vector2f H = _Ht;
float R = dist_bottom_var;

_time_last_update_us = time_us;
_dist_bottom_prev = dist_bottom;
// z, direct state measurement
if (measurement_idx == 0) {
H(RangeFilter::z.idx) = 1.f;
H(RangeFilter::terrain.idx) = 0.f;
R = z_var;

}

// residual
const float measurement_pred = H * _x;
const float y = measurements(measurement_idx) - measurement_pred;

// for H as col-vector:
// innovation variance S = H^T * P * H + R
// kalman gain K = P * H / S
const float S = (H.transpose() * _P * H + R)(0, 0);
Vector2f K = (_P * H / S);

if (measurement_idx == 0) {
K(RangeFilter::z.idx) = 1.f;

} else if (measurement_idx == 1) {
_innov = y;
const float test_ratio = fminf(sq(y) / (sq(_gate) * S), 4.f); // limit to 4 to limit sensitivity to outliers
_test_ratio_lpf.setParameters(dt, _t_to_init);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can set the time constant when declaring the AlphaFilter and then call the update(dt) function

_test_ratio_lpf.update(sign(_innov) * test_ratio);
}

// update step
_x(RangeFilter::z.idx) += K(RangeFilter::z.idx) * y;
_x(RangeFilter::terrain.idx) += K(RangeFilter::terrain.idx) * y;

// covariance update with Joseph form:
// P = (I - K H) P (I - K H)^T + K R K^T
Matrix2f I;
I.setIdentity();
Matrix2f IKH = I - K.multiplyByTranspose(H);
_P = IKH * _P * IKH.transpose() + (K * R).multiplyByTranspose(K);
}

evaluateState(dt, vz, vz_var);
}

void RangeFinderConsistencyCheck::updateConsistency(float vz, uint64_t time_us)
void RangeFinderConsistencyCheck::evaluateState(const float dt, const float vz, const float vz_var)
{
if (fabsf(_signed_test_ratio_lpf.getState()) >= 1.f) {
if ((time_us - _time_last_horizontal_motion) > _signed_test_ratio_tau) {
_is_kinematically_consistent = false;
_time_last_inconsistent_us = time_us;
// start the consistency check after 1s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// start the consistency check after 1s
// let the filter settle before starting the consistency check

Avoid mentioning numerical values defined elsewhere as ppl will change them without updating the comments

if (_t_since_first_sample > _t_to_init) {
if (fabsf(_test_ratio_lpf.getState()) < 1.f) {
const bool vertical_motion = sq(vz) > fmaxf(vz_var, 0.1f);

if (!horizontal_motion && vertical_motion) {
_state = KinematicState::CONSISTENT;

} else if (_state == KinematicState::CONSISTENT || vertical_motion) {
_state = KinematicState::UNKNOWN;
}

} else {
_t_since_first_sample = 0.f;
_state = KinematicState::INCONSISTENT;
}

} else {
if ((fabsf(vz) > _min_vz_for_valid_consistency)
&& (_test_ratio < 1.f)
&& ((time_us - _time_last_inconsistent_us) > _consistency_hyst_time_us)
) {
_is_kinematically_consistent = true;
}
_t_since_first_sample += dt;
}
}

void RangeFinderConsistencyCheck::run(const float z, const float z_var, const float vz, const float vz_var,
const float dist_bottom, const float dist_bottom_var, const uint64_t time_us)
{
if (!_initialized || current_posD_reset_count != _last_posD_reset_count) {
_last_posD_reset_count = current_posD_reset_count;
_initialized = false;
}

update(z, z_var, vz, vz_var, dist_bottom, dist_bottom_var, time_us);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,62 @@ class RangeFinderConsistencyCheck final
RangeFinderConsistencyCheck() = default;
~RangeFinderConsistencyCheck() = default;

void update(float dist_bottom, float dist_bottom_var, float vz, float vz_var, bool horizontal_motion, uint64_t time_us);
enum class KinematicState : int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INCONSISTENT = 0,
CONSISTENT = 1,
UNKNOWN = 2
};

void setGate(float gate) { _gate = gate; }
float getTestRatioLpf() const { return _initialized ? _test_ratio_lpf.getState() : 0.f; }
float getInnov() const { return _initialized ? _innov : 0.f; }
float getInnovVar() const { return _initialized ? _innov_var : 0.f; }
bool isKinematicallyConsistent() const { return _state == KinematicState::CONSISTENT && _t_since_first_sample > _t_to_init; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the _state supposed to be UNKNOWN if _t_since_first_sample < _t_to_init anyway?

bool isNotKinematicallyInconsistent() const { return _state != KinematicState::INCONSISTENT && _t_since_first_sample > _t_to_init; }
void setGate(const float gate) { _gate = gate; }
void run(float z, float z_var, float vz, float vz_var,
float dist_bottom, float dist_bottom_var, uint64_t time_us);
void set_terrain_process_noise(float terrain_process_noise) { _terrain_process_noise = terrain_process_noise; }
void reset()
{
if (_initialized && _state == KinematicState::CONSISTENT) {
_state = KinematicState::UNKNOWN;
}

float getTestRatio() const { return _test_ratio; }
float getSignedTestRatioLpf() const { return _signed_test_ratio_lpf.getState(); }
float getInnov() const { return _innov; }
float getInnovVar() const { return _innov_var; }
bool isKinematicallyConsistent() const { return _is_kinematically_consistent; }
_initialized = false;
}

private:
void updateConsistency(float vz, uint64_t time_us);

uint64_t _time_last_update_us{};
float _dist_bottom_prev{};

float _test_ratio{};
AlphaFilter<float> _signed_test_ratio_lpf{}; // average signed test ratio used to detect a bias in the data
float _gate{.2f};
float _innov{};
float _innov_var{};

bool _is_kinematically_consistent{true};
uint64_t _time_last_inconsistent_us{};
uint64_t _time_last_horizontal_motion{};
uint8_t current_posD_reset_count{0};
bool horizontal_motion{false};
Comment on lines +75 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except if not possible otherwise, member variables should always be private and set/access with setter and getter functions. (and prefixed with _)


static constexpr float _signed_test_ratio_tau = 2.f;
private:

static constexpr float _min_vz_for_valid_consistency = .5f;
static constexpr uint64_t _consistency_hyst_time_us = 1e6;
void update(float z, float z_var, float vz, float vz_var, float dist_bottom,
float dist_bottom_var, uint64_t time_us);
void init(float z, float z_var, float dist_bottom, float dist_bottom_var);
void evaluateState(float dt, float vz, float vz_var);
float _terrain_process_noise{0.0f};
matrix::SquareMatrix<float, 2> _P{};
matrix::Vector2f _Ht{};
matrix::Vector2f _x{};
bool _initialized{false};
float _innov{0.f};
float _innov_var{0.f};
uint64_t _time_last_update_us{0};
static constexpr float time_constant{1.f};
AlphaFilter<float> _test_ratio_lpf{time_constant};
float _gate{1.0f};
KinematicState _state{KinematicState::UNKNOWN};
float _t_since_first_sample{0.f};
uint8_t _last_posD_reset_count{0};
static constexpr float _t_to_init{1.f};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static constexpr float _t_to_init{1.f};
static constexpr float _kTestRatioLpfTimeConstant{1.f};

};

namespace RangeFilter
{
struct IdxDof { unsigned idx; unsigned dof; };
static constexpr IdxDof z{0, 1};
static constexpr IdxDof terrain{1, 1};
static constexpr uint8_t size{2};
}

#endif // !EKF_RANGE_FINDER_CONSISTENCY_CHECK_HPP
Loading
Loading