Loading…
Loading…
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) |
| 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).
| # | 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 |
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.)
# 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.
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.
config.json — confidence_threshold, temperature, preprocessing, class list (original + display names).JAMEE_CONFIDENCE_THRESHOLD overrides the threshold without editing files.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.
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.
| 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 |