AgriTech plant disease & pest classification service. A full-stack reference app: fine-tune a YOLO11-cls model on the public PlantVillage dataset, export to ONNX, serve behind a FastAPI backend, and consume from a Vue 3 SPA. Built as a portfolio capstone aligned with the detection-style pipelines used in modern AgTech products (Solinftec Solix, Climate Fieldview, etc.).
🇧🇷 Versão em português · 🇺🇸 English version
- Trains a YOLO11n-cls model on the 54k-image PlantVillage dataset (38 classes, 14 crops).
- Exports the fine-tuned weights to ONNX for fast CPU inference.
- Serves a REST API (
/health,/model/info,POST /detect) that accepts a leaf image and returns the top-K predicted classes with confidences. - A small Vue 3 SPA lets you drag-and-drop a leaf and see the predictions in a clean list with confidence bars.
I am preparing for a Junior Web Developer role at an AgTech company (Solinftec's public stack on GitHub shows a YOLOv5 fork for in-field pest detection, so this project mirrors that choice at portfolio scale). I wanted a small but end-to-end example that demonstrates:
- I can train and export a vision model, not just call an API.
- I can build a clean REST API with FastAPI + Pydantic.
- I can ship a usable Vue 3 frontend.
- I think about deployment (ONNX, multi-stage Docker, CI).
# From the repo root, in bash (WSL / git-bash / MSYS)
cd backend
./setup.sh # creates .venv with Python 3.14 and installs deps
./test.sh # pytest (4 tests, no model needed)
./lint.sh # ruff
./run.sh # uvicorn on http://localhost:8000From the frontend/ directory:
npm install
npm run dev # http://localhost:5173 (proxies /api to :8000)docker compose up --build
# Frontend: http://localhost:5173
# Backend: http://localhost:8000 (Swagger at /docs)Note: the model weights are not in the image. Either run
python -m scripts.train once on the host (writes into
backend/artifacts/, which is mounted as a volume) or drop a
pre-trained model.onnx there.
.
├── backend/ # FastAPI service
│ ├── app/
│ │ ├── api/ # routes + Pydantic schemas
│ │ ├── core/ # configuration
│ │ └── ml/ # ONNX + PyTorch backends (Strategy pattern)
│ ├── scripts/
│ │ ├── train.py # fine-tune YOLO11-cls on PlantVillage
│ │ └── export_onnx.py # re-export .pt -> .onnx
│ ├── tests/
│ ├── setup.{bat,sh} test.{bat,sh} lint.{bat,sh} run.{bat,sh}
│ └── Dockerfile
├── frontend/ # Vue 3 SPA
│ ├── src/
│ │ ├── components/ # Predictor, PredictionList, ModelStatus
│ │ ├── api/ # axios client
│ │ ├── types/ # TypeScript shapes
│ │ └── utils/ # class-name formatting
│ ├── Dockerfile + nginx.conf
│ └── package.json
├── docker-compose.yml # backend + frontend
├── .github/workflows/ # backend-ci + frontend-ci
└── README.md
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness + model status |
GET |
/model/info |
Model metadata (class names, imgsz, backend) |
POST |
/detect |
Multipart upload of an image; returns top-K predictions |
Full schema: http://localhost:8000/docs (auto-generated by FastAPI).
cd backend
./setup.sh
./run.sh false # don't start uvicorn; just install
.venv/Scripts/python.exe -m scripts.train --epochs 5 --imgsz 224 --batch 32 --device cpu
# or with GPU: --device 0
# Artifacts land in backend/artifacts/best.pt + backend/artifacts/model.onnxA 5-epoch CPU run on PlantVillage takes ~1-2 hours; a GPU run is
~10-15 minutes. The included pytest smoke tests do not require a
trained model — they verify the app boots and returns proper 4xx/5xx.
- How to fine-tune a YOLO11-cls model on a custom dataset using the
Ultralytics Python API and the
hf:dataset prefix for Hugging Face Hub datasets. - How to export a model to ONNX and serve it without PyTorch at runtime (much smaller image, faster cold start).
- How to design a backend-agnostic inference layer with a Strategy pattern (ONNX and PyTorch backends implement the same interface).
- How to write smoke tests that don't need the heavy model weights
(TestClient +
app.stateinjection).
- Add bounding-box detection by switching to YOLO11n (detection) on a dataset like PlantDoc or CottonWeedDet12.
- Add Grad-CAM saliency maps so the user can see why the model predicted what it predicted.
- Add a "upload to retrain" endpoint so field photos can improve the model over time.
- Move inference to a GPU pod behind a queue (Celery / RQ).
MIT — see LICENSE.
- Treina um modelo YOLO11n-cls no PlantVillage (54k imagens, 38 classes, 14 culturas).
- Exporta os pesos pra ONNX, inferência rápida em CPU.
- Expõe uma API REST (
/health,/model/info,POST /detect) que recebe uma imagem de folha e retorna top-K classes com confiança. - Uma SPA Vue 3 permite arrastar-e-soltar a folha e ver as predições em lista limpa com barras de confiança.
Estou me preparando pra Analista Desenvolvimento Web Jr numa AgTech (o stack público da Solinftec no GitHub mostra um fork do YOLOv5 pra detecção de pragas em campo, então este projeto espelha essa escolha em escala de portfólio). Eu queria um exemplo pequeno mas end-to-end que demonstrasse:
- Sei treinar e exportar um modelo de visão, não só chamar API.
- Sei construir uma API REST limpa com FastAPI + Pydantic.
- Sei entregar um frontend Vue 3 usável.
- Penso em deploy (ONNX, multi-stage Docker, CI).
Opção A — local com os scripts do venv (sem Docker):
# Da raiz do repo, em bash (WSL / git-bash / MSYS)
cd backend
./setup.sh # cria .venv com Python 3.14 e instala deps
./test.sh # pytest (4 testes, sem precisar de modelo)
./lint.sh # ruff
./run.sh # uvicorn em http://localhost:8000Em frontend/:
npm install
npm run dev # http://localhost:5173 (proxia /api para :8000)Opção B — Docker Compose (tudo em containers):
docker compose up --build
# Frontend: http://localhost:5173
# Backend: http://localhost:8000 (Swagger em /docs)Os pesos do modelo não vão na imagem. Rode
python -m scripts.train uma vez no host (gera em
backend/artifacts/, montado como volume) ou coloque um model.onnx
já treinado lá.
| Método | Path | Descrição |
|---|---|---|
GET |
/health |
Liveness + status do modelo |
GET |
/model/info |
Metadados do modelo (classes, imgsz, backend) |
POST |
/detect |
Upload multipart de imagem; retorna top-K predições |
Schema completo: http://localhost:8000/docs.
cd backend
./setup.sh
.venv/Scripts/python.exe -m scripts.train --epochs 5 --imgsz 224 --batch 32 --device cpu
# ou com GPU: --device 0
# Artefatos em backend/artifacts/best.pt + backend/artifacts/model.onnxCPU em 5 épocas no PlantVillage leva 1-2h; GPU leva 10-15 min. Os testes de smoke inclusos não precisam de modelo treinado.
- Como fazer fine-tuning de YOLO11-cls num dataset customizado usando
a API Python do Ultralytics e o prefixo
hf:pra datasets do Hugging Face. - Como exportar pra ONNX e servir sem PyTorch em runtime (imagem muito menor, cold start mais rápido).
- Como desenhar uma camada de inferência backend-agnóstica com Strategy pattern (ONNX e PyTorch implementam a mesma interface).
- Como escrever smoke tests que não precisam dos pesos do modelo
(TestClient +
app.state).
- Trocar pra detecção com bounding boxes usando YOLO11n num dataset tipo PlantDoc ou CottonWeedDet12.
- Adicionar Grad-CAM pra explicar visualmente a predição.
- Adicionar endpoint "upload pra retreinar" pra fotos de campo melhorarem o modelo ao longo do tempo.
- Mover inferência pra um pod com GPU atrás de fila (Celery / RQ).
MIT — veja LICENSE.
Issues e PRs são bem-vindos! Por favor leia CONTRIBUTING.md.
Davi Roque — daviroque.luiz03@gmail.com · LinkedIn · GitHub
Built as a portfolio capstone while preparing for a Junior Web Developer role at an AgTech company (Solinftec — public job posting 4425371310).