Files changed (1) hide show
  1. app.py +42 -24
app.py CHANGED
@@ -3,9 +3,9 @@ from fastapi.middleware.cors import CORSMiddleware
3
  from ultralytics import YOLO
4
  from PIL import Image
5
  import io
6
-
7
  app = FastAPI(title="AgriVision API")
8
-
9
  app.add_middleware(
10
  CORSMiddleware,
11
  allow_origins=["*"],
@@ -132,7 +132,7 @@ TREATMENTS = {
132
  "treatment": "Apply pyrethroid insecticides. Store harvested grain in airtight containers. Use hermetic storage bags and apply diatomaceous earth as a physical control."
133
  },
134
  }
135
-
136
  # ── Severity function ──────────────────────────────────────────
137
  def get_severity(confidence: float, crop_type: str) -> str:
138
  if crop_type == "Healthy":
@@ -143,56 +143,74 @@ def get_severity(confidence: float, crop_type: str) -> str:
143
  return "Medium"
144
  else:
145
  return "Low"
146
-
147
  # ── Name formatter ─────────────────────────────────────────────
148
  def format_class_name(name: str) -> str:
149
  return name.replace("_", " ").title()
150
-
151
  # ── Load model once at startup ─────────────────────────────────
152
  model = YOLO("best.pt")
153
-
154
  # ── Routes ─────────────────────────────────────────────────────
155
  @app.get("/")
156
  def home():
157
  return {"status": "AgriVision API is running"}
158
-
159
  @app.post("/predict")
160
  async def predict(file: UploadFile = File(...)):
161
- # Read and process image
162
  contents = await file.read()
163
  image = Image.open(io.BytesIO(contents)).convert("RGB")
164
-
165
- # Run inference
166
  results = model.predict(image, imgsz=224, verbose=False)
167
  probs = results[0].probs
168
-
169
- # Top prediction
170
  top1_index = probs.top1
171
  top1_class = model.names[top1_index]
172
  top1_confidence = round(float(probs.top1conf), 4)
173
-
174
- # ── Invalid image detection ─────────────────────────────────
175
- top3_probs = [float(probs.data[i]) for i in probs.top5[:3]]
176
- top3_spread = max(top3_probs) - min(top3_probs)
177
-
178
- if top1_confidence < 0.45 and top3_spread < 0.45:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  raise HTTPException(
180
  status_code=422,
181
  detail={
182
  "error": "Invalid image",
183
- "message": "The uploaded image does not appear to be a crop or pest image. Please upload a clear photo of a crop leaf or pest.",
184
  "confidence": f"{round(top1_confidence * 100, 2)}%"
185
  }
186
  )
187
-
188
- # Look up treatment and type
189
  info = TREATMENTS.get(top1_class, {
190
  "type": "Unknown",
191
  "treatment": "No treatment data available for this class."
192
  })
193
  severity = get_severity(top1_confidence, info["type"])
194
-
195
- # Top 3 predictions
196
  top3 = [
197
  {
198
  "class": format_class_name(model.names[i]),
@@ -200,7 +218,7 @@ async def predict(file: UploadFile = File(...)):
200
  }
201
  for i in probs.top5[:3]
202
  ]
203
-
204
  return {
205
  "prediction": format_class_name(top1_class),
206
  "type": info["type"],
 
3
  from ultralytics import YOLO
4
  from PIL import Image
5
  import io
6
+
7
  app = FastAPI(title="AgriVision API")
8
+
9
  app.add_middleware(
10
  CORSMiddleware,
11
  allow_origins=["*"],
 
132
  "treatment": "Apply pyrethroid insecticides. Store harvested grain in airtight containers. Use hermetic storage bags and apply diatomaceous earth as a physical control."
133
  },
134
  }
135
+
136
  # ── Severity function ──────────────────────────────────────────
137
  def get_severity(confidence: float, crop_type: str) -> str:
138
  if crop_type == "Healthy":
 
143
  return "Medium"
144
  else:
145
  return "Low"
146
+
147
  # ── Name formatter ─────────────────────────────────────────────
148
  def format_class_name(name: str) -> str:
149
  return name.replace("_", " ").title()
150
+
151
  # ── Load model once at startup ─────────────────────────────────
152
  model = YOLO("best.pt")
153
+
154
  # ── Routes ─────────────────────────────────────────────────────
155
  @app.get("/")
156
  def home():
157
  return {"status": "AgriVision API is running"}
158
+
159
  @app.post("/predict")
160
  async def predict(file: UploadFile = File(...)):
 
161
  contents = await file.read()
162
  image = Image.open(io.BytesIO(contents)).convert("RGB")
163
+
 
164
  results = model.predict(image, imgsz=224, verbose=False)
165
  probs = results[0].probs
166
+
 
167
  top1_index = probs.top1
168
  top1_class = model.names[top1_index]
169
  top1_confidence = round(float(probs.top1conf), 4)
170
+
171
+ # ── Invalid image detection ────────────────────────────────
172
+ # Get all top 5 probabilities
173
+ top5_probs = [float(probs.data[i]) for i in probs.top5]
174
+
175
+ # How much the top prediction dominates over the rest
176
+ top1_prob = top5_probs[0]
177
+ top2_prob = top5_probs[1]
178
+ top3_prob = top5_probs[2]
179
+
180
+ # The gap between 1st and 2nd β€” a confident real crop image
181
+ # will have a clear winner. A random image spreads probability evenly.
182
+ top1_top2_gap = top1_prob - top2_prob
183
+
184
+ # Entropy-like spread across top 3
185
+ top3_spread = top1_prob - top3_prob
186
+
187
+ # Reject if EITHER of these is true:
188
+ # 1. Very low confidence (model has no idea)
189
+ # 2. Low confidence AND the top predictions are bunched together
190
+ # (model is guessing randomly across classes)
191
+ is_invalid = (
192
+ top1_confidence < 0.30 # extremely low confidence
193
+ or (top1_confidence < 0.55 and top1_top2_gap < 0.10) # low + no clear winner
194
+ or (top1_confidence < 0.50 and top3_spread < 0.15) # low + very spread out
195
+ )
196
+
197
+ if is_invalid:
198
  raise HTTPException(
199
  status_code=422,
200
  detail={
201
  "error": "Invalid image",
202
+ "message": "The uploaded image does not appear to be a crop or pest. Please upload a clear photo of a crop leaf or pest.",
203
  "confidence": f"{round(top1_confidence * 100, 2)}%"
204
  }
205
  )
206
+
207
+ # ── Valid scan ─────────────────────────────────────────────
208
  info = TREATMENTS.get(top1_class, {
209
  "type": "Unknown",
210
  "treatment": "No treatment data available for this class."
211
  })
212
  severity = get_severity(top1_confidence, info["type"])
213
+
 
214
  top3 = [
215
  {
216
  "class": format_class_name(model.names[i]),
 
218
  }
219
  for i in probs.top5[:3]
220
  ]
221
+
222
  return {
223
  "prediction": format_class_name(top1_class),
224
  "type": info["type"],