PROJECT
ARGUS
A fully containerized, event-driven satellite intelligence pipeline — ingesting raw EO imagery, geo-localizing it via a Vision Transformer, and streaming live telemetry to a terminal dashboard and real-time GUI viewer.
Architecture
An event-driven pipeline built on battle-tested primitives — chosen for performance, operational simplicity, and future scale.
Why Redpanda over Kafka?
Redpanda is written in C++ using the Seastar async framework. It eliminates ZooKeeper, ships as a single binary, and delivers 10× lower median produce latency. Full Kafka 3.x API compatibility — zero client code changes.
Why Go for the watcher?
Go's goroutine model makes it trivial to watch directories at kernel speed (inotify via fsnotify) while concurrently uploading to MinIO and producing events. The compiled binary is 8 MB and idles at <1% CPU.
Why MinIO?
S3-API compatible, runs on a single node with erasure-coding durability, and has a Go SDK with streaming upload support. Swapping MinIO for AWS S3 in production requires zero code changes.
Why PostGIS?
Future spatial queries — bounding-box filtering, trajectory reconstruction, proximity alerts — require a spatial index. PostGIS gives us these for free on top of PostgreSQL, making the schema forward-compatible.
CV & ML Logic
How a Vision Transformer turns raw pixels into GPS coordinates — the mathematics behind the prediction.
ViT-Base/16 Embedding Pipeline
The satellite tile is resized to $224 \times 224$ and partitioned into non-overlapping patches of size $16 \times 16$, yielding:
Each patch is flattened and linearly projected to a $D = 768$-dimensional embedding. A learnable [CLS] token is prepended. With positional encodings added:
The sequence passes through $L = 12$ Transformer encoder layers, each consisting of Multi-Head Self-Attention (MSA) and MLP sub-layers with layer normalization:
After all $L$ layers, the CLS token output $z^0_L \in \mathbb{R}^{768}$ is extracted as the scene embedding. It is L2-normalized before storage and retrieval.
Cosine Similarity Geo-Search
Given query embedding $\mathbf{q}$ and corpus matrix $E \in \mathbb{R}^{874 \times 768}$, cosine similarity is computed for all reference tiles simultaneously:
Since embeddings are pre-L2-normalized ($\|\mathbf{e}_i\| = 1$), this reduces to a dot product — vectorised as a single matrix-vector multiply completing in ~3 ms on CPU.
The top-$k = 5$ matches are selected. A softmax-temperature-weighted mean GPS coordinate is derived:
where $\tau = 0.07$ sharpens the weight distribution, ensuring the closest match dominates the prediction.
Workflow Visualization
Follow a single satellite tile from raw downlink to geo-localized result.
Component Breakdown
Every service in the stack — its language, role, and key dependencies.
Sovereign View
The Raylib GUI renders the latest processed tile with a geo-lock crosshair over the predicted GPS centroid.
- local_sync/latest_processed.jpg is written atomically by the processor worker.
- The Raylib GUI polls the file's mod-time every frame (sub-ms overhead).
- On change, it reloads the texture — no IPC, no network required.
- GPS coords are painted as a crosshair overlay with confidence HUD.
- Corner brackets — viewport boundary indicators.
- Scan-line effect — CRT aesthetic render pass.
- Crosshair rings — pulsing geo-lock confidence indicator.
- Coordinate overlay — LAT/LON + confidence percentage.
Quick Start
Prerequisites: Docker + Docker Compose, WSL2 (Debian/Ubuntu recommended) or native Linux, Go 1.21+, GCC + libraylib-dev (for GUI), tmux.
# Clone the repository
git clone https://github.com/parthbhanti22/seopc-ground-station-epics
cd seopc-ground-station-epics
# Make scripts executable
chmod +x launch.sh feed.sh
# One-command launch (opens tmux with 4 panes)
./launch.sh
# Force rebuild Docker images
./launch.sh --rebuild
# Drip-feed tiles every 5s (default)
bash feed.sh
# Custom interval (30s)
bash feed.sh 30
# Manually inject a single tile
sudo docker cp tiles/tile_0001.jpg \
seopc-project-satellite-1:/downlink_buffer/
# Press Q in the dashboard pane to exit TUI, then:
sudo docker compose down