Radar-RF Fusion Tracking for UAVs.
This repository contains implementation and evaluation code for tracking UAVs
with AERPAW Dataset-28 / Dryad DOI 10.5061/dryad.7d7wm3898. The initial
baseline is an asynchronous constant-velocity Kalman fusion tracker built on
PyRecEst.
Large datasets and generated bulk artifacts are intentionally not stored in this repository.
python -m pip install -e ".[dev]"After installation, prefer the raft-uav console script for experiments. It
routes through the canonical tracklet-Viterbi wrapper, registers the
tracklet-viterbi radar-association mode, and exposes the wrapper-only
--tracklet-* options. Use python -m raft_uav.cli only for legacy base-CLI
debugging.
Download and extract the Dryad archive outside git, for example:
data/raw/AADM2025Dryad/
RF Sensor and Radar/
<flight>/
AADM*.csv
radar_data_*.json
date_time_vehicleOut.txt
The loader starts with the RF Sensor and Radar folder because it contains the modalities needed for the tracking work.
Inspect available flights:
raft-uav inspect data/raw/AADM2025DryadRun the canonical range-covariance tracklet-Viterbi fusion baseline on one flight:
raft-uav run-baseline data/raw/AADM2025Dryad \
--flight Opt2 \
--radar-association tracklet-viterbi \
--tracklet-variant range-covarianceFor a maneuver-aware replay of the same association path, add
--tracklet-replay-tracker imm. For a strictly legacy CV/catProb baseline,
spell it out explicitly:
raft-uav run-baseline data/raw/AADM2025Dryad --flight Opt2 --radar-association catprobRun the normalized-innovation-squared gated baseline on one flight:
raft-uav run-baseline data/raw/AADM2025Dryad --flight Opt2 --enable-gating --rf-gate-prob 0.99 --radar-gate-prob 0.99Run the soft NIS covariance-inflation baseline on one flight:
raft-uav run-baseline data/raw/AADM2025Dryad --flight Opt2 --robust-update nis-inflate --rf-gate-prob 0.99 --radar-gate-prob 0.99Tune source-specific inflation strength:
raft-uav run-baseline data/raw/AADM2025Dryad --flight Opt2 --robust-update nis-inflate --rf-inflation-alpha 1.5 --radar-inflation-alpha 0.5Run the Opt1-Opt3 source-specific inflation grid:
python scripts/run_source_specific_inflation_grid.py data/raw/AADM2025DryadRun the Opt1-Opt3 radar association ablation:
python scripts/run_radar_association_ablation.py data/raw/AADM2025DryadRun the Opt1-Opt3 smoothing ablation:
python scripts/run_smoothing_ablation.py data/raw/AADM2025DryadAnalyze online radar association against the diagnostic oracle on Opt1:
python scripts/analyze_association_failures.py data/raw/AADM2025Dryad --flight Opt1Calibrate residual RF/radar time offsets on training flights, then apply the held-out offsets explicitly in the baseline run:
raft-uav-lofo-time-offset data/raw/AADM2025Dryad \
--flight Opt1 --flight Opt2 --flight Opt3 \
--output-dir outputs/lofo_time_offset
python -m raft_uav.cli run-baseline data/raw/AADM2025Dryad \
--flight Opt2 \
--rf-time-offset-correction-s <rf_offset_s_from_train_folds> \
--radar-time-offset-correction-s <radar_offset_s_from_train_folds>The loader-level --rf-clock-offset-s and --radar-clock-offset-s arguments
control conversion from raw sensor timestamps to the truth timeline. The
--*-time-offset-correction-s arguments are residual calibrated corrections
applied after normalization, which matches the output convention of
raft-uav-lofo-time-offset.
Build a paper-style comparison table with the paper-compatible hard-gated fusion row:
raft-uav-diagnose-paper-table data/raw/AADM2025Dryad \
--flight Opt1 \
--fusion-association paper-compatible \
--stable-segment-min-frames 100 \
--stable-segment-max-transition-speed-mps 65By default, raft-uav-diagnose-paper-table emits unsmoothed paper-table fusion
rows. Pass --include-smoothed-fusion only when fixed-lag rows are desired as
offline enhancement diagnostics.
Run the strict Table-II parity diagnostic. This path uses a 95% chi-square NIS
gate, disables catProb by default, requires Fortem range_m for the 800 m
radar gate, estimates RF/radar covariances from truth residuals, evaluates
errors at measurement/output timestamps, and bootstraps the Kalman filter from
the selected radar track rather than the first RF row:
raft-uav-paper-strict data/raw/AADM2025Dryad \
--flight Opt1 \
--origin-config config/origins.toml \
--variant rerun \
--count-mismatch-action fail \
--output-dir outputs/paper-strictCreate config/origins.toml from config/origins.example.toml and fill in the
LW1 coordinates before running strict paper-parity reproduction.
Before comparing algorithms against Table II, run the count-fingerprint audit. It writes the RF/radar/KF stage counts, file paths, and count deltas against the published reference rows so wrong flight variants, timestamp normalization, or range-gate semantics show up before tuning starts:
raft-uav-paper-fingerprint data/raw/AADM2025Dryad \
--flight Opt1 \
--origin-config config/origins.toml \
--variant rerun \
--output-dir outputs/paper-fingerprintWhen the paper-reference counts still do not match, enumerate both likely file
variants and the radar-track selection-order ambiguity before changing tracker
parameters. The summary CSV is ranked by absolute count delta, so the top row is
the best paper-fingerprint candidate for a subsequent raft-uav-paper-strict
run:
raft-uav-paper-fingerprint data/raw/AADM2025Dryad \
--flight Opt1 \
--origin-config config/origins.toml \
--enumerate-file-variants \
--enumerate-radar-track-selection-orders \
--output-dir outputs/paper-fingerprint-gridFor LW1-origin reproduction, pass the origin explicitly because the coordinates are not hard-coded in the repository:
raft-uav-paper-strict data/raw/AADM2025Dryad \
--flight Opt1 \
--enu-origin lw1 \
--lw1-origin-lla <LAT>,<LON>,<ALT> # or set RAFT_UAV_LW1_ORIGIN_LLAPaper-parity commands persist the selected RF/radar/truth filenames, variants,
sizes, SHA-256 digests, and ENU origin in their manifests. Use
--variant original or --variant rerun when reproducing a specific Opt1 file
choice; --variant auto keeps the historical behavior of preferring rerun files
when present. The example config/origins.example.toml intentionally contains a
0,0,0 placeholder and strict paper diagnostics reject it until the actual LW1
origin is supplied.
Run the same paper-compatible hard-preselector path directly through the
baseline runner, so the output artifacts are comparable with other
run-baseline rows:
raft-uav run-baseline data/raw/AADM2025Dryad \
--flight Opt1 \
--radar-association paper-compatible \
--paper-compatible-bootstrap-source radar \
--stable-segment-range-gate-m 800 \
--smoother fixed-lag --smoother-lag-s 20For a deliberate catProb ablation of the paper-compatible path, use
--paper-compatible-catprob-threshold <value>. The generic
--radar-catprob-threshold continues to configure legacy catProb and
non-paper association modes, but it does not affect paper-compatible parity
runs.
The paper-compatible fusion path applies an 800 m radar range gate, radar
bootstrap by default, optional radar class-probability thresholding, NIS gates
for RF/radar updates, and records a
radar missed_detection posterior when no radar candidate passes the hard
preselector. The table also includes stable range-gated radar segment rows,
including an interpolated full-frame diagnostic, to separate clean radar
coverage from long-gap fill behavior. The stable-segment knobs control how long
a same-track run must be before it is trusted and how aggressively separate
segments may be stitched across radar ID changes.
Run the Opt1-Opt3 radar candidate class-probability threshold ablation:
python scripts/run_candidate_threshold_ablation.py data/raw/AADM2025Dryad --thresholds 0.4 0.5Sweep the stable radar segment diagnostic without running fusion or oracle rows:
python scripts/run_stable_radar_segment_ablation.py data/raw/AADM2025Dryad \
--min-segment-frames 75 100 150 \
--max-transition-speeds-mps 35 65 100The script writes per-flight rows plus aggregate rows to the summary CSV, and a
separate ranking CSV next to it for quickly identifying the best knob setting.
Ranking defaults to --ranking-min-coverage 0.95, so low-coverage rows remain
visible but are not treated as recommendation-eligible. Ranking rows also
include coverage-penalized error columns and a Pareto-front flag for comparing
coverage/error tradeoffs. A compact recommendation JSON is written next to the
summary and ranking CSVs for workflow and paper-note automation.
Run the Opt1-Opt3 PDA-mixture association ablation:
python scripts/run_pda_association_ablation.py data/raw/AADM2025DryadRun the Opt1-Opt3 PyRecEst MHT track-bank ablation:
python scripts/run_track_bank_ablation.py data/raw/AADM2025DryadRun the current best non-oracle preset on one flight:
raft-uav-best-non-oracle data/raw/AADM2025Dryad --flight Opt2Run the integrated result-improvement suite. This executes the leakage-safe calibration/tuning/evaluation workflow used for result-oriented development: LOFO time-offset calibration, LOFO radar covariance tuning, nested LOFO tuning, leave-flight-out SOTA evaluation, oracle-gap diagnostics, and constrained leaderboard ranking.
raft-uav-result-improvement-suite data/raw/AADM2025Dryad \
--flights Opt1 Opt2 Opt3 \
--skip-existingUse --dry-run to write the manifest and print the exact commands without
executing them.
The preset expands to range-covariance tracklet-Viterbi association, IMM replay,
Student-t robust updates, and 20-second fixed-lag RTS smoothing. It deliberately
does not use truth-gated or nearest-truth radar association; truth is used only
for the same post-run metrics already produced by run-baseline.
The first baseline is deliberately conservative. It is meant to reproduce the published constant-velocity Kalman fusion setup before adding robust gating, learned sensor uncertainties, maneuvering models, and smoothing.
Baseline runs write gitignored per-flight artifacts under outputs/baseline/:
estimates.csvdiagnostics.csvselected_radar.csvmetrics.jsontrajectory.png
Radar JSON frames contain many Fortem trackData entries. The lower-level
base-CLI default remains --radar-association catprob, which keeps radar rows
whose UAV class probability catProb[0] is at least 0.5. For
result-oriented reproduction runs, prefer the explicit tracklet-Viterbi path:
raft-uav run-baseline data/raw/AADM2025Dryad \
--flight Opt2 \
--radar-association tracklet-viterbi \
--tracklet-variant range-covarianceThe installed wrapper defaults the tracklet implementation to
range-covariance when tracklet-viterbi is selected, but commands in this
README spell it out to avoid accidental regressions to the base dispatcher.
Use --radar-association oracle-nearest-truth only as a diagnostic upper bound
because it uses ground truth. The online alternatives are
--radar-association prediction-nis,
--radar-association track-continuity,
--radar-association paper-compatible, which applies hard range/catProb/NIS
validation with radar coasting, and the experimental
--radar-association geometry-score mode, which adds velocity consistency,
track-switch, and UAV class-probability terms to the NIS score. The
paper-reproduction-oriented --radar-association paper-largest-continuous-track
mode applies the configured radar range gate, defaults to 800 m via
--stable-segment-range-gate-m, and keeps the longest continuous Fortem
track_id segment without class-probability scoring or segment stitching. The
experimental --radar-association pda-mixture mode keeps multiple candidates
inside one radar update by using NIS/class-probability weights and adding the
candidate spread to the measurement covariance. The experimental
--radar-association track-bank mode uses PyRecEst's
MultiHypothesisTracker to keep multiple single-UAV association hypotheses
alive across radar frames. Baseline runs also write hypotheses.csv for modes
that expose per-hypothesis diagnostics. Legacy --radar-selection modes are
retained for schema debugging and reproducibility; use
--radar-selection catprob-all only to reproduce the historical behavior that
feeds every above-threshold radar candidate in a frame into the single-target
filter.
Use --smoother fixed-lag --smoother-lag-s 20 to apply a 20-second RTS
fixed-lag pass before metrics and plots are written. --smoother rts runs the
full offline RTS smoother and is mainly useful as an upper-bound diagnostic.
The IMM runner supports the same post-filter smoothing switches, so online IMM, bounded-latency IMM, and offline upper-bound IMM rows can be compared directly:
raft-uav-imm data/raw/AADM2025Dryad --flight Opt2 --tracker imm
raft-uav-imm data/raw/AADM2025Dryad --flight Opt2 --tracker imm \
--smoother fixed-lag --smoother-lag-s 20
raft-uav-imm data/raw/AADM2025Dryad --flight Opt2 --tracker imm \
--smoother rtsRun the leave-one-flight-out SOTA protocol with the explicit online/fixed-lag/RTS IMM rows and the current best non-oracle tracklet replay row:
python scripts/run_leave_flight_out_sota.py data/raw/AADM2025Dryad \
--methods cv_catprob imm_catprob imm_catprob_fixed_lag imm_catprob_rts \
imm_tracklet_viterbi_fixed_lagFor the leakage-safe calibrated heteroscedastic CV row, use:
python scripts/run_leave_flight_out_sota.py data/raw/AADM2025Dryad \
--methods hetero_cv_lofo_nis_fixed_lag