Innovation Gating¶
Innovation gating rejects measurements that are statistically inconsistent with the filter's predictions. This prevents outliers from corrupting the state estimate.
Available Methods¶
Chi-Squared Gate¶
Tests whether the Normalized Innovation Squared (NIS) exceeds a chi-squared threshold:
\[\text{NIS} = v_k^T S_k^{-1} v_k \leq \chi^2_{m, \alpha}\]
Where \(m\) is the measurement dimension and \(\alpha\) is the confidence level.
import numpy as np
from kalbee import chi2_gate
innovation = np.array([[0.5]])
innovation_cov = np.array([[1.0]])
passed, nis_value, threshold = chi2_gate(
innovation, innovation_cov, confidence=0.95
)
print(f"NIS: {nis_value:.3f}, Threshold: {threshold:.3f}, Passed: {passed}")
Mahalanobis Distance Gate¶
Uses the Mahalanobis distance (square root of NIS) with a fixed threshold:
\[d_M = \sqrt{v_k^T S_k^{-1} v_k} \leq \text{threshold}\]
from kalbee import ellipsoidal_gate, mahalanobis_distance
# Compute distance
dist = mahalanobis_distance(innovation, innovation_cov)
print(f"Mahalanobis distance: {dist:.3f}")
# Gate with threshold
passed = ellipsoidal_gate(innovation, innovation_cov, gate_threshold=5.0)
Gated Update¶
High-level function that runs a gated update on any BaseFilter:
from kalbee import KalmanFilter, gated_update
kf = KalmanFilter(state, cov, F, Q, H, R)
# Chi-squared gating (default)
updated, state = gated_update(kf, measurement, confidence=0.95)
# Mahalanobis gating
updated, state = gated_update(kf, measurement, gate_threshold=3.0)
if not updated:
print("Measurement rejected by gate")
When to Use¶
| ✅ Use gating when | ❌ Don't use when |
|---|---|
| Measurements may contain outliers | All measurements are trusted |
| Multi-target tracking (data association) | Single target, clean measurements |
| Sensor fusion with noisy sensors | — |
Multi-Object Tracking Example¶
import numpy as np
from kalbee import KalmanFilter, chi2_gate
def associate_and_update(trackers, detections, H, R):
"""Simple nearest-neighbor with gating."""
updated = []
for track in trackers:
innovation = detections - (H @ track.x)
S = H @ track.P @ H.T + R
best_det = None
best_nis = float('inf')
for i, det in enumerate(detections):
v = det.reshape(-1, 1) - (H @ track.x)
nis = float(v.T @ np.linalg.inv(S) @ v)
_, nis_val, threshold = chi2_gate(v, S)
if nis_val < threshold and nis < best_nis:
best_nis = nis
best_det = i
if best_det is not None:
track.predict()
track.update(detections[best_det].reshape(-1, 1))
updated.append(track)
return updated