Program Listing for File kalman_covariance_provider.cpp
↰ Return to documentation for file (src/kalman_covariance_provider.cpp)
/*******************************************************************************
* @file kalman_covariance_provider.cpp
* @brief Implementation of Kalman filter-based covariance estimation.
* @author Tudor Alinei
*******************************************************************************/
// Copyright 2026 Analog Devices, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "adi_imu/kalman_covariance_provider.h"
#include <algorithm>
#include <cmath>
#include <utility>
namespace adi_imu
{
KalmanCovarianceProvider::KalmanCovarianceProvider(
double process_noise_q, double measurement_noise_r, double initial_variance,
size_t warmup_samples, double min_variance, MotionDetector motion_detector)
: m_process_noise_q(process_noise_q),
m_measurement_noise_r(measurement_noise_r),
m_initial_variance(initial_variance),
m_min_variance(min_variance),
m_motion_detector(std::move(motion_detector)),
m_warmup_samples(warmup_samples),
m_sample_count(0)
{
reset();
}
void KalmanCovarianceProvider::reset()
{
m_sample_count = 0;
// Initialize all axis filters
auto initFilter = [this](AxisFilter & f) {
f.mean = 0.0;
f.variance = m_initial_variance;
f.error_covariance = m_initial_variance; // Initial uncertainty in variance estimate
};
initFilter(m_accel_x);
initFilter(m_accel_y);
initFilter(m_accel_z);
initFilter(m_gyro_x);
initFilter(m_gyro_y);
initFilter(m_gyro_z);
}
void KalmanCovarianceProvider::updateAxisFilter(AxisFilter & filter, double sample)
{
// Guard against NaN/Inf inputs
if (!std::isfinite(sample)) {
return;
}
// Update running mean using exponential moving average
// For first sample, initialize mean directly
if (m_sample_count == 0) {
filter.mean = sample;
} else {
filter.mean = (1.0 - MEAN_ALPHA) * filter.mean + MEAN_ALPHA * sample;
}
// Compute squared residual as measurement of variance
double residual = sample - filter.mean;
double measurement = residual * residual;
// === Kalman Filter Update ===
// Prediction step:
// State prediction: variance_predicted = variance (no change model)
// Error covariance prediction: P_predicted = P + Q
double P_predicted = filter.error_covariance + m_process_noise_q;
// Update step:
// Kalman gain: K = P_predicted / (P_predicted + R)
double K = P_predicted / (P_predicted + m_measurement_noise_r);
// State update: variance = variance + K * (measurement - variance)
filter.variance = filter.variance + K * (measurement - filter.variance);
// Error covariance update: P = (1 - K) * P_predicted (is equiv to the bellow form = Joseph form --more stable--)
filter.error_covariance = (1.0 - K) * P_predicted * (1.0 - K) + K * m_measurement_noise_r * K;
// Enforce minimum variance floor
filter.variance = std::max(filter.variance, m_min_variance);
// Ensure error covariance stays positive
filter.error_covariance = std::max(filter.error_covariance, m_min_variance);
}
void KalmanCovarianceProvider::addSample(const Vec3 & accel, const Vec3 & gyro)
{
// Skip non-stationary samples to avoid motion-induced variance inflation
if (!m_motion_detector.isStationary(accel, gyro)) {
return;
}
// Update each axis filter
updateAxisFilter(m_accel_x, accel.x);
updateAxisFilter(m_accel_y, accel.y);
updateAxisFilter(m_accel_z, accel.z);
updateAxisFilter(m_gyro_x, gyro.x);
updateAxisFilter(m_gyro_y, gyro.y);
updateAxisFilter(m_gyro_z, gyro.z);
// Increment sample count (cap to prevent overflow)
if (m_sample_count < m_warmup_samples) {
++m_sample_count;
}
}
bool KalmanCovarianceProvider::isReady() const { return m_sample_count >= m_warmup_samples; }
double KalmanCovarianceProvider::getCalibrationProgress() const
{
if (m_warmup_samples == 0) {
return 1.0;
}
return std::min(1.0, static_cast<double>(m_sample_count) / static_cast<double>(m_warmup_samples));
}
CovarianceMatrix KalmanCovarianceProvider::buildCovarianceMatrix(
double var_x, double var_y, double var_z) const
{
// Build diagonal covariance matrix (assuming independent axes)
// Layout: [xx, xy, xz, yx, yy, yz, zx, zy, zz]
return {var_x, 0.0, 0.0, 0.0, var_y, 0.0, 0.0, 0.0, var_z};
}
CovarianceMatrix KalmanCovarianceProvider::getAccelCovariance() const
{
return buildCovarianceMatrix(m_accel_x.variance, m_accel_y.variance, m_accel_z.variance);
}
CovarianceMatrix KalmanCovarianceProvider::getGyroCovariance() const
{
return buildCovarianceMatrix(m_gyro_x.variance, m_gyro_y.variance, m_gyro_z.variance);
}
} // namespace adi_imu