Train a land-cover classifier on 6 known EuroSAT classes from scratch, then build an OOD detection + unsupervised clustering system that discovers 4 previously unseen ("ghost") terrain classes — without ever using their labels during training or detection.
eurosat_classifier/
├── data/
│ └── dataset.py # Data pipeline, augmentation, normalization
├── models/
│ ├── simple_cnn.py # Baseline: Simple ConvNet
│ └── resnet_scratch.py # Strong: ResNet-18 (trained from scratch)
├── training/
│ ├── trainer.py # Training loop, early stopping, LR schedulers
│ └── losses.py # Cross-entropy, label smoothing, focal loss
├── evaluation/
│ └── evaluator.py # Metrics, confusion matrix, misclassified viz
├── utils/
│ ├── utils.py # Reproducibility, logging, optimizer helpers
│ └── weights_manager.py # Google Drive checkpoint downloader ← NEW
├── experiments/
│ └── run_experiments.py # HP sweeps, overfitting/underfitting demos
├── task2/
│ ├── deployment_pool.py # Unlabeled pool construction
│ ├── ood_detection.py # MSP, Energy, Mahalanobis, KNN detectors
│ ├── feature_extraction.py # Neural + spectral feature extraction
│ ├── clustering.py # UMAP + HDBSCAN ghost-class discovery
│ └── analysis_plots.py # Task 2 visualizations
├── main.py # Task 1 entry point
├── task2_main.py # Task 2 entry point
├── config.py # All hyperparameters + Google Drive IDs
├── 01_data_pipeline.ipynb # Notebook 1 - Dataset exploration
├── 02_models_and_training.ipynb # Notebook 2 — Model Architectures & Training
├── 03_evaluation.ipynb # Notebook 3 - Evaluation & Model Comparison
├── 04_task2_ood_discovery.ipynb # Notebook 4 — Task 2: OOD Detection & Ghost Class Discovery
├── Task1_report.md # Task 1 technical report
├── Task2_report.md # Task 2 technical report
└── requirements.txt # pip dependencies, pinned
Download EuroSAT RGB from one of:
Extract so the folder structure is ./EuroSAT_RGB/AnnualCrop/, ./EuroSAT_RGB/Forest/, etc.
| Split | Classes | Meaning |
|---|---|---|
| Known (6) | AnnualCrop, Forest, Highway, Industrial, Residential, SeaLake | Trained on |
| Ghost (4) | HerbaceousVegetation, Pasture, PermanentCrop, River | OOD — never seen in Task 1 |
pip install --upgrade pip
pip install -r requirements.txt
Pre-trained weights are hosted on Google Drive and downloaded automatically when running with --resume or when Task 2 cannot find the checkpoint.
| Model | Google Drive Link | Description |
|---|---|---|
resnet18.pt |
Download | ResNet-18 from scratch — best Task 1 model |
simple_cnn.pt |
Download | SimpleCNN baseline |
Replace
PLACEHOLDER_*_FILE_IDwith real Google Drive file IDs after uploading. Seeutils/weights_manager.py → WEIGHTS_REGISTRYfor configuration.
python utils/weights_manager.py download # all weights
python utils/weights_manager.py download --name resnet18 # specific
python utils/weights_manager.py list # check statuspython main.py # full pipeline: train + evaluate
python main.py --resume # skip training; load weights (auto-downloads from Drive)
python main.py --resume --skip_experiments # weights + evaluation onlypython task2_main.py # full OOD discovery pipeline
python task2_main.py --skip_clustering # OOD detection only (faster)Task 2 auto-downloads resnet18.pt from Google Drive if not found locally.
git clone <repo_url> && cd eurosat_classifier
pip install -r requirements.txt
wget https://madm.dfki.de/files/sentinel/EuroSAT.zip && unzip EuroSAT.zip -d EuroSAT_RGB
python main.py # Task 1 (~60 epochs)
python task2_main.py # Task 2Or, using pre-trained weights:
python main.py --resume # downloads weights from Google Drive automatically
python task2_main.pyAll seeds fixed via SEED = 42 in config.py. Results are fully reproducible.
| Model | Test Acc | Macro F1 | Params |
|---|---|---|---|
| SimpleCNN (baseline) | 92.27% | 0.9196 | ~0.3M |
| ResNet-18 (from scratch) | 97.45% | 0.9745 | ~11M |
| OOD Method | AUROC | FPR@95TPR |
|---|---|---|
| MSP | 0.9086 | 0.3870 |
| Energy Score | 0.8289 | 0.7960 |
| Mahalanobis | 0.6348 | 0.6690 |
| KNN (k=10) | 0.9285 | 0.3280 |
- No pretrained weights — every parameter learned from EuroSAT data only.
- Ghost class exclusion — filtered at dataset level; cannot leak into Task 1.
- Test set evaluated once — enforced via
EVALUATE_TEST_ONCE=Truein config. - Normalization from train set only — no validation/test statistics used.
- Intermediate layer features for OOD —
stage3features, not final logits. - HDBSCAN — no prior on number of ghost classes required.
- UMAP — preserves global + local structure; deterministic with fixed seed.