Jamee Crop Disease Classifier
Image classifier that recognises 15 crop / disease classes (Pepper bell, Potato, Tomato) from a photo of a single leaf. It is the ML component of Jamee, an AI crop-disease detection system. It runs on CPU with ONNX Runtime; no GPU or PyTorch is needed.
| Model | MobileNetV3-Large (ImageNet-pretrained, two-stage fine-tuning) |
| Input | 224 × 224 RGB image (any size accepted: shorter side resized to 256, centre-cropped to 224) |
| Output | temperature-calibrated class probabilities, top-3, uncertain status below the confidence threshold |
| Number of classes | 15 (detected from the dataset folders) |
| Parameters | 4,221,247 |
| ONNX model size | 16.4 MB |
| CPU latency | 9 ms per image (median, incl. preprocessing, measured on the training machine) |
| Dataset | PlantVillage — Kaggle emmarex/plantdisease |
| Training | Google Colab (Tesla T4) |
| Deployment | Download from this repository and run on CPU with ONNX Runtime (see below) |
Metrics (held-out test set, evaluated once)
| Metric | Value |
|---|---|
| Accuracy | 99.52% |
| Macro precision | 99.54% |
| Macro recall | 99.51% |
| Macro F1 | 99.52% |
| Weighted F1 | 99.52% |
| Top-3 accuracy | 99.97% |
| Calibration error (ECE) before → after temperature scaling | 0.003 → 0.002 (T = 1.210) |
| Confidence threshold | 0.99 → 95.4% of test images answered, 99.93% accuracy on those |
20624 images after cleaning, split 14434 / 3095 / 3095 (train / validation / test; 70/15/15, stratified, duplicate-aware, made before augmentation). Best validation macro-F1: 99.67% (epoch 25).
Per-class test results
| # | Original class name | Display name | Precision | Recall | F1 | Test images |
|---|---|---|---|---|---|---|
| 0 | Pepper__bell___Bacterial_spot | Pepper bell — Bacterial spot | 1.000 | 1.000 | 1.000 | 150 |
| 1 | Pepper__bell___healthy | Pepper bell — Healthy | 0.996 | 1.000 | 0.998 | 222 |
| 2 | Potato___Early_blight | Potato — Early blight | 1.000 | 1.000 | 1.000 | 150 |
| 3 | Potato___Late_blight | Potato — Late blight | 1.000 | 1.000 | 1.000 | 150 |
| 4 | Potato___healthy | Potato — Healthy | 1.000 | 1.000 | 1.000 | 23 |
| 5 | Tomato_Bacterial_spot | Tomato — Bacterial spot | 0.997 | 1.000 | 0.998 | 319 |
| 6 | Tomato_Early_blight | Tomato — Early blight | 0.987 | 0.993 | 0.990 | 150 |
| 7 | Tomato_Late_blight | Tomato — Late blight | 1.000 | 0.989 | 0.995 | 285 |
| 8 | Tomato_Leaf_Mold | Tomato — Leaf Mold | 1.000 | 0.993 | 0.996 | 143 |
| 9 | Tomato_Septoria_leaf_spot | Tomato — Septoria leaf spot | 0.993 | 1.000 | 0.996 | 266 |
| 10 | Tomato_Spider_mites_Two_spotted_spider_mite | Tomato — Spider mites Two spotted spider mite | 0.996 | 0.972 | 0.984 | 251 |
| 11 | Tomato__Target_Spot | Tomato — Target Spot | 0.968 | 1.000 | 0.984 | 211 |
| 12 | Tomato__Tomato_YellowLeaf__Curl_Virus | Tomato — Tomato YellowLeaf Curl Virus | 1.000 | 0.996 | 0.998 | 481 |
| 13 | Tomato__Tomato_mosaic_virus | Tomato — Tomato mosaic virus | 1.000 | 0.982 | 0.991 | 56 |
| 14 | Tomato_healthy | Tomato — Healthy | 0.996 | 1.000 | 0.998 | 238 |
Use it from Python
Install the runtime (tested on Python 3.12; onnxruntime 1.30 does not install on Python 3.10):
pip install huggingface_hub onnxruntime==1.30.0 numpy==2.4.4 pillow==12.3.0
import sys
from huggingface_hub import snapshot_download
MODEL_DIR = snapshot_download("sarang-sketch/sarox-plangton", local_dir="jamee_model") # ~17 MB; unchanged files are not downloaded again
sys.path.insert(0, MODEL_DIR)
from inference import load_model, predict_image
model = load_model(MODEL_DIR) # load once, reuse for every image
result = predict_image("leaf.jpg", model=model) # path, bytes or PIL image
print(result["status"], result.get("predicted_class"), result["confidence"])
For production, pin an exact model version with snapshot_download(..., revision="<commit id>"). If this repository is made private, set the HF_TOKEN environment variable on the machine that downloads it.
Successful prediction:
{"status": "success", "predicted_class": "Tomato_Early_blight", "display_name": "Tomato — Early blight",
"crop": "Tomato", "condition": "Early blight", "is_healthy": false, "confidence": 0.995, "threshold": 0.99,
"top_predictions": [{"class": "Tomato_Early_blight", "display_name": "Tomato — Early blight", "confidence": 0.995},
{"class": "Tomato_Late_blight", "display_name": "Tomato — Late blight", "confidence": 0.003},
{"class": "Tomato_Septoria_leaf_spot", "display_name": "Tomato — Septoria leaf spot", "confidence": 0.001}]}
Low confidence (top_predictions is still included for logging — do not present it as a diagnosis):
{"status": "uncertain", "predicted_class": null, "confidence": 0.42, "threshold": 0.99,
"message": "Jamee could not confidently identify this image.", "top_predictions": ["..."]}
(The two JSON examples show the response format; their values are illustrative.)
Optional: FastAPI wrapper for the Jamee backend
# Run the download step above first, so MODEL_DIR is on sys.path.
from fastapi import FastAPI, File, HTTPException, UploadFile
from inference import InvalidImageError, load_model, predict_image
app = FastAPI()
model = load_model(MODEL_DIR)
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
try:
return predict_image(await file.read(), model=model)
except InvalidImageError as exc:
raise HTTPException(status_code=400, detail=str(exc))
pip install fastapi uvicorn python-multipart, then uvicorn api:app --host 127.0.0.1 --port 8000. Add authentication and an upload-size limit before exposing it to the internet.
Try the demo locally
In the downloaded folder:
pip install gradio==6.29.1 -r requirements.txt
python app.py
The Gradio page is served at http://127.0.0.1:7860, reachable only from your own computer.
Configuration
config.json—confidence_threshold,temperature, preprocessing, class list (original + display names).- Environment variable
JAMEE_CONFIDENCE_THRESHOLDoverrides the threshold without editing files.
Limitations
- The model only recognises the diseases represented in its training dataset (15 classes: Pepper bell, Potato, Tomato). Any other crop or disease is forced into one of these classes or reported as uncertain.
- A high confidence score does not guarantee real-world correctness.
- Images taken in uncontrolled field conditions (cluttered background, several leaves, blur, unusual light) may produce lower performance: PlantVillage photos are lab images of single leaves on plain backgrounds.
- The confidence threshold is a basic rejection mechanism, not out-of-distribution detection. It and the temperature were fitted on PlantVillage validation images, so they may be optimistic for field photos.
Responsible use
The model returns a class label and probabilities only. It does not recommend pesticides, doses or treatments. Agricultural guidance must come from Jamee's separate knowledge layer and should be reviewed by experts.
Security
inference.py rejects files it cannot decode (InvalidImageError) and images larger than 40 megapixels. Any service that exposes the model on the internet still needs its own authentication, upload-size limit and rate limiting.
Files
| File | Purpose |
|---|---|
model.onnx | float32 network, input input [batch, 3, 224, 224] → output logits |
config.json | preprocessing, classes, threshold, temperature, training details |
class_names.json | index → original class name |
inference.py | load_model → load_image → preprocess_image → predict → format_prediction |
app.py | optional Gradio demo with a /predict API |
requirements.txt | pinned runtime dependencies for inference.py |