Sensor-fused 2D rat tracking with JAX EKF/UKF for SpikeGadgets/Trodes
Project description
trodestrack
Sensor-fused 2D rat tracking with JAX EKF/UKF for neuroscience research
trodestrack combines video tracking (Trodes LEDs and/or DeepLabCut keypoints) with IMU data from SpikeGadgets headstages to provide accurate position, velocity, and heading estimates for freely-moving rats on behavioral mazes.
Features
- Sensor Fusion: Extended Kalman Filter (EKF) and Unscented Kalman Filter (UKF) for combining video (~30 Hz) and IMU (100 Hz) measurements
- 3D IMU Support: Full 6-axis IMU processing (3-axis gyro + 3-axis accel) with gravity compensation
- Online & Offline Processing: Real-time filtering and RTS smoothing for offline analysis
- Robust Handling: Occlusions, LED swaps, reflections, and sensor dropout
- JAX-Accelerated: High-performance implementation using JAX - 316× realtime on CPU, GPU-ready
- Rich Simulation: Comprehensive synthetic data generation for testing and validation
- Diagnostic Visualization: Publication-quality video output for quality control
Hardware Compatibility
Supported IMU Hardware:
trodestrack is designed for SpikeGadgets headstages with integrated 6-axis IMU sensors.
Official Hardware Specifications (source: SpikeGadgets Product Manual):
- 3-axis accelerometer: ±2g range, 16-bit signed (0.000061g per LSB)
- 3-axis gyroscope: ±2000 deg/s range, 16-bit signed (0.061 deg/s per LSB)
- Sensor refresh rate: 104 Hz (when both sensors enabled)
- Internal sampling: 500 Hz per sensor (both enabled), 1 kHz (single sensor)
- Output format: Sample-and-hold repeats expand 104 Hz data to ~20-30 kHz nominal rate
Data Processing:
- Preprocessing removes sample-and-hold duplicates → ~100 Hz effective rate
- Timestamp-based integration handles variable sampling rates
- Compatible with both 2D IMU mode (gyro-Z, accel-XY) and full 3D mode (all 6 axes)
Simulation Defaults:
- All synthetic data generation uses realistic SpikeGadgets specifications
- IMU rate: 104 Hz (matches hardware sensor refresh rate)
- Noise levels: 0.01 °/s/√Hz gyro, 0.2 mg/√Hz accel (per SpikeGadgets spec)
- Ensures simulations accurately predict real-world performance
Video Tracking:
- Trodes LED detection (dual LED setup for heading)
- DeepLabCut keypoint tracking (any pose estimation output)
- Camera rate: typically 30 Hz (configurable)
Installation
Requirements
- Python ≥ 3.11
- uv package manager
Install from source
git clone https://github.com/edeno/trodestrack.git
cd trodestrack
uv sync
Quick Start
1. Generate and Filter Synthetic Data (3 minutes)
The fastest way to understand TrodesTrack is to run the EKF example on synthetic data:
# Clone and setup
git clone https://github.com/edeno/trodestrack.git
cd trodestrack
uv sync
# Run EKF on basic scenarios (stationary, constant velocity, circular)
uv run python examples/03_ekf_basic_scenarios.py
This generates 3 diagnostic PNGs showing filter performance, bias convergence, and NEES consistency checks. Key insight: gyro bias is only observable during rotation!
2. Compare EKF vs UKF
uv run python examples/04_ukf_basic_scenarios.py
Compares sigma-point (UKF) vs Jacobian (EKF) approaches. Verdict: EKF wins 5/9 metrics and is 1-5× faster—start with EKF!
3. Test Dropout Robustness
uv run python examples/05_ekf_with_dropouts.py
uv run python examples/06_ukf_with_dropouts.py
Simulates 10%, 20%, and 30% camera dropout to stress-test IMU-only periods.
4. Use Smoothing for Offline Analysis
uv run python examples/07_smoother_demonstration.py
Shows how backward RTS smoothing achieves 3× drift reduction on 5-second dropout by using future observations.
5. Generate QA Reports
uv run python examples/08_qa_report_generation.py
Creates a publication-quality PDF with all PRD metrics, NEES/NIS checks, and time series plots.
Python API Examples
Generate synthetic data
from trodestrack.sim.rat_imu import RatIMUSimConfig, simulate_rat_imu
# Default config matches SpikeGadgets hardware (104 Hz IMU, realistic noise)
config = RatIMUSimConfig(duration_s=10.0, seed=42)
sim = simulate_rat_imu(config)
Run EKF filter
from trodestrack.models.ekf import ekf_forward, EKFConfig, ekf_initialize_state
# Initialize from simulation
cfg = EKFConfig()
x0, P0 = ekf_initialize_state(sim, cfg)
# Run filter
fwd = ekf_forward(x0, P0, cfg, sim)
Working with State Layouts (Recommended Pattern)
TrodesTrack uses an explicit state layout system to eliminate hardcoded dimension assumptions and support multiple tracking modes (5D, 8D, 10D, 15D states). Always use state layouts instead of magic indices like [:, 0:2].
from trodestrack.models.ekf import extended_kalman_filter, EKFConfig
from trodestrack.models.state_layout import get_layout
from trodestrack.sim.simple import simulate_circular, SimpleSimConfig
# Generate simulation and run filter
sim_config = SimpleSimConfig(duration_s=10.0)
sim = simulate_circular(sim_config)
ekf_config = EKFConfig()
result = extended_kalman_filter(ekf_config, sim)
# Get state layout from filter config (BEST PRACTICE!)
layout = get_layout(ekf_config.state_mode) # Usually "2d_full" (8D state)
# ✅ GOOD: Extract states using layout indices (dimension-agnostic)
positions = result.filtered_means[:, layout.pos_idx] # (N, 2) in meters
velocities = result.filtered_means[:, layout.vel_idx] # (N, 2) in m/s
headings = result.filtered_means[:, layout.heading_idx] # (N,) in radians
# ❌ BAD: Hardcoded indices (breaks when switching state modes!)
# positions = result.filtered_means[:, 0:2] # Fragile! Don't do this!
# Extract uncertainties (covariances) using layout indices
P = result.filtered_covariances # (N, 8, 8) full covariance
pos_cov = P[:, layout.pos_idx, :][:, :, layout.pos_idx] # (N, 2, 2) position covariance
pos_std = np.sqrt(np.diagonal(pos_cov, axis1=1, axis2=2)) # (N, 2) position uncertainty
# Plot position with ±2σ uncertainty bands
import matplotlib.pyplot as plt
t = sim['t_cam_exp']
plt.plot(t, positions[:, 0], label='x')
plt.fill_between(t,
positions[:, 0] - 2*pos_std[:, 0],
positions[:, 0] + 2*pos_std[:, 0],
alpha=0.3, label='±2σ')
plt.xlabel('Time (s)')
plt.ylabel('X Position (m)')
plt.legend()
plt.show()
Available State Layouts:
| Layout String | Dimensions | State Vector | Use Case |
|---|---|---|---|
"2d_full" |
8D | [x, y, vx, vy, θ, b_gz, b_ax, b_ay] |
Standard sensor fusion (camera + IMU) |
"vision_only" |
5D | [x, y, vx, vy, θ] |
Camera-only tracking (no biases) |
"2d_cam_3d_imu" |
10D | [x, y, vx, vy, vz, θ, b_gz, b_ax, b_ay, b_az] |
2D camera with 3D accel (detect rearing) |
"3d_euler" |
15D | [x, y, z, vx, vy, vz, roll, pitch, yaw, b_gx, b_gy, b_gz, b_ax, b_ay, b_az] |
Full 3D tracking with Euler angles |
"3d_quat" |
16D | [x, y, z, vx, vy, vz, qw, qx, qy, qz, b_gx, b_gy, b_gz, b_ax, b_ay, b_az] |
Full 3D tracking with quaternions |
Why use state layouts?
- Dimension-agnostic code: Works with 5D, 8D, 10D, 15D states without modification
- Self-documenting:
layout.pos_idxis clearer than[:, 0:2] - Robust to changes: Switching state modes doesn't break your analysis code
- Matches internal implementation: Filters use the same layout system
See src/trodestrack/models/state_layout.py for full API documentation.
Generate QA report
from trodestrack.qa.report import generate_filter_report
generate_filter_report(
states_fwd=fwd['x'],
states_truth=sim['x_truth'],
covariances=fwd['P'],
config=cfg,
output_path="report.pdf"
)
Explore All Examples
See examples/README.md for the complete learning path. Examples are numbered to teach concepts progressively:
- 01-02: Simulation fundamentals
- 03-04: Filter basics (EKF and UKF)
- 05-06: Robustness (dropouts and occlusions)
- 07: Smoothing techniques
- 08: QA reporting
Project Status
Current Milestone: Integration & QA (M4) - Ready for Production Testing
Completed ✅
- ✅ Simulation Foundation (M1)
- Realistic rat motion with Ornstein-Uhlenbeck dynamics
- Full IMU physics (tilt, drag, bias random walks)
- Camera dropout, LED swaps, occlusions, and reflections
- Arena boundaries with inelastic collisions
- ✅ Filter Implementation (M2)
- Extended Kalman Filter (EKF) with IMU pre-integration
- Unscented Kalman Filter (UKF) with sigma-point transforms
- Rauch-Tung-Striebel (RTS) smoothing for offline analysis
- Iterated EKF/Smoother (IEKF/IEKS) for nonlinear accuracy
- ✅ Robustness Features (M3)
- Mahalanobis gating for outlier rejection
- Zero-velocity updates (ZUPT) for stationary periods
- Adaptive process noise during camera dropout
- Heading pseudo-measurements from dual LEDs
- LED spacing validation and adaptive measurement noise
- ✅ QA & Diagnostics (M4)
- Comprehensive metrics (RMSE, NEES, NIS, innovation statistics)
- Publication-quality plots and multi-page PDF reports
- CLI tool:
trodestrack report --run run1/ --pdf report.pdf - Diagnostic videos with 9-panel filter state visualization
- ✅ Testing & Validation
- 236+ unit, integration, and property tests (all passing)
- PRD acceptance criteria achieved:
- Position RMSE ≤ 2 cm ✓
- Velocity RMSE ≤ 10 cm/s ✓
- Heading RMSE ≤ 7° ✓
- Throughput: 316× realtime (CPU), latency: 0.11 ms/frame ✓
- ✅ 3D IMU Support (M5)
- Full 6-axis IMU processing (gyro + accel)
- Gravity-aware dynamics with 3D acceleration
- 2D pose estimation with 3D IMU inputs
- Improved drift handling during vision dropout
- ✅ JAX Optimization (M6)
- JIT-compiled UKF (mirrors EKF pattern)
- Vectorized operations (sigma points, bias freeze)
- Host-side preprocessing for efficiency
- 316× realtime speedup on 5-minute session
In Progress 🚧
- 🚧 I/O Loaders
- Trodes LED detection format
- DeepLabCut keypoint format
- SpikeGadgets raw IMU format
- 🚧 CLI Tools
trodestrack smooth --config session.yamltrodestrack online --config session.yaml
Documentation
User Documentation
- Examples README - Start here! Progressive learning path with 8 pedagogical examples
- Tuning Guide - NEES-based diagnostics and parameter selection (coming soon)
- Troubleshooting Guide - Common filter failures and solutions (coming soon)
Developer Documentation
- Product Requirements Document (PRD.md) - Full project specification
- Development Guide (CLAUDE.md) - Commands and architecture
- Task Tracking (TASKS.md) - Current roadmap and completion status
Development
Run tests
uv run pytest tests/ -v
Code quality
# Type checking
uv run mypy src/trodestrack --ignore-missing-imports
# Linting
uv run ruff check src/ tests/
# Formatting
uv run black src/ tests/
Development commands
See CLAUDE.md for complete list of development commands and project architecture.
Architecture
trodestrack/
sim/ # Simulation: analytic scenarios + realistic rat IMU
models/ # EKF, UKF, RTS/IEKS smoothers, state initialization
runtime/ # Online filter API + offline smoother workflows
qa/ # Metrics (RMSE, NEES, NIS), plots, PDF reports
viz/ # Diagnostic videos with multi-panel state visualization
cli/ # CLI: trodestrack report (more commands coming)
io/ # Data loaders: Trodes, DLC, SpikeGadgets (coming soon)
config/ # Configuration schemas (coming soon)
Contributing
This project follows strict test-driven development (TDD) practices:
- Write tests first
- Run tests and verify they fail
- Implement features
- Run tests until they pass
- Refactor for clarity
See PRD.md for development guidelines and code style requirements.
Citation
If you use trodestrack in your research, please cite:
@software{trodestrack2025,
title={trodestrack: Sensor-fused 2D rat tracking with JAX EKF/UKF},
author={Your Name},
year={2025},
url={https://github.com/edeno/trodestrack}
}
License
MIT License - see LICENSE file for details.
Acknowledgments
- SpikeGadgets for hardware specifications
- DeepLabCut team for pose estimation framework
- JAX team for high-performance numerical computing
Contact
For questions, issues, or feature requests, please open an issue on GitHub.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trodestrack-0.1.0.tar.gz.
File metadata
- Download URL: trodestrack-0.1.0.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2113e740ce5594960c97f242633bb350720434cfbfca08bfa69f7d073a512f64
|
|
| MD5 |
028ca6ae65a0fdae400a7f44e8765e82
|
|
| BLAKE2b-256 |
c0efd023a3a281ed06eaafa3b28657ad5d6834560bb4bb15501dd77b7c2307af
|
Provenance
The following attestation bundles were made for trodestrack-0.1.0.tar.gz:
Publisher:
ci.yml on edeno/trodestrack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trodestrack-0.1.0.tar.gz -
Subject digest:
2113e740ce5594960c97f242633bb350720434cfbfca08bfa69f7d073a512f64 - Sigstore transparency entry: 995159978
- Sigstore integration time:
-
Permalink:
edeno/trodestrack@5fa220f896893a28bbedf13ea0d2052914efee3e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/edeno
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5fa220f896893a28bbedf13ea0d2052914efee3e -
Trigger Event:
push
-
Statement type:
File details
Details for the file trodestrack-0.1.0-py3-none-any.whl.
File metadata
- Download URL: trodestrack-0.1.0-py3-none-any.whl
- Upload date:
- Size: 134.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81e0a5b886b78e8af7e91fd54ca255096e721151f87cb07a9ead0b6e660a8aa1
|
|
| MD5 |
9d4b1a6fb6de56d3fdc900919da74ea3
|
|
| BLAKE2b-256 |
6e30b60120652c5a7d09469914cd82d69b5f24d6f1ab4a8bdbfdb7be0bf3d27d
|
Provenance
The following attestation bundles were made for trodestrack-0.1.0-py3-none-any.whl:
Publisher:
ci.yml on edeno/trodestrack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trodestrack-0.1.0-py3-none-any.whl -
Subject digest:
81e0a5b886b78e8af7e91fd54ca255096e721151f87cb07a9ead0b6e660a8aa1 - Sigstore transparency entry: 995159980
- Sigstore integration time:
-
Permalink:
edeno/trodestrack@5fa220f896893a28bbedf13ea0d2052914efee3e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/edeno
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5fa220f896893a28bbedf13ea0d2052914efee3e -
Trigger Event:
push
-
Statement type: