Sensor Fusion Platform for IMU Data
INVARI is a production-grade sensor fusion platform for analyzing IMU logs, applying complementary and Extended Kalman filters, and visualizing orientation in 3D.
INVARI provides:
- Complementary Filter: Simple, efficient orientation estimation combining accelerometer and gyroscope data
- Extended Kalman Filter: Optimal state estimation with uncertainty quantification
- 3D Visualization: Real-time orientation viewer using Three.js
- Comparative Analysis: RMSE metrics and parameter tuning tools
- REST API: FastAPI backend for processing IMU logs
- Web Dashboard: React frontend with interactive plots and controls
INVARI/
├── src/ # Core Python modules
│ ├── filters/ # Filter implementations
│ │ ├── complementary.py
│ │ └── ekf.py
│ ├── io/ # Data I/O
│ │ └── imu_loader.py
│ └── utils/ # Utilities
│ ├── metrics.py
│ └── visualization.py
├── api/ # FastAPI backend
│ └── main.py
├── web/ # React frontend
│ └── src/
│ ├── components/
│ │ ├── OrientationViewer.js
│ │ ├── SignalPlots.js
│ │ └── FileUpload.js
│ └── App.js
├── notebooks/ # Analysis notebooks
│ └── fusion_comparison.ipynb
├── scripts/ # Utility scripts
│ ├── run_fusion.py
│ └── sweep_parameters.py
├── tests/ # Test suite
│ └── test_filters.py
└── data/ # Sample data
└── sample_imu.csv
The complementary filter combines accelerometer and gyroscope measurements using a tunable blending parameter α:
Accelerometer-based angles (low-frequency):
φ_acc = atan2(ay, az)
θ_acc = atan2(-ax, sqrt(ay² + az²))
Gyroscope integration (high-frequency):
φ_gyro = φ_prev + gx * dt
θ_gyro = θ_prev + gy * dt
Complementary fusion:
φ = α * φ_gyro + (1 - α) * φ_acc
θ = α * θ_gyro + (1 - α) * θ_acc
ψ = ψ_prev + gz * dt (yaw from gyro only)
Parameters:
α: Blending parameter (0-1), typically 0.95-0.98- Higher α: More trust in gyroscope (better dynamic response)
- Lower α: More trust in accelerometer (better long-term stability)
The EKF provides optimal state estimation by modeling system dynamics and measurement uncertainties.
State Vector:
x = [q0, q1, q2, q3]ᵀ (quaternion [w, x, y, z])
Process Model (gyroscope integration):
x_k = f(x_{k-1}, u_k, w_k)
where u_k is gyroscope input and w_k is process noise.
Measurement Model (accelerometer):
z_k = h(x_k) + v_k
where v_k is measurement noise.
Prediction Step:
P_k|k-1 = F_k P_{k-1|k-1} F_kᵀ + Q_k
x_k|k-1 = f(x_{k-1|k-1}, u_k)
Update Step:
K_k = P_k|k-1 H_kᵀ (H_k P_k|k-1 H_kᵀ + R_k)⁻¹
x_k|k = x_k|k-1 + K_k (z_k - h(x_k|k-1))
P_k|k = (I - K_k H_k) P_k|k-1
Parameters:
Q: Process noise covariance (4x4) - models gyroscope uncertaintyR: Measurement noise covariance (3x3) - models accelerometer uncertainty
- Python 3.8+
- Node.js 18+ (for web frontend)
- Docker & Docker Compose (optional)
# Install dependencies
make install
# or
pip install -r requirements.txt
# For development
make install-dev
# or
pip install -r requirements-dev.txtcd web
npm installProcess IMU data with both filters:
python scripts/run_fusion.py data/sample_imu.csv --filter BOTH --plotParameter sweep:
python scripts/sweep_parameters.py data/sample_imu.csv --output sweep_resultsfrom src.filters import ComplementaryFilter, ExtendedKalmanFilter
from src.io import IMULoader
# Load data
data = IMULoader.load_csv("data/sample_imu.csv")
# Complementary filter
comp_filter = ComplementaryFilter(alpha=0.96, dt=1.0/data.sample_rate)
comp_orientations, comp_quaternions = comp_filter.process_batch(
data.accelerometer,
data.gyroscope,
data.timestamps
)
# Extended Kalman Filter
ekf_filter = ExtendedKalmanFilter(dt=1.0/data.sample_rate)
ekf_orientations, ekf_quaternions, ekf_covariances = ekf_filter.process_batch(
data.accelerometer,
data.gyroscope,
data.timestamps
)Start API server:
make run-api
# or
uvicorn api.main:app --reloadEndpoints:
POST /upload- Upload IMU CSV filePOST /process/complementary- Process with complementary filterPOST /process/ekf- Process with EKFPOST /compare- Compare both filters
Example:
curl -X POST "http://localhost:8000/process/complementary" \
-F "file=@data/sample_imu.csv" \
-F "alpha=0.96"Start web frontend:
make run-web
# or
cd web && npm startNavigate to http://localhost:3000 to access the dashboard.
Start all services:
make run-docker
# or
docker-compose up --buildThis starts:
- API server at
http://localhost:8000 - Web dashboard at
http://localhost:3000
IMU CSV files should have the following columns:
timestamp: Time in secondsax, ay, az: Accelerometer readings in m/s²gx, gy, gz: Gyroscope readings in rad/s
Example:
timestamp,ax,ay,az,gx,gy,gz
0.000,0.12,-0.05,-9.78,0.001,-0.002,0.001
0.010,0.11,-0.04,-9.79,0.002,-0.001,0.000
...
Measures the difference between estimated and true orientations:
RMSE = sqrt(mean((θ_true - θ_estimated)²))
Quaternion-based angular error:
θ_error = 2 * arccos(|q_true · q_estimated|)
- Lower RMSE: Better accuracy
- Lower orientation error: Better alignment
- Stable covariance (EKF): Consistent uncertainty estimates
For a static IMU (gravity only), both filters should converge to near-zero orientation:
- Complementary filter: α = 0.96 typically works well
- EKF: Q = 0.01I, R = 0.1I provides good balance
For dynamic motion:
- Complementary filter: Higher α (0.98) for better tracking
- EKF: Tune Q/R based on sensor noise characteristics
Use sweep_parameters.py to find optimal parameters:
python scripts/sweep_parameters.py data/sample_imu.csv \
--alpha-min 0.90 \
--alpha-max 0.99 \
--alpha-steps 20make test
# or
pytest tests/ -vCode formatting:
make formatLinting:
make lintClean generated files:
make cleansrc/filters/complementary.py: Complementary filter implementation with bias estimationsrc/filters/ekf.py: Extended Kalman Filter with quaternion state representationsrc/io/imu_loader.py: CSV loader with flexible column mappingsrc/utils/metrics.py: RMSE, MAE, and orientation error calculationssrc/utils/visualization.py: Matplotlib plotting utilities
/upload: Validate and parse IMU CSV files/process/complementary: Run complementary filter with configurable α/process/ekf: Run EKF with configurable Q/R matrices/compare: Run both filters and return comparison metrics
OrientationViewer: Three.js 3D visualization with playback controlsSignalPlots: Plotly.js charts for raw signals and fused orientationsFileUpload: Drag-and-drop CSV upload with parameter controls
- Batch processing: Use
process_batch()for efficiency - Vectorization: All operations use NumPy for speed
- Memory: Large datasets (>100k samples) may require chunking
- Real-time: Single
update()/step()calls are optimized for <1ms latency
- Yaw drift: Without magnetometer, yaw estimation drifts over time
- Gimbal lock: Euler angles have singularities (quaternions avoid this)
- Ground truth: RMSE requires known reference trajectories
- Sensor calibration: Assumes calibrated IMU (bias removal included)
- Magnetometer integration for yaw correction
- SLAM integration for absolute positioning
- Real-time streaming API
- Mobile app for live IMU capture
- Machine learning-based filter tuning
- Multi-IMU sensor fusion
MIT License - see LICENSE file for details
If you use INVARI in your research, please cite:
@software{invari2024,
title={INVARI: Sensor Fusion Platform for IMU Data},
author={Forgingalex},
year={2024},
url={https://github.com/Forgingalex/invari}
}Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For issues and questions:
- GitHub Issues: Create an issue
- Documentation: See README.md and QUICKSTART.md
Built with ❤️ from me