Automatic detection of agricultural pests in images using Faster R-CNN ResNet50-FPN and an MLOps stack: uv, DVC, MLflow, Triton Inference Server, MinIO, PostgreSQL, docker-compose.
- Python 3.12
- uv
- Docker + Docker Compose
- For GPU inference/training on Linux/WSL2: NVIDIA Driver + NVIDIA Container Toolkit
On macOS (MPS), training is possible, but some
torchvisiondetection ops can be unstable. For stability, use CPU or a Linux GPU.
# create venv and install dependencies
uv sync
# (optional) enable pre-commit
uv run pre-commit install
uv run pre-commit run -aThe dataset is stored outside git and managed with DVC.
# pull data from the DVC remote
uv run dvc pullAfter dvc pull, data folders will appear in the project (e.g., raw_data/...).
If you keep a manual copy of the dataset in
raw_data/, make sure DVC is configured correctly and the path matchesconfigs/paths/default.yaml.
docker compose reads environment variables from the .env file in the repository root.
- The repository contains an example:
.env.example - For local runs, create your own
.env:
cp .env.example .envRunning without a profile starts only the MLOps stack (Postgres + MinIO + MLflow) — without Triton.
docker compose up -dChecks:
- MLflow UI: http://localhost:8080
- MinIO Console: http://localhost:9001
If you don’t want to start everything, you can start specific services only (e.g., the MLflow stack without Triton).
Training is launched via the cpd CLI (Fire + Hydra compose API). Configs live in configs/.
uv run cpd train \
train.trainer.max_epochs=1 \
train.trainer.limit_train_batches=0.02 \
train.trainer.limit_val_batches=0.02uv run cpd train \
train.trainer.accelerator=gpu train.trainer.devices=1 train.trainer.precision=16-mixed \
train.trainer.max_epochs=25 \
data.batch_size=4 data.num_workers=8 data.pin_memory=true \
train.trainer.log_every_n_steps=50 \
train.trainer.limit_train_batches=1.0 train.trainer.limit_val_batches=1.0Outputs:
- checkpoints:
checkpoints/(git-ignored) - exports:
exports/(git-ignored) - logs in MLflow (metrics/params/artifacts)
Export can be done automatically after training (see train.export.*) or manually via a dedicated pipeline.
uv run cpd export_onnx \
infer.source=local \
infer.ckpt_path=checkpoints/last.ckpt \
infer.onnx_path=exports/onnx/model.onnx \
infer.export.opset=17 \
infer.export.input_h=640 infer.export.input_w=640 \
infer.export.score_thr=0.05Supported modes:
infer.source=mlflow_ckpt— download a checkpoint from MLflow and exportinfer.source=mlflow_onnx— download an already exported ONNX from MLflow
Example:
uv run cpd export_onnx \
infer.source=mlflow_onnx \
infer.mlflow.tracking_uri=http://localhost:8080 \
infer.mlflow.run_id=<RUN_ID> \
infer.mlflow.onnx_artifact_path=onnx/model.onnx \
infer.onnx_path=exports/onnx/model.onnxuv run cpd triton_build_repo \
infer.onnx_path=exports/onnx/model.onnx \
infer.triton.model_repository=model_repository \
infer.triton.model_name=crop_pest_detector \
infer.triton.model_version=1 \
infer.export.input_h=640 infer.export.input_w=640 \
infer.export.max_dets=100By default, config.pbtxt is generated for GPU (instance_kind=KIND_GPU). For CPU, override: infer.triton.instance_kind=KIND_CPU.
Triton is started via profiles:
- GPU: profile
gpu(servicetriton) - CPU: profile
cpu(servicetriton-cpu)
# GPU Triton
docker compose --profile gpu up -d triton
# or CPU Triton
docker compose --profile cpu up -d triton-cpu
# logs (GPU Triton)
docker compose logs -f tritonHealth checks:
uv run cpd infer \
infer.backend=triton_http \
infer.triton.url=localhost:8000 \
infer.triton.model_name=crop_pest_detector \
infer.input_path=raw_data/agro_pest/valid/images/<IMAGE>.jpg \
infer.output_path=outputs/infer/result.jsonuv run cpd infer \
infer.backend=onnxruntime \
infer.onnx_path=exports/onnx/model.onnx \
infer.input_path=raw_data/agro_pest/valid/images/<IMAGE>.jpg \
infer.output_path=outputs/infer/result.jsonClass names are read from raw_data/agro_pest/data.yaml.
uv run cpd visualize \
viz.input_json=outputs/infer/result.json \
viz.output_path=outputs/infer/result.png \
viz.score_thr=0.30 \
viz.yolo_data_yaml=raw_data/agro_pest/data.yamlExample inference output on a single image (score_thr=0.50):
crop_pest_detection/— package codeconfigs/— Hydra configs (train/infer/paths/model/data)scripts/— helper scripts (if any)model_repository/— Triton model repository (git keeps only the config; weights/onnx are ignored)raw_data/,checkpoints/,exports/,outputs/,downloads/— data/artifacts (git-ignored)
If you run the gpu profile and see an error like could not select device driver ... with capabilities: [[gpu]], Docker cannot see an NVIDIA GPU.
Check:
- NVIDIA Driver is installed
- NVIDIA Container Toolkit is installed
- Docker is running in an environment where the GPU is visible (on WSL2, Docker Desktop must be running)
If you see error mounting ... to rootfs at "/models": ... no such file or directory, it’s usually a Docker Desktop/WSL bind-mount path issue. Make sure the model_repository path exists inside WSL and is accessible to Docker.
# infrastructure
docker compose up -d # MLOps stack (no profile)
# training
uv run cpd train ...
# export
uv run cpd export_onnx ...
# triton repo + run triton
uv run cpd triton_build_repo ...
docker compose --profile gpu up -d triton # Triton GPU
docker compose --profile cpu up -d triton-cpu # Triton CPU
# inference + visualization
uv run cpd infer ...
uv run cpd visualize ...