mulatyazew/cassava-crop-disease-detection
AgroVision Africa
Cassava Leaf Disease Classification — from noisy field photos to explainable predictions

Cassava feeds over 800 million people in sub-Saharan Africa, yet most smallholder farmers have no fast way to identify which disease is killing their crop. This project builds a full end-to-end image classification pipeline — data cleaning, class-imbalance handling, backbone comparison, and a Streamlit demo with Grad-CAM explainability — trained on the Kaggle Cassava Leaf Disease dataset.
Results at a glance
EfficientNet-V2-S is the best checkpoint and is what the demo loads by default.
What's inside
.
├── codes/
│ ├── config.py # all hyperparameters and paths in one place
│ ├── utils.py # seeding, device detection
│ ├── outlier_handler.py # 3-stage data cleaning pipeline
│ ├── data_handler.py # Dataset, augmentations, weighted sampler
│ ├── model.py # CassavaCNN / EfficientNet-V2-S / Swin-Tiny factory
│ ├── train.py # training loop, early stopping, checkpointing
│ └── evaluate.py # metrics, confusion matrix, classification report
├── notebooks/
│ └── AgroVision_Africa.ipynb # full pipeline — run this top to bottom
├── demo/
│ └── app.py # Streamlit app
├── cassava-leaf-dataset/
│ ├── train.csv
│ ├── train_images/
│ ├── test_images/ # held-out test split (generated by the notebook)
│ └── label_num_to_disease_map.json
├── models/ # saved checkpoints (.pth)
├── results/ # metrics JSON, plots, outlier review CSVs
└── requirements.txtDisease classes
CMD is by far the most common class (~62% of samples), which drives a lot of the class-imbalance design decisions below.
Pipeline overview
1. Data cleaning (3-stage outlier removal)
Raw field photos are messy. Before any training, outlier_handler.py runs three passes:
- File integrity — drop corrupt or unreadable images.
- Green-content check — flag images with too little green in the HSV channel (not a leaf at all).
- Embedding outliers — extract ResNet-50 features, then use IsolationForest per class to catch samples that don't belong with their label.
Flagged images are written to review CSVs in results/ rather than silently deleted, so you can inspect them.
2. Stratified split
80 / 10 / 10 train/val/test split, stratified by label. The test set (~2,128 images) is held out entirely and only touched during final evaluation.
3. Class-imbalance handling
Two complementary approaches run together:
- WeightedRandomSampler — oversamples minority classes during training so each batch sees a more balanced label mix.
- Focal Loss (γ = 2) — down-weights easy, well-classified examples and focuses gradient updates on hard cases, which tend to be the minority classes.
4. Augmentation
- Standard pipeline for all classes: random crops, flips, color jitter, normalization.
- MixUp (α = 0.4) and CutMix (α = 1.0) — one chosen at random per batch to improve generalization and reduce overconfidence.
5. Training
- Optimizer: AdamW with weight decay 1e-5.
- Schedule: cosine annealing LR.
- Early stopping: patience = 10 epochs on validation macro-F1.
- Best checkpoint saved to
models/<arch>/.
6. Evaluation
Test-Time Augmentation (TTA, 5 views) is applied at inference. Final metrics include accuracy, precision, recall, macro F1, per-class F1, and both raw and normalized confusion matrices.
Setup
Two separate dependency files, for two separate purposes:
- `requirements.txt` — only what
demo/app.pyneeds to run. This is what Streamlit Community Cloud and Hugging Face Spaces install. - `requirements-train.txt` — the full local training/notebook environment (adds albumentations extras, scikit-learn, tensorboard, cleanlab, jupyter, etc.).
To run the demo app locally:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
streamlit run demo/app.pyTo run the training pipeline / notebook instead:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-train.txtDownload the Kaggle Cassava Leaf Disease dataset and place it under cassava-leaf-dataset/ so the folder contains train.csv, train_images/, and label_num_to_disease_map.json.
Running the pipeline
Open notebooks/AgroVision_Africa.ipynb and run the cells from top to bottom. Each section is labeled and can be re-run independently after the first full pass.
Key config knobs are all in codes/config.py:
MODEL_ARCHITECTURE = "efficientnet_v2_s" # or "swin_tiny" / "cassava_cnn"
NUM_EPOCHS = 30
BATCH_SIZE = 64
SEED = 42Device selection is automatic: MPS (Apple Silicon) → CUDA → CPU.
Demo app
[Try the live demo →](https://cassava-multitask-visiongit-cplqe9vabribzpdiuha4nt.streamlit.app/)
The bare link self-redirects into Community Cloud's ?embed=true view for every visitor, including the owner — no Streamlit or Community Cloud branding (Share/star/fork, GitHub icon, footer, top color bar, native toolbar) is ever shown. There is no admin bypass; app management (secrets, reboot, logs) happens through the Community Cloud dashboard, not the in-app chrome.
Or run locally:
streamlit run demo/app.pyUpload any cassava leaf photo. The app returns the predicted disease class, per-class confidence scores, and a Grad-CAM heatmap showing which regions drove the prediction. You can swap between all three trained backbones from the sidebar.
Deploying to Hugging Face Spaces
This repo is also set up to run as a Hugging Face Space, independent of the Community Cloud deployment above — the YAML block at the very top of this README (sdk: streamlit, app_file: demo/app.py) is HF Spaces' own config format and is what makes that possible.
- Create a new Space at huggingface.co/new-space: pick the Streamlit SDK, any visibility you want.
- Add it as a second git remote in your local clone and push:
git remote add space https://huggingface.co/spaces/<your-username>/<space-name>
git push space main- Push the LFS-tracked model checkpoints too — a new remote does not automatically copy LFS objects from GitHub:
git lfs push space main- The Space rebuilds automatically on push. It installs
requirements.txt(notrequirements-train.txt) and launchesdemo/app.pyper the README frontmatter — no other config needed. - Nothing in
demo/app.pyrequires API keys or secrets, so there's nothing to add under the Space's Settings → Variables and secrets.
The embed/toolbar-hiding logic in demo/app.py (see the top of the file) is harmless on HF Spaces too: st.context.is_embedded will simply be False there, so it does one redirect to ?embed=true on first load and hides Streamlit's native toolbar — there's just no Community-Cloud-specific chrome underneath to hide, since that only exists on *.streamlit.app.
Reproducibility
- Global seed
42is set viautils.set_seedbefore any data split, sampling, or model initialization. - Every hyperparameter lives in
codes/config.py— no magic numbers scattered through notebooks. - The held-out test set is never touched during training or model selection.
