ARGUS — Automated Recognition and Grading of Unidentified Streaks.
An end-to-end pipeline that detects satellite streaks in FITS telescope images.
Also a reference to Argus Panoptes (Ἄργος Πανόπτης), the hundred-eyed giant of Greek mythology — always vigilant, always watching.
Every detection is a line segment defined by two image-space endpoints (x1, y1, x2, y2).
When WCS is available those endpoints are resolved to sky coordinates and matched
against a local TLE catalog using SGP4 propagation. A FastAPI backend and React
frontend expose the full pipeline.
Images are processed as overlapping 400 px tiles so that short streaks remain visible at native scale. A frozen DINOv3 ViT-S/16 backbone extracts patch features from each tile; a lightweight convolutional head turns those features into a one-channel centerline heatmap that is bright where a streak axis runs and dark everywhere else. Connected bright regions are reduced to their principal-axis endpoints, remapped to full-frame coordinates, and passed through a suppression step that collapses duplicate detections from overlapping tiles. Nearby collinear fragments are stitched into single segments.
A satellite streak is a 1D structure — a directed line across the sensor. Bounding boxes and oriented rectangles introduce a width dimension that carries no physical meaning and makes matching and evaluation unnecessarily complex. Using raw endpoints preserves the information that matters (position, angle, extent) and keeps every downstream step — annotation, training targets, post-processing, evaluation, and API output — in the same coordinate space.
Early models reached >97% recall but precision was poor; blob-shaped heatmap activations triggered detections on noise and background gradients. Two changes fixed this:
- Asymmetric Loss (ASL) zeroes out the gradient contribution from easy negatives, focusing training on the genuinely ambiguous background pixels.
- clDice (Centerline Dice) computes loss via the soft morphological skeleton of the prediction, rewarding thin connected linear responses and penalising any prediction that spreads laterally or forms a blob.
Together they brought detection precision from ~45% to 91.8% with no recall
regression. See docs/loss_ablation_v9_v10_postmortem.md
for the full ablation.
Both endpoints of a matched detection are transformed to RA/Dec through the frame's WCS solution. The resulting sky track and observation timestamp are compared against pre-propagated TLE positions stored in a local SQLite catalog. No live catalog query is required at inference time.
For each detected streak, every object in the TLE catalog is propagated forward using SGP4 to its predicted topocentric position at the mid-exposure instant (the physically correct moment — the satellite is at the streak midpoint exactly halfway through the exposure). The angular separation between the predicted position and the observed streak midpoint feeds a Gaussian confidence score. That score is multiplied by a second factor that penalises orbital elements that have drifted far from the observation epoch, so a geometrically close match against a stale TLE is automatically discounted.
For unclipped streaks, the total positional residual is further decomposed into along-track (timing offset) and cross-track (orbital-plane offset) components. A large along-track residual implies the TLE epoch is off; a large cross-track residual implies the wrong satellite. The final confidence score is the product of both Gaussian factors times the TLE-age penalty.
The top candidate's TLE is then propagated at the exposure-start time to determine which detected tip is the beginning of the pass and which is the end. The expected streak length in pixels is also computed from the SGP4 angular velocity and the frame's plate scale, providing an independent sanity check against the measured length.
The API returns the top three ranked candidates per detection — each with its NORAD ID, satellite name, separation in arcseconds, along-track/cross-track residuals, and the composite confidence score — so the UI can present both a primary identification and the uncertainty around it.
The FastAPI backend wraps the full pipeline behind a REST interface. A FITS
file (or PNG/JPEG) is POSTed to /api/upload; the server enqueues it and
returns a job ID immediately. A background worker runs the heatmap detector,
applies WCS, propagates the TLE catalog via SGP4, scores all candidates, and
writes the results to SQLite. The client polls GET /api/result/{job_id} until
the job completes, at which point it receives the full detection list — each
entry containing image-space endpoints, RA/Dec sky coordinates, streak quality
flags, and the ranked identification candidates with their confidence breakdowns.
A rendered PNG overlay (detection segments drawn on the source image) is
available at GET /api/image/{job_id}.
The React frontend provides a drag-and-drop upload panel, a live job-status indicator, and a results view that displays the processed image with detections overlaid. For each streak the UI shows the primary satellite identification (name and NORAD ID), the composite match confidence, and the top alternative candidates — giving operators an immediate probabilistic answer to "what object caused this streak?" alongside the residuals that explain how certain that answer is.
FITS image
│
▼
┌─────────────────────────────────────────────────────┐
│ Inference pipeline (Python / PyTorch / astropy) │
│ │
│ FITS load → tile → DINOv3 ViT-S backbone (frozen) │
│ → conv head → centerline heatmap │
│ → connected-component endpoints │
│ → stitch + dedup → WCS → sky coords │
│ → SGP4 TLE match │
└──────────────────────┬──────────────────────────────┘
│ REST + WebSocket
▼
┌───────────────────────┐
│ FastAPI (Python) │
│ SQLite (SQLAlchemy) │
└──────────┬────────────┘
│ JSON API
▼
┌───────────────────────┐
│ React + Tailwind CSS │
│ Vite dev server │
└───────────────────────┘
| Layer | Technology |
|---|---|
| ML backbone | DINOv3 ViT-S/16 (frozen), PyTorch |
| Conv head | Custom single-channel heatmap head (PyTorch) |
| Image I/O | astropy.io.fits, scipy |
| Astrometry | astropy WCS |
| Satellite catalog | sgp4 propagation, local SQLite TLE store |
| API | FastAPI, SQLAlchemy (async), SQLite |
| Frontend | React 19, Tailwind CSS v4, Vite |
| Environment | Conda (satid), Python 3.10+ |
Training data is drawn from two sources:
- Atwood Observatory FITS — real observations captured across six sessions
(
Img_20260412,Img_20260515,Img_20260527,Img_20260528,20260530,Geo_20260520), totalling approximately 2,270 frames. - Synthetic augmentation — ~3,840 frames generated from rendered streak
composites (
argus_run13_npy) to supplement scarce short-streak examples.
The merged training annotation pool (all_train_run17_merged.json) contains
6,105 frames — 5,958 positive and 147 negative — with 12,647 endpoint
annotations. Streak lengths range from very short (~20 px) to long tracks
spanning most of the frame.
Positive frames receive oriented bounding box annotations in Frigate that are
immediately converted to endpoint pairs (x1, y1, x2, y2). Negative frames
(clear sky, no streaks) are retained as hard-negative tiles during dataset
construction. All annotation file paths are relative to ARGUS_DATA_ROOT.
A fixed 8% validation split and 42% negative-tile fraction are held constant across experiments so results are directly comparable. Synthetic frames are only used in training, never in the validation or test splits.
The backbone is always frozen; only the conv head is trained. Features are cached to disk before training so the backbone forward pass runs once per dataset, not once per epoch. This makes a 40-epoch run complete in minutes rather than hours on an M-series Mac.
The full pipeline:
- Build a window dataset from the merged annotation pool
- Cache ViT-S features once for train and validation splits
- Train the conv head with ASL + clDice loss, cosine LR schedule
- Evaluate with
eval.geometry_metricsonval_balanced_v1.json
See DEVELOPER.md for the full commands.
Model: vits_v11_asl_cldice — DINOv3 ViT-S/16, ASL + clDice loss, 400 px
tiles, v11 coord-validated dataset, evaluated on val_balanced_v1.json at
threshold = 0.70, peak floor = 0.85.
| Metric | Value |
|---|---|
| Recall | 98.8% (238 / 241) |
| Precision | 99.2% |
| False positives | 2 |
| Band | GT | Found | Recall |
|---|---|---|---|
| Short (< 50 px) | 61 | 61 | 100.0% |
| Medium (50–400 px) | 103 | 102 | 99.0% |
| Long (> 400 px) | 77 | 75 | 97.4% |
| Metric | Mean | Median | P90 |
|---|---|---|---|
| Angle error | 0.52° | 0.26° | 1.29° |
| Endpoint error | 11 px | 9 px | 15 px |
Radon-based angle refinement (T3) was evaluated and found to degrade both metrics significantly. T2 raw geometry is used in production.
inference/ — FITS loading, heatmap detection, post-processing, WCS, cross-ID
models/plain_dinov3/ — DINOv3 ViT heatmap model definition
training/ — endpoint datasets and cached-feature trainers
eval/geometry_metrics.py — canonical segment evaluator
scripts/ — dataset preparation, caching, evaluation, operations
api/, db/, frontend/ — FastAPI backend, SQLite persistence, React frontend
src/ — classical astrometry and satellite matching components
docs/ — postmortems and experiment notes
agent_docs/ — developer and assistant reference guides
results/ — per-run geometry_eval.json files (committed)
weights/ — trained model checkpoints (committed; DINOv3 backbones excluded)
ARGUS builds on the following research and prior work:
- DINOv3 by Meta AI Research provides the frozen visual backbones used by the heatmap detectors. See the DINOv3 repository and license and Siméoni et al. (2025).
- The training objective implements Asymmetric Loss from Ridnik et al. (2021) and clDice from Shit et al. (2021).
- The endpoint representation, segment-matching methodology, and optional Radon angle-refinement approach were informed by StreakMind.
- Cross-identification refinements, including midpoint scoring, along-track/cross-track residuals, direction disambiguation, expected streak length, and detection-quality conventions, were adapted from SkyTrack with acknowledgement to its author and contributors.
- The Gaussian satellite cross-identification confidence-scoring approach was informed by the work of Danarianto et al.
For environment setup, weight downloads, annotation workflow, training commands,
API/UI startup, and TLE catalog management, see DEVELOPER.md.