Green Doctor Deployer commited on
Commit
b97e578
·
1 Parent(s): 919d004

Improve false positive detection: add leaf density validation and stricter confidence thresholds

Browse files
Files changed (2) hide show
  1. backend/app.py +41 -26
  2. constants/diseases.js +5 -5
backend/app.py CHANGED
@@ -59,32 +59,42 @@ def focalize_leaf(image_bytes):
59
  import numpy as np
60
  import cv2
61
  """
62
- Expert Focalizer: Uses computer vision to find the leaf and crop it.
 
63
  """
64
  try:
65
  # 1. Convert to OpenCV format
66
  nparr = np.frombuffer(image_bytes, np.uint8)
67
  img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
68
- if img is None: return None
69
 
70
  # 2. Convert to HSV for better color segmentation
71
  hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
72
 
73
  # 3. Create mask for plant-like colors (Green, Yellow, Brown)
74
- # We target a wide range of leaf colors including diseased ones
75
  mask_green = cv2.inRange(hsv, (25, 40, 40), (90, 255, 255))
76
  mask_brown = cv2.inRange(hsv, (10, 50, 20), (30, 255, 200))
77
  mask = cv2.bitwise_or(mask_green, mask_brown)
78
 
79
- # 4. Find the largest contour (assuming it's the leaf)
 
 
 
 
 
 
 
 
 
 
80
  contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
81
  if not contours:
82
- return Image.open(io.BytesIO(image_bytes)).convert('RGB') # Fallback to original
83
 
84
  largest_cnt = max(contours, key=cv2.contourArea)
85
  x, y, w, h = cv2.boundingRect(largest_cnt)
86
 
87
- # 5. Add padding (20%)
88
  padding_w = int(w * 0.2)
89
  padding_h = int(h * 0.2)
90
 
@@ -95,12 +105,12 @@ def focalize_leaf(image_bytes):
95
 
96
  crop = img[y_pad:y_pad+h_pad, x_pad:x_pad+w_pad]
97
 
98
- # 6. Convert back to PIL for Transformers
99
  crop_rgb = cv2.cvtColor(crop, cv2.COLOR_BGR2RGB)
100
- return Image.fromarray(crop_rgb)
101
  except Exception as e:
102
  print(f"Focalizer Warning: {e}")
103
- return Image.open(io.BytesIO(image_bytes)).convert('RGB')
104
 
105
  # --- MAPPINGS ---
106
  # Comprehensive Expert Mapping (Matches wambugu71/crop_leaf_diseases_vit)
@@ -173,14 +183,21 @@ def debug_status():
173
  async def predict(file: UploadFile = File(...)):
174
  print(f"Leaf Vision Processing: {file.filename}")
175
  try:
176
- gen_expert, rice_expert, sugar_expert = get_experts()
177
  contents = await file.read()
178
 
179
- # 0. LEAF FOCALIZER: Pre-process the image for professional precision
180
- image = focalize_leaf(contents)
181
- if image is None:
182
- image = Image.open(io.BytesIO(contents)).convert('RGB')
183
 
 
 
 
 
 
 
 
 
 
184
  # 1. RUN ALL EXPERTS (with safety checks)
185
  results_gen = GENERAL_EXPERT(image) if GENERAL_EXPERT else []
186
  results_rice = RICE_EXPERT(image) if RICE_EXPERT else []
@@ -194,14 +211,13 @@ async def predict(file: UploadFile = File(...)):
194
  ai_label = results_specialist[0]['label']
195
  ai_score = results_specialist[0]['score']
196
  mapped = PLANT_VILLAGE_MAP.get(ai_label)
197
- if mapped and ai_score > 0.40:
198
  best_prediction = {"class": mapped, "score": ai_score, "expert": "Specialist", "label": ai_label}
199
 
200
  # Pass 2: Rice/Wheat Specialist (Priority for Cereals)
201
  if results_rice:
202
  ai_label = results_rice[0]['label']
203
  ai_score = results_rice[0]['score']
204
- # Re-keying to match labels for wambugu71/crop_leaf_diseases_vit
205
  RICE_LEGACY_MAP = {
206
  'Rice___Brown_Spot': 'Paddy - Brown Spot', 'Rice___Healthy': 'Paddy - Healthy', 'Rice___Leaf_Blast': 'Paddy - Blast',
207
  'Corn___Common_Rust': 'Corn - Common Rust', 'Corn___Gray_Leaf_Spot': 'Corn - Gray Leaf Spot', 'Corn___Healthy': 'Corn - Healthy',
@@ -209,24 +225,23 @@ async def predict(file: UploadFile = File(...)):
209
  }
210
  mapped = RICE_LEGACY_MAP.get(ai_label)
211
  # Prioritize specialists if they have high confidence
212
- if mapped and (ai_score > 0.50 or (mapped.startswith("Paddy") and ai_score > 0.35)):
213
  if ai_score > best_prediction['score']:
214
  best_prediction = {"class": mapped, "score": ai_score, "expert": "Cereal Expert", "label": ai_label}
215
 
216
- # Pass 3: General Expert (Fallback for visual recognition)
217
- if results_gen and best_prediction['score'] < 0.30:
218
  ai_label = results_gen[0]['label']
219
  ai_score = results_gen[0]['score']
220
  mapped = GENERAL_MAP.get(ai_label)
221
- if mapped:
222
  if ai_score > best_prediction['score']:
223
  best_prediction = {"class": mapped, "score": ai_score, "expert": "General", "label": ai_label}
224
- else:
225
- if ai_score > best_prediction['score'] and ai_score > 0.60:
226
- best_prediction = {"class": f"Detected: {ai_label}", "score": ai_score, "expert": "General", "label": ai_label}
227
 
228
- # PLANTIX LOGIC: Final check
229
- CONFIDENCE_THRESHOLD = 0.35
230
 
231
  if best_prediction['score'] < CONFIDENCE_THRESHOLD:
232
  final_class = "UNKNOWN"
@@ -238,7 +253,7 @@ async def predict(file: UploadFile = File(...)):
238
  return {
239
  "class": final_class,
240
  "confidence": float(best_prediction['score']),
241
- "ai_details": f"{best_prediction['expert']}: {best_prediction.get('label', 'None')}",
242
  "status": "success"
243
  }
244
  except Exception as e:
 
59
  import numpy as np
60
  import cv2
61
  """
62
+ Expert Focalizer: Uses computer vision to find the leaf, crop it, and calculate density.
63
+ Returns: (PIL Image, density_percentage)
64
  """
65
  try:
66
  # 1. Convert to OpenCV format
67
  nparr = np.frombuffer(image_bytes, np.uint8)
68
  img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
69
+ if img is None: return None, 0.0
70
 
71
  # 2. Convert to HSV for better color segmentation
72
  hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
73
 
74
  # 3. Create mask for plant-like colors (Green, Yellow, Brown)
 
75
  mask_green = cv2.inRange(hsv, (25, 40, 40), (90, 255, 255))
76
  mask_brown = cv2.inRange(hsv, (10, 50, 20), (30, 255, 200))
77
  mask = cv2.bitwise_or(mask_green, mask_brown)
78
 
79
+ # 4. Calculate Leaf Density
80
+ total_pixels = img.shape[0] * img.shape[1]
81
+ plant_pixels = cv2.countNonZero(mask)
82
+ density = (plant_pixels / total_pixels) * 100
83
+
84
+ # Rejection Threshold: If less than 2.5% of the image is plant-like, it's likely not a leaf
85
+ if density < 2.5:
86
+ print(f"Leaf Validation FAILED: Density {density:.2f}% < 2.5%")
87
+ return None, density
88
+
89
+ # 5. Find the largest contour (assuming it's the leaf)
90
  contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
91
  if not contours:
92
+ return Image.open(io.BytesIO(image_bytes)).convert('RGB'), density
93
 
94
  largest_cnt = max(contours, key=cv2.contourArea)
95
  x, y, w, h = cv2.boundingRect(largest_cnt)
96
 
97
+ # 6. Add padding (20%)
98
  padding_w = int(w * 0.2)
99
  padding_h = int(h * 0.2)
100
 
 
105
 
106
  crop = img[y_pad:y_pad+h_pad, x_pad:x_pad+w_pad]
107
 
108
+ # 7. Convert back to PIL for Transformers
109
  crop_rgb = cv2.cvtColor(crop, cv2.COLOR_BGR2RGB)
110
+ return Image.fromarray(crop_rgb), density
111
  except Exception as e:
112
  print(f"Focalizer Warning: {e}")
113
+ return Image.open(io.BytesIO(image_bytes)).convert('RGB'), 0.0
114
 
115
  # --- MAPPINGS ---
116
  # Comprehensive Expert Mapping (Matches wambugu71/crop_leaf_diseases_vit)
 
183
  async def predict(file: UploadFile = File(...)):
184
  print(f"Leaf Vision Processing: {file.filename}")
185
  try:
186
+ gen_expert, rice_expert, plant_specialist = get_experts()
187
  contents = await file.read()
188
 
189
+ # 0. LEAF FOCALIZER: Pre-process and VALIDATE if it's a leaf
190
+ image, density = focalize_leaf(contents)
 
 
191
 
192
+ if image is None:
193
+ print(f"REJECTED: Low Leaf Density ({density:.2f}%)")
194
+ return {
195
+ "class": "UNKNOWN",
196
+ "confidence": 0.0,
197
+ "ai_details": f"Quality Check: Low Leaf Density ({density:.1f}%)",
198
+ "status": "success"
199
+ }
200
+
201
  # 1. RUN ALL EXPERTS (with safety checks)
202
  results_gen = GENERAL_EXPERT(image) if GENERAL_EXPERT else []
203
  results_rice = RICE_EXPERT(image) if RICE_EXPERT else []
 
211
  ai_label = results_specialist[0]['label']
212
  ai_score = results_specialist[0]['score']
213
  mapped = PLANT_VILLAGE_MAP.get(ai_label)
214
+ if mapped and ai_score > 0.45: # Raised from 0.40
215
  best_prediction = {"class": mapped, "score": ai_score, "expert": "Specialist", "label": ai_label}
216
 
217
  # Pass 2: Rice/Wheat Specialist (Priority for Cereals)
218
  if results_rice:
219
  ai_label = results_rice[0]['label']
220
  ai_score = results_rice[0]['score']
 
221
  RICE_LEGACY_MAP = {
222
  'Rice___Brown_Spot': 'Paddy - Brown Spot', 'Rice___Healthy': 'Paddy - Healthy', 'Rice___Leaf_Blast': 'Paddy - Blast',
223
  'Corn___Common_Rust': 'Corn - Common Rust', 'Corn___Gray_Leaf_Spot': 'Corn - Gray Leaf Spot', 'Corn___Healthy': 'Corn - Healthy',
 
225
  }
226
  mapped = RICE_LEGACY_MAP.get(ai_label)
227
  # Prioritize specialists if they have high confidence
228
+ if mapped and (ai_score > 0.60 or (mapped.startswith("Paddy") and ai_score > 0.45)):
229
  if ai_score > best_prediction['score']:
230
  best_prediction = {"class": mapped, "score": ai_score, "expert": "Cereal Expert", "label": ai_label}
231
 
232
+ # Pass 3: General Expert (Strict Fallback)
233
+ if results_gen and best_prediction['score'] < 0.40:
234
  ai_label = results_gen[0]['label']
235
  ai_score = results_gen[0]['score']
236
  mapped = GENERAL_MAP.get(ai_label)
237
+ if mapped and ai_score > 0.60: # High threshold for general mapping
238
  if ai_score > best_prediction['score']:
239
  best_prediction = {"class": mapped, "score": ai_score, "expert": "General", "label": ai_label}
240
+ elif ai_score > 0.85: # Extremely high threshold for raw labels
241
+ best_prediction = {"class": f"Detected: {ai_label}", "score": ai_score, "expert": "General", "label": ai_label}
 
242
 
243
+ # PLANTIX LOGIC: Final check (Stricter threshold)
244
+ CONFIDENCE_THRESHOLD = 0.45 # Raised from 0.35
245
 
246
  if best_prediction['score'] < CONFIDENCE_THRESHOLD:
247
  final_class = "UNKNOWN"
 
253
  return {
254
  "class": final_class,
255
  "confidence": float(best_prediction['score']),
256
+ "ai_details": f"{best_prediction['expert']}: {best_prediction.get('label', 'None')} (Density: {density:.1f}%)",
257
  "status": "success"
258
  }
259
  except Exception as e:
constants/diseases.js CHANGED
@@ -6,13 +6,13 @@ export const DISEASES = [
6
  isHealthy: false,
7
  severity: 'N/A',
8
  symptoms: {
9
- en: "The AI cannot find a clear match. The image may be unclear, or the leaf condition is not recognized.",
10
- ta: "AI-ஆல் சரியான முடிவைக் கண்டறிய முடியவில்லை. படம் மங்கலாக இருக்கலாம் அல்லது இலை நிலை அடையாளம் காணப்படவில்லை."
11
  },
12
- cause: { en: "Image quality issues or rare plant variety.", ta: "படத்தின் தரம் குறைவு அல்லது அரிதான தாவர வகை." },
13
  remedy_organic: {
14
- en: "1. Clean the leaf surface. 2. Take a photo in bright, indirect sunlight. 3. Focus on a single leaf showing clear symptoms.",
15
- ta: "1. இலையின் மேற்பரப்பை சுத்தம் செய்யவும். 2. பிரகாசமான சூரிய ஒளியில் புகைப்படம் எடுக்கவும். 3. நோய் அறிகுறிகள் உள்ள ஒரு இலையில் மட்டும் கவனம் செலுத்தவும்."
16
  },
17
  remedy_chemical: {
18
  en: "Do not apply chemical pesticides until a clear diagnosis is made by an expert.",
 
6
  isHealthy: false,
7
  severity: 'N/A',
8
  symptoms: {
9
+ en: "No leaf detected or detection confidence is low. The image may be too blurry, random (non-plant), or the leaf is too far away.",
10
+ ta: "இலை அடையாளம் காணப்படவில்லை அல்லது துல்லியம் குறைவாக உள்ளது. படம் மங்கலாக இருக்கலாம் அல்லது செடியல்லாத மற்ற பொருளாக இருக்கலாம்."
11
  },
12
+ cause: { en: "Non-plant image or poor photo quality.", ta: "செடியல்லாத படம் அல்லது படத்தின் தரம் குறைவு." },
13
  remedy_organic: {
14
+ en: "1. Ensure the leaf is the main subject. 2. Clear background. 3. Good lighting. 4. Hold the camera closer (10-15cm).",
15
+ ta: "1. இலையை படத்தின் மையத்தில் வைக்கவும். 2. பின்புலத்தை சுத்தமாக வைக்கவும். 3. நல்ல வெளிச்சத்தில் படம் எடுக்கவும். 4. கேமராவை சற்று நெருக்கமாக (10-15 செ.மீ) பிடிக்கவும்."
16
  },
17
  remedy_chemical: {
18
  en: "Do not apply chemical pesticides until a clear diagnosis is made by an expert.",