Outlier Detection¶
Real-time detection of measurement outliers using chi-squared gating on the Normalized Innovation Squared (NIS).
Chi2OutlierDetector¶
Detects outliers by comparing NIS against a chi-squared threshold. Supports both fixed and adaptive thresholds.
Setup¶
from kalbee import Chi2OutlierDetector
# Fixed threshold
detector = Chi2OutlierDetector(m=1, confidence=0.95)
# Adaptive threshold (adjusts based on recent NIS history)
adaptive_detector = Chi2OutlierDetector(
m=1, confidence=0.95,
adaptive=True,
window_size=50,
scale_factor=3.0,
)
Checking Measurements¶
import numpy as np
innovation = np.array([[2.5]])
innovation_cov = np.array([[1.0]])
result = detector.check(innovation, innovation_cov)
print(f"Is inlier: {result.is_inlier}")
print(f"NIS: {result.nis_value:.3f}")
print(f"Threshold: {result.threshold:.3f}")
print(f"Confidence: {result.confidence}")
Batch Processing¶
innovations = np.array([[0.5], [1.2], [8.0], [0.3], [0.8]]) # (T, m)
innovation_covs = np.array([np.eye(1)] * 5) # (T, m, m)
results = detector.batch_check(innovations, innovation_covs)
for i, r in enumerate(results):
status = "✓" if r.is_inlier else "✗ OUTLIER"
print(f"Step {i}: NIS={r.nis_value:.3f} {status}")
Statistics¶
stats = detector.get_statistics()
print(f"Checks: {stats['num_checks']}")
print(f"NIS mean: {stats['nis_mean']:.3f}")
print(f"Current threshold: {stats['current_threshold']:.3f}")
DetectionResult¶
Each check() call returns a DetectionResult dataclass:
@dataclass
class DetectionResult:
is_inlier: bool
nis_value: float
threshold: float
confidence: float
innovation: Optional[np.ndarray]
Adaptive Threshold¶
When adaptive=True, the threshold is computed as:
\[\text{threshold} = \mu_{\text{NIS}} + s \cdot \sigma_{\text{NIS}}\]
Where:
- \(\mu_{\text{NIS}}\): Mean of recent NIS values (sliding window)
- \(\sigma_{\text{NIS}}\): Standard deviation of recent NIS values
- \(s\):
scale_factor(default 3.0)
The adaptive threshold activates after window_size (default 50) measurements have been collected.
Complete Example¶
import numpy as np
from kalbee import KalmanFilter, Chi2OutlierDetector
# Setup
state = np.zeros((2, 1))
cov = np.eye(2) * 10.0
F = np.array([[1, 1], [0, 1]])
Q = np.eye(2) * 0.01
H = np.array([[1, 0]])
R = np.array([[0.5]])
kf = KalmanFilter(state, cov, F, Q, H, R)
detector = Chi2OutlierDetector(m=1, confidence=0.95, adaptive=True)
# Simulate with outliers
np.random.seed(42)
true_positions = np.arange(0, 10, 0.1)
outlier_indices = {15, 45, 75} # Inject outliers
inliers = 0
outliers = 0
for k, pos in enumerate(true_positions):
kf.predict()
# Generate measurement (with occasional outlier)
if k in outlier_indices:
z = np.array([[pos + np.random.randn() * 10]]) # Large outlier
else:
z = np.array([[pos + np.random.randn() * 0.5]]) # Normal noise
# Check for outlier
innovation = z - kf.measure()
S = H @ kf.P @ H.T + R
result = detector.check(innovation, S)
if result.is_inlier:
kf.update(z)
inliers += 1
else:
print(f"Step {k}: Outlier detected (NIS={result.nis_value:.2f})")
outliers += 1
print(f"Inliers: {inliers}, Outliers: {outliers}")