ML-based contrail detection and segmentation on sky-camera imagery — a runnable, end-to-end reference app (React → Node BFF → FastAPI → PyTorch U-Net) built as a job-application demo for the EUROCONTROL Contrail Avoidance (COAV) programme.
flowchart LR
U[Browser] -->|HTTP /api| FE[React + Vite frontend<br/>nginx :8080]
FE -->|/api proxy| BFF[Node.js + Express BFF<br/>:3000]
BFF -->|REST + multipart| ML[Python FastAPI<br/>:8000]
ML -->|forward pass| NN[PyTorch U-Net<br/>128x128 semantic segmentation]
NN -->|mask + stats| ML
ML -->|overlay PNG + coverage% + count| BFF
BFF --> FE
Pick a sample sky image (or upload your own); the app returns the original image with the predicted contrail mask overlaid, the contrail coverage % of the sky, and the number of distinct contrails detected (connected components).
A single coherent project that exercises the full stack named in the JD:
| JD requirement | Where it lives |
|---|---|
| React frontend | frontend/ — React 18 + Vite + TypeScript, sample gallery, upload, overlay rendering |
| Node.js | bff/ — Express Backend-for-Frontend fronting the ML service |
| Python / FastAPI / NumPy | ml-service/ — FastAPI app, NumPy synthetic-data pipeline |
| PyTorch + neural-network image segmentation | ml-service/ml_service/model.py — hand-written U-Net, BCE+Dice training |
| Docker | per-service Dockerfile + docker-compose.yml (3 services, healthchecks) |
| CI/CD | .github/workflows/ci.yml — pytest + ruff, bff tests, frontend build (matrix) |
| Technical documentation | this README + docs/ARCHITECTURE.md + docs/DECISIONS.md |
Aircraft condensation trails (contrails) that persist and spread into cirrus cloud trap outgoing longwave radiation and are a significant share of aviation's non-CO₂ climate impact. EUROCONTROL / MUAC have been pioneering operational contrail avoidance — small, targeted altitude adjustments on the subset of flights crossing ice-supersaturated regions where persistent contrails form. Reliable detection of contrails from ground sky-cameras (and satellites) is a building block for validating and closing the loop on such measures.
- EUROCONTROL — MUAC contrail avoidance measures: https://www.eurocontrol.int/article/research-operations-muac-pioneering-atm-condensation-trail-contrail-avoidance-measures
- GVCCS — Ground Visible Camera Contrail Sequences dataset (real sky-cam contrail segmentation/tracking): arXiv 2507.18330
This model is trained on synthetic data generated procedurally on the fly (blue→white gradient skies + smoothed-noise clouds + thin anti-aliased bright streaks with matching masks). The synthetic task is deliberately easy, so the U-Net reaches high validation IoU/Dice in ~1–2 minutes of CPU training. The point is a clean, runnable end-to-end system, not production accuracy.
These are not production metrics and the model has not seen real sky-camera imagery. A high synthetic IoU says the plumbing, training loop, loss, metrics, and inference path are correct — nothing about real-world contrail detection performance.
- Real data: train/fine-tune on GVCCS and real Sky Cam Vision / Sky InSight ground-camera imagery; weak-label bootstrap from satellite-detected contrail flags, then human-in-the-loop correction.
- Cross-sensor: correlate ground-camera detections with satellite contrail products and flight trajectories (ADS-B) for spatio-temporal validation.
- Better task framing: move from semantic to instance segmentation + tracking (per-contrail across a camera sequence), which is what GVCCS targets.
- Stronger models: larger encoders (e.g. pretrained backbones), test-time augmentation, calibrated thresholds per sky condition; quantify uncertainty.
- MLOps: experiment tracking + model registry with MLflow / Databricks, reproducible data versioning, scheduled retraining, drift monitoring.
- Serving: containerised inference deployed on OpenShift (EUROCONTROL's platform), autoscaled, with the BFF/API gateway pattern shown here unchanged.
docker compose up --build
# open http://localhost:8080Frontend (nginx :8080) proxies /api to the BFF (:3000), which forwards to the
FastAPI ML service (:8000). Compose waits on healthchecks between tiers.
make install # python venv + npm installs for all three services
make train # (optional) retrain the U-Net; a checkpoint is committed
make test # pytest (ml) + node tests (bff) + frontend build
make dev # prints the 3 commands to run each service in its own terminalmake dev runs:
# terminal 1 — ML service
cd ml-service && .venv/bin/uvicorn app:app --port 8000
# terminal 2 — BFF
cd bff && MLSERVICE_URL=http://localhost:8000 npm start
# terminal 3 — frontend (open http://localhost:5173)
cd frontend && npm run devThe trained checkpoint ml-service/ml_service/checkpoints/unet.pt is committed,
so the app works without running make train first.
| Method | Path | Description |
|---|---|---|
| GET | /health |
liveness + whether the checkpoint loaded |
| GET | /samples |
list of sample sky image ids |
| GET | /samples/{id} |
a sample sky PNG |
| POST | /segment |
multipart image upload → JSON: base64 overlay PNG, coverage_pct, contrail_count |
The BFF mirrors these under /api/*.
Screenshots placeholder — run
docker compose up --buildand open http://localhost:8080, then pick a sample sky or upload one to see the original image, the contrail overlay, and the coverage/count stats.
ml-service/ FastAPI + PyTorch U-Net (model, synthetic data, train, inference, tests)
bff/ Node.js + Express BFF (forwards to the ML service)
frontend/ React + Vite + TS (gallery, upload, overlay UI; nginx Dockerfile)
docs/ ARCHITECTURE.md, DECISIONS.md
.github/ CI workflow