Spaces:
Sleeping
Sleeping
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- backend/app.py +41 -26
- 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
|
|
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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')
|
| 83 |
|
| 84 |
largest_cnt = max(contours, key=cv2.contourArea)
|
| 85 |
x, y, w, h = cv2.boundingRect(largest_cnt)
|
| 86 |
|
| 87 |
-
#
|
| 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 |
-
#
|
| 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,
|
| 177 |
contents = await file.read()
|
| 178 |
|
| 179 |
-
# 0. LEAF FOCALIZER: Pre-process
|
| 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.
|
| 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.
|
| 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 (
|
| 217 |
-
if results_gen and best_prediction['score'] < 0.
|
| 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 |
-
|
| 225 |
-
|
| 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: "
|
| 10 |
-
ta: "
|
| 11 |
},
|
| 12 |
-
cause: { en: "
|
| 13 |
remedy_organic: {
|
| 14 |
-
en: "1.
|
| 15 |
-
ta: "1. இலையின் ம
|
| 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.",
|