Spotradarr / src /app.py
AI-Manith's picture
Upload 3 files
f04d7cf verified
import cv2
import numpy as np
from matplotlib import pyplot as plt
class FruitDiseaseDetector:
def __init__(self):
self.disease_mask = None
self.healthy_mask = None
self.processed_image = None
def remove_background(self, image):
"""
Remove background from fruit image using multiple segmentation techniques
Returns the fruit mask and the image with background removed
"""
# Method 1: GrabCut algorithm (most effective for fruits)
fruit_mask_grabcut = self._grabcut_segmentation(image)
# Method 2: Color-based segmentation
fruit_mask_color = self._color_based_segmentation(image)
# Method 3: Edge-based segmentation
fruit_mask_edge = self._edge_based_segmentation(image)
# Combine all methods using voting
combined_mask = self._combine_masks([fruit_mask_grabcut, fruit_mask_color, fruit_mask_edge])
# Post-process the mask
final_mask = self._post_process_mask(combined_mask)
# Apply mask to image
result_image = image.copy()
result_image[final_mask == 0] = [0, 0, 0] # Set background to black
return final_mask, result_image
def _grabcut_segmentation(self, image):
"""Use GrabCut algorithm for mango foreground/background separation"""
height, width = image.shape[:2]
# Initialize mask
mask = np.zeros((height, width), np.uint8)
# Define rectangle around the mango (mangoes are typically oval/elongated)
# Adjust margins for mango shape - less margin on sides, more on top/bottom
margin_x = int(width * 0.10) # Reduced horizontal margin for mango width
margin_y = int(height * 0.12) # Slightly more vertical margin for mango length
rect = (margin_x, margin_y, width - 2*margin_x, height - 2*margin_y)
# Initialize background and foreground models
bgd_model = np.zeros((1, 65), np.float64)
fgd_model = np.zeros((1, 65), np.float64)
# Apply GrabCut with more iterations for better mango segmentation
cv2.grabCut(image, mask, rect, bgd_model, fgd_model, 8, cv2.GC_INIT_WITH_RECT)
# Create binary mask (0 for background, 1 for foreground)
fruit_mask = np.where((mask == 2) | (mask == 0), 0, 255).astype(np.uint8)
return fruit_mask
def _color_based_segmentation(self, image):
"""Segment mango using mango-specific color characteristics"""
# Convert to HSV for better color segmentation
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Mango-specific color ranges (calibrated for mango dataset)
# Green mangoes (unripe) - broader green range for mangoes
lower_green1 = np.array([35, 25, 25])
upper_green1 = np.array([85, 255, 255])
# Yellow/Orange mangoes (ripe) - extended range for mango ripeness
lower_yellow = np.array([10, 30, 30])
upper_yellow = np.array([35, 255, 255])
# Red/Orange mangoes (very ripe) - specific to mango varieties
lower_red1 = np.array([0, 30, 30])
upper_red1 = np.array([15, 255, 255])
lower_red2 = np.array([165, 30, 30])
upper_red2 = np.array([180, 255, 255])
# Yellowish-green mangoes (semi-ripe)
lower_yellow_green = np.array([25, 20, 40])
upper_yellow_green = np.array([45, 255, 255])
# Create masks for mango color ranges
mask_green = cv2.inRange(hsv, lower_green1, upper_green1)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
mask_red1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask_yellow_green = cv2.inRange(hsv, lower_yellow_green, upper_yellow_green)
# Combine all mango color masks
fruit_mask = cv2.bitwise_or(mask_green, mask_yellow)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_red1)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_red2)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_yellow_green)
# Include areas with moderate saturation and brightness (mango skin variations)
saturation = hsv[:, :, 1] # Saturation channel
value = hsv[:, :, 2] # Value channel
# Mango skin can have lower saturation but still be fruit
_, moderate_sat_mask = cv2.threshold(saturation, 30, 255, cv2.THRESH_BINARY)
_, bright_mask = cv2.threshold(value, 50, 255, cv2.THRESH_BINARY)
# Additional mask for pale/light mango areas
mango_skin_mask = cv2.bitwise_and(moderate_sat_mask, bright_mask)
# Final combination optimized for mangoes
fruit_mask = cv2.bitwise_or(fruit_mask, mango_skin_mask)
return fruit_mask
def _edge_based_segmentation(self, image):
"""Use edge detection and contour analysis for segmentation"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply Gaussian blur
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Edge detection
edges = cv2.Canny(blurred, 50, 150)
# Find contours
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create mask
mask = np.zeros(gray.shape, np.uint8)
if contours:
# Find the largest contour (assuming it's the fruit)
largest_contour = max(contours, key=cv2.contourArea)
# Only consider if the contour is reasonably large
if cv2.contourArea(largest_contour) > (gray.shape[0] * gray.shape[1] * 0.1):
cv2.fillPoly(mask, [largest_contour], 255)
return mask
def _combine_masks(self, masks):
"""Combine multiple masks using majority voting"""
height, width = masks[0].shape
combined = np.zeros((height, width), dtype=np.uint8)
# Convert masks to binary (0 or 1)
binary_masks = [(mask > 127).astype(np.uint8) for mask in masks]
# Sum all masks
mask_sum = np.sum(binary_masks, axis=0)
# Use majority voting (at least 2 out of 3 methods agree)
combined[mask_sum >= 2] = 255
return combined
def _post_process_mask(self, mask):
"""Clean up the mask using morphological operations"""
# Remove small noise
kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
cleaned = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_small)
# Fill small holes
kernel_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_large)
# Find the largest connected component (should be the main fruit)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(cleaned, connectivity=8)
if num_labels > 1:
# Find largest component (excluding background)
largest_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])
# Create mask with only the largest component
final_mask = np.zeros_like(cleaned)
final_mask[labels == largest_label] = 255
else:
final_mask = cleaned
return final_mask
def preprocess_image(self, image):
"""Preprocess the input image for disease detection"""
# Convert to different color spaces for better analysis
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
# Apply Gaussian blur to reduce noise
blurred = cv2.GaussianBlur(image, (5, 5), 0)
return blurred, hsv, lab
def detect_diseased_areas(self, image, fruit_mask=None):
"""
Detect mango diseases using color-based segmentation and texture analysis
Calibrated for: Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)
"""
blurred, hsv, lab = self.preprocess_image(image)
# Method 1: Detect Anthracnose (dark circular spots with orange/pink halos)
disease_mask1 = self._detect_anthracnose(hsv, lab)
# Method 2: Detect Alternaria (brown/black irregular spots)
disease_mask2 = self._detect_alternaria(hsv, lab)
# Method 3: Detect Aspergillus (Black Mould Rot - dark, fuzzy patches)
disease_mask3 = self._detect_aspergillus(hsv, lab)
# Method 4: Detect Lasiodiplodia (Stem and Rot - soft, dark areas)
disease_mask4 = self._detect_lasiodiplodia(hsv, lab)
# Method 5: General texture-based detection for rough/irregular surfaces
disease_mask5 = self._detect_texture_anomalies(blurred)
# Method 6: Edge-based detection for irregular boundaries
disease_mask6 = self._detect_irregular_edges(blurred)
# Combine disease-specific methods first (higher confidence)
primary_disease_mask = cv2.bitwise_or(disease_mask1, disease_mask2)
primary_disease_mask = cv2.bitwise_or(primary_disease_mask, disease_mask3)
primary_disease_mask = cv2.bitwise_or(primary_disease_mask, disease_mask4)
# Secondary detection (texture and edges) - use only if there's significant evidence
secondary_mask = cv2.bitwise_and(disease_mask5, disease_mask6)
# Combine primary and secondary with different weights
combined_mask = cv2.bitwise_or(primary_disease_mask, secondary_mask)
# Apply fruit mask to limit detection to mango area only
if fruit_mask is not None:
combined_mask = cv2.bitwise_and(combined_mask, fruit_mask)
# Post-processing: More aggressive noise removal for better accuracy
kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
kernel_medium = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
# Remove small noise (more aggressive)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_small)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_medium)
# Fill small holes but not too aggressively
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel_close)
# Filter out very small regions (likely noise)
contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
filtered_mask = np.zeros_like(combined_mask)
min_area = 75 # Balanced minimum area for good detection
for contour in contours:
if cv2.contourArea(contour) >= min_area:
cv2.fillPoly(filtered_mask, [contour], 255)
return filtered_mask
def _detect_anthracnose(self, hsv, lab):
"""Detect Anthracnose disease - dark circular spots with orange/pink halos"""
# Anthracnose characteristics: Dark centers with lighter halos
# Use both HSV and LAB for better detection
# Dark spots in HSV (less conservative thresholds)
lower_dark = np.array([0, 40, 0])
upper_dark = np.array([180, 255, 80])
dark_mask = cv2.inRange(hsv, lower_dark, upper_dark)
# Reddish-brown areas (anthracnose lesions) - broader range
lower_reddish = np.array([0, 50, 15])
upper_reddish = np.array([25, 255, 140])
reddish_mask = cv2.inRange(hsv, lower_reddish, upper_reddish)
# LAB color space for better brown/dark detection
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
# Less conservative dark areas with reddish tint
_, dark_lab = cv2.threshold(l_channel, 85, 255, cv2.THRESH_BINARY_INV)
_, red_lab = cv2.threshold(a_channel, 135, 255, cv2.THRESH_BINARY)
lab_anthracnose = cv2.bitwise_and(dark_lab, red_lab)
# Combine HSV and LAB detections - use OR instead of AND for better sensitivity
anthracnose_mask = cv2.bitwise_or(dark_mask, reddish_mask)
anthracnose_mask = cv2.bitwise_or(anthracnose_mask, lab_anthracnose)
return anthracnose_mask
def _detect_alternaria(self, hsv, lab):
"""Detect Alternaria disease - brown/black irregular spots with concentric rings"""
# Alternaria has characteristic brown to black lesions
# Less conservative brown to black spots in HSV
lower_brown1 = np.array([5, 60, 5])
upper_brown1 = np.array([25, 255, 100])
lower_brown2 = np.array([0, 40, 3])
upper_brown2 = np.array([20, 255, 80])
brown_mask1 = cv2.inRange(hsv, lower_brown1, upper_brown1)
brown_mask2 = cv2.inRange(hsv, lower_brown2, upper_brown2)
# Very dark areas (advanced alternaria) - less conservative
lower_black = np.array([0, 30, 0])
upper_black = np.array([180, 255, 50])
black_mask = cv2.inRange(hsv, lower_black, upper_black)
# LAB space detection for brown lesions - less strict
l_channel = lab[:, :, 0]
_, dark_lab = cv2.threshold(l_channel, 70, 255, cv2.THRESH_BINARY_INV)
# Combine alternaria indicators - use OR for better sensitivity
alternaria_mask = cv2.bitwise_or(brown_mask1, brown_mask2)
alternaria_mask = cv2.bitwise_or(alternaria_mask, black_mask)
alternaria_mask = cv2.bitwise_or(alternaria_mask, dark_lab)
return alternaria_mask
def _detect_aspergillus(self, hsv, lab):
"""Detect Aspergillus (Black Mould Rot) - dark, fuzzy patches with irregular borders"""
# Aspergillus appears as very dark, irregular patches with possible greenish tints
# Very dark areas in HSV - less conservative
lower_black = np.array([0, 20, 0])
upper_black = np.array([180, 255, 40])
black_mask = cv2.inRange(hsv, lower_black, upper_black)
# Dark greenish areas (aspergillus can have greenish tint) - broader range
lower_dark_green = np.array([40, 40, 3])
upper_dark_green = np.array([80, 255, 60])
dark_green_mask = cv2.inRange(hsv, lower_dark_green, upper_dark_green)
# Dark bluish-green areas (specific to aspergillus) - broader range
lower_blue_green = np.array([85, 30, 5])
upper_blue_green = np.array([120, 255, 80])
blue_green_mask = cv2.inRange(hsv, lower_blue_green, upper_blue_green)
# LAB space for very dark areas - less conservative
l_channel = lab[:, :, 0]
b_channel = lab[:, :, 2]
_, very_dark_lab = cv2.threshold(l_channel, 50, 255, cv2.THRESH_BINARY_INV)
# Blue-green tint in LAB space (aspergillus characteristic) - broader range
_, blue_tint = cv2.threshold(b_channel, 110, 255, cv2.THRESH_BINARY_INV)
lab_aspergillus = cv2.bitwise_and(very_dark_lab, blue_tint)
# Combine aspergillus indicators - use OR for better sensitivity
aspergillus_mask = cv2.bitwise_or(black_mask, very_dark_lab)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, dark_green_mask)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, blue_green_mask)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, lab_aspergillus)
return aspergillus_mask
def _detect_lasiodiplodia(self, hsv, lab):
"""Detect Lasiodiplodia (Stem and Rot disease) - soft, dark areas with brown/black coloration"""
# Lasiodiplodia characteristics: dark, soft areas with brown/black coloration
# Brown rot areas - less conservative
lower_rot = np.array([8, 50, 10])
upper_rot = np.array([30, 255, 120])
rot_mask = cv2.inRange(hsv, lower_rot, upper_rot)
# Dark spots with higher saturation (active rot) - less strict
lower_active_rot = np.array([0, 60, 5])
upper_active_rot = np.array([20, 255, 100])
active_rot_mask = cv2.inRange(hsv, lower_active_rot, upper_active_rot)
# Very dark areas (advanced lasiodiplodia) - broader range
lower_very_dark = np.array([0, 30, 0])
upper_very_dark = np.array([30, 255, 60])
very_dark_mask = cv2.inRange(hsv, lower_very_dark, upper_very_dark)
# LAB space for detecting rot areas - less conservative
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
_, dark_rot = cv2.threshold(l_channel, 70, 255, cv2.THRESH_BINARY_INV)
_, red_tint = cv2.threshold(a_channel, 130, 255, cv2.THRESH_BINARY)
lab_rot = cv2.bitwise_and(dark_rot, red_tint)
# Combine lasiodiplodia indicators - use OR for better sensitivity
lasiodiplodia_mask = cv2.bitwise_or(rot_mask, active_rot_mask)
lasiodiplodia_mask = cv2.bitwise_or(lasiodiplodia_mask, very_dark_mask)
lasiodiplodia_mask = cv2.bitwise_or(lasiodiplodia_mask, lab_rot)
return lasiodiplodia_mask
def _detect_black_mould_rot(self, hsv, lab):
"""Detect Black Mould Rot - dark, fuzzy patches with irregular borders (legacy method)"""
# This method is now replaced by _detect_aspergillus but kept for compatibility
return self._detect_aspergillus(hsv, lab)
def _detect_stem_rot(self, hsv, lab):
"""Detect Stem and Rot disease - soft, dark areas typically near stem end (legacy method)"""
# This method is now replaced by _detect_lasiodiplodia but kept for compatibility
return self._detect_lasiodiplodia(hsv, lab)
def _detect_brown_spots(self, hsv):
"""Detect brown/dark spots typical of fruit diseases (legacy method)"""
# Define HSV range for brown/dark diseased areas
# Brown spots: Low saturation, low value, wide hue range
lower_brown = np.array([0, 20, 20])
upper_brown = np.array([30, 255, 120])
# Create mask for brown areas
brown_mask1 = cv2.inRange(hsv, lower_brown, upper_brown)
# Additional brown range (reddish-brown)
lower_brown2 = np.array([150, 20, 20])
upper_brown2 = np.array([180, 255, 120])
brown_mask2 = cv2.inRange(hsv, lower_brown2, upper_brown2)
# Combine brown masks
brown_mask = cv2.bitwise_or(brown_mask1, brown_mask2)
return brown_mask
def _detect_dark_spots_lab(self, lab):
"""Detect dark spots using LAB color space"""
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
# Dark areas have low L values
_, dark_mask = cv2.threshold(l_channel, 80, 255, cv2.THRESH_BINARY_INV)
# Areas with high 'a' values (reddish) combined with darkness
_, red_mask = cv2.threshold(a_channel, 140, 255, cv2.THRESH_BINARY)
# Combine dark and reddish areas
lab_mask = cv2.bitwise_and(dark_mask, red_mask)
return lab_mask
def _detect_texture_anomalies(self, image):
"""Detect texture anomalies using local binary patterns concept (calibrated for mango)"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Calculate local standard deviation (texture measure) - optimized for mango texture
kernel = np.ones((7, 7), np.float32) / 49 # Slightly smaller kernel for mango details
gray_float = gray.astype(np.float32)
mean = cv2.filter2D(gray_float, -1, kernel)
sqr_mean = cv2.filter2D(gray_float**2, -1, kernel)
# Ensure variance is non-negative (handle floating point precision errors)
variance = sqr_mean - mean**2
variance = np.maximum(variance, 0) # Clamp negative values to 0
std_dev = np.sqrt(variance)
# Handle NaN and infinity values
std_dev = np.nan_to_num(std_dev, nan=0.0, posinf=255.0, neginf=0.0)
# Normalize to 0-255 range
if std_dev.max() > 0:
std_dev = (std_dev / std_dev.max() * 255).astype(np.uint8)
else:
std_dev = std_dev.astype(np.uint8)
# Higher threshold for mango diseases (less sensitive to normal texture)
_, texture_mask = cv2.threshold(std_dev, 30, 255, cv2.THRESH_BINARY)
# Additional texture analysis for mango-specific roughness
# Use Sobel operators to detect rough areas - more conservative
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel_magnitude = np.sqrt(sobelx**2 + sobely**2)
sobel_magnitude = np.uint8(sobel_magnitude / sobel_magnitude.max() * 255)
# Higher threshold for rough texture detection (less sensitive)
_, rough_mask = cv2.threshold(sobel_magnitude, 50, 255, cv2.THRESH_BINARY)
# Combine standard deviation and sobel-based texture detection
# Use AND operation to require both methods to agree (more conservative)
final_texture_mask = cv2.bitwise_and(texture_mask, rough_mask)
return final_texture_mask
def _detect_irregular_edges(self, image):
"""Detect irregular edges that might indicate disease boundaries"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply Canny edge detection
edges = cv2.Canny(gray, 50, 150)
# Dilate edges to create regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
dilated_edges = cv2.dilate(edges, kernel, iterations=2)
return dilated_edges
def calculate_disease_severity(self, image, disease_mask, fruit_mask):
"""Calculate disease severity as percentage of affected area"""
if fruit_mask is not None:
# Use provided fruit mask
fruit_area = cv2.countNonZero(fruit_mask)
else:
# Fallback: create fruit mask (assuming fruit takes up most of the image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, fruit_mask = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
fruit_area = cv2.countNonZero(fruit_mask)
# Calculate disease area (only within fruit region)
if fruit_mask is not None:
disease_in_fruit = cv2.bitwise_and(disease_mask, fruit_mask)
disease_area = cv2.countNonZero(disease_in_fruit)
else:
disease_area = cv2.countNonZero(disease_mask)
if fruit_area == 0:
return 0
severity_percentage = (disease_area / fruit_area) * 100
return min(severity_percentage, 100) # Cap at 100%
def classify_disease_level(self, severity_percentage):
"""Classify mango disease level based on severity percentage (calibrated for mango)"""
if severity_percentage < 2:
return "Healthy", (0, 255, 0) # Green - very strict for healthy
elif severity_percentage < 8:
return "Early Disease", (0, 255, 255) # Yellow - early detection
elif severity_percentage < 20:
return "Moderate Disease", (0, 165, 255) # Orange - moderate infection
elif severity_percentage < 40:
return "Severe Disease", (0, 100, 255) # Red-Orange - severe infection
else:
return "Critical Disease", (0, 0, 255) # Red - critical, unmarketable
def process_image(self, image_path):
"""Main processing function with background removal (calibrated for mango diseases)"""
# Load image
image = cv2.imread(image_path)
if image is None:
raise ValueError("Could not load image")
print("Step 1: Removing background (mango-optimized)...")
# Remove background first
fruit_mask, image_no_bg = self.remove_background(image)
print("Step 2: Detecting mango diseases (Alternaria, Anthracnose, Aspergillus, Lasiodiplodia)...")
# Detect diseased areas (only within mango region)
disease_mask = self.detect_diseased_areas(image_no_bg, fruit_mask)
print("Step 3: Calculating mango disease severity...")
# Calculate severity
severity = self.calculate_disease_severity(image_no_bg, disease_mask, fruit_mask)
disease_level, color = self.classify_disease_level(severity)
print("Step 4: Creating mango disease visualization...")
# Create output visualization with bounding boxes
output_image, disease_info = self._create_output_visualization(image, disease_mask, severity, disease_level, color, fruit_mask)
# Store results
self.disease_mask = disease_mask
self.processed_image = output_image
self.fruit_mask = fruit_mask
self.image_no_bg = image_no_bg
self.disease_info = disease_info
return {
'severity_percentage': severity,
'disease_level': disease_level,
'disease_mask': disease_mask,
'output_image': output_image,
'fruit_mask': fruit_mask,
'image_no_bg': image_no_bg,
'disease_info': disease_info,
'num_diseased_regions': len(disease_info)
}
def _create_output_visualization(self, original, mask, severity, level, color, fruit_mask=None):
"""Create visualization showing detected diseased areas with bounding boxes"""
# Start with original image
result = original.copy()
# If we have a fruit mask, dim the background
if fruit_mask is not None:
background = np.zeros_like(original)
result = np.where(fruit_mask[..., np.newaxis] == 0,
background, result)
# Create colored overlay for diseased areas
overlay = result.copy()
disease_info = []
if np.any(mask > 0): # Only if there are diseased areas
overlay[mask > 0] = color
# Blend original image with overlay
result = cv2.addWeighted(result, 0.7, overlay, 0.3, 0)
# Find contours and draw bounding boxes
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter contours by minimum area to avoid tiny noise
min_area = 50 # Minimum area threshold
valid_contours = [cnt for cnt in contours if cv2.contourArea(cnt) >= min_area]
# Draw contours and bounding boxes
for i, contour in enumerate(valid_contours):
# Draw contour outline
cv2.drawContours(result, [contour], -1, color, 2)
# Get bounding rectangle
x, y, w, h = cv2.boundingRect(contour)
# Draw bounding box
cv2.rectangle(result, (x, y), (x + w, y + h), color, 3)
# Calculate area of this diseased region
area = cv2.contourArea(contour)
disease_info.append({
'id': i + 1,
'bbox': (x, y, w, h),
'area': area,
'center': (x + w//2, y + h//2)
})
# Add label with disease ID
label = f"D{i+1}"
label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
# Position label above bounding box
label_x = x
label_y = y - 10 if y - 10 > 20 else y + h + 25
# Draw label background
cv2.rectangle(result,
(label_x - 2, label_y - label_size[1] - 2),
(label_x + label_size[0] + 2, label_y + 2),
color, -1)
# Draw label text
cv2.putText(result, label, (label_x, label_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Add overall statistics
num_diseases = len(disease_info)
text1 = f"{level}: {severity:.1f}%"
text2 = f"Diseased Regions: {num_diseases}"
# Main status text
cv2.putText(result, text1, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
cv2.putText(result, text2, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
# Add fruit outline
if fruit_mask is not None:
fruit_contours, _ = cv2.findContours(fruit_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(result, fruit_contours, -1, (255, 255, 255), 2)
return result, disease_info
def save_detailed_report(self, output_path, results):
"""Save detailed report of disease detection results"""
report_path = output_path.replace('.jpg', '_report.txt').replace('.png', '_report.txt')
with open(report_path, 'w') as f:
f.write("=== FRUIT DISEASE DETECTION REPORT ===\n")
f.write(f"Image processed: {output_path}\n")
f.write(f"Overall Disease Level: {results['disease_level']}\n")
f.write(f"Overall Severity: {results['severity_percentage']:.2f}%\n")
f.write(f"Number of Diseased Regions: {results['num_diseased_regions']}\n")
f.write("\n=== INDIVIDUAL DISEASE REGIONS ===\n")
if results['disease_info']:
for disease in results['disease_info']:
f.write(f"\nDisease Region D{disease['id']}:\n")
f.write(f" - Bounding Box: x={disease['bbox'][0]}, y={disease['bbox'][1]}, ")
f.write(f"width={disease['bbox'][2]}, height={disease['bbox'][3]}\n")
f.write(f" - Area: {disease['area']:.0f} pixels\n")
f.write(f" - Center: ({disease['center'][0]}, {disease['center'][1]})\n")
else:
f.write("No diseased regions detected.\n")
print(f"Detailed report saved: {report_path}")
return report_path
def save_results(self, output_path, include_mask=True, include_background_removed=True):
"""Save processing results"""
if self.processed_image is not None:
cv2.imwrite(output_path, self.processed_image)
print(f"Main result saved: {output_path}")
if include_mask and self.disease_mask is not None:
mask_path = output_path.replace('.', '_disease_mask.')
cv2.imwrite(mask_path, self.disease_mask)
print(f"Disease mask saved: {mask_path}")
if include_background_removed and hasattr(self, 'image_no_bg') and self.image_no_bg is not None:
bg_removed_path = output_path.replace('.', '_no_background.')
cv2.imwrite(bg_removed_path, self.image_no_bg)
print(f"Background removed image saved: {bg_removed_path}")
if hasattr(self, 'fruit_mask') and self.fruit_mask is not None:
fruit_mask_path = output_path.replace('.', '_fruit_mask.')
cv2.imwrite(fruit_mask_path, self.fruit_mask)
print(f"Fruit mask saved: {fruit_mask_path}")
# Example usage and testing function
def demonstrate_disease_detection():
"""Demonstrate the mango disease detection algorithm"""
detector = FruitDiseaseDetector()
print("Mango Disease Detection Algorithm with Background Removal & Bounding Boxes")
print("========================================================================")
print("This algorithm detects mango diseases using:")
print("1. Mango-optimized background removal (GrabCut + Color + Edge detection)")
print("2. Anthracnose detection (dark circular spots with orange/pink halos)")
print("3. Alternaria detection (brown/black irregular spots with concentric rings)")
print("4. Aspergillus detection (black mould with greenish tints)")
print("5. Lasiodiplodia detection (stem rot with brown/black soft areas)")
print("6. Mango-calibrated texture analysis for rough surfaces")
print("7. Edge detection for irregular disease boundaries")
print("8. Individual disease region bounding boxes")
print()
print("Algorithm Features for Mango:")
print("- Multi-method background removal optimized for mango shape")
print("- Disease-specific detection for common mango diseases")
print("- Individual region labeling (D1, D2, D3, etc.)")
print("- Mango-specific severity assessment (Healthy < 2%, Early < 8%, etc.)")
print("- Comprehensive reporting for mango disease management")
print("- Calibrated for mango color variations (green, yellow, orange, red)")
print("- Specialized detection for Aspergillus (Black Mould) and Lasiodiplodia (Stem Rot)")
return detector
# Batch testing function for algorithm validation
def test_mango_detection_algorithm():
"""Test the algorithm on multiple mango samples"""
detector = FruitDiseaseDetector()
# Test cases with expected results - using correct file names
test_cases = [
("SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg", "Should be Healthy"),
("SenMangoFruitDDS_bgremoved/Healthy/healthy_010.jpg", "Should be Healthy"),
("SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg", "Should detect Alternaria"),
("SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_010.jpg", "Should detect Alternaria"),
("SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg", "Should detect Anthracnose"),
("SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_010.jpg", "Should detect Anthracnose"),
("SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_001.jpg", "Should detect Aspergillus (Black Mould)"),
("SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_010.jpg", "Should detect Aspergillus (Black Mould)"),
("SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_001.jpg", "Should detect Lasiodiplodia (Stem Rot)"),
("SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_012.jpg", "Should detect Lasiodiplodia (Stem Rot)"),
]
print("=== MANGO DISEASE DETECTION ALGORITHM VALIDATION ===")
print("Testing multiple samples to validate algorithm performance")
print("Diseases: Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)")
print("=" * 80)
results_summary = []
for i, (image_path, expected) in enumerate(test_cases, 1):
print(f"\nTest {i}: {image_path}")
print(f"Expected: {expected}")
try:
results = detector.process_image(image_path)
output_path = f"test_result_{i}.jpg"
result_info = {
'test_id': i,
'image_path': image_path,
'expected': expected,
'detected_level': results['disease_level'],
'severity': results['severity_percentage'],
'num_regions': results['num_diseased_regions']
}
results_summary.append(result_info)
print(f"Result: {results['disease_level']} ({results['severity_percentage']:.2f}%)")
print(f"Regions: {results['num_diseased_regions']}")
# Save test result
detector.save_results(output_path, include_mask=True)
except Exception as e:
print(f"Error: {e}")
results_summary.append({
'test_id': i,
'image_path': image_path,
'expected': expected,
'detected_level': 'ERROR',
'severity': 0.0,
'num_regions': 0
})
print("-" * 50)
# Print summary
print("\n" + "=" * 80)
print("TESTING SUMMARY")
print("=" * 80)
for result in results_summary:
status = "βœ“" if result['detected_level'] != 'Healthy' and 'Should detect' in result['expected'] else "βœ—" if result['detected_level'] == 'Healthy' and 'Should detect' in result['expected'] else "βœ“"
print(f"Test {result['test_id']:2d}: {status} {result['detected_level']} ({result['severity']:.1f}%) - {result['expected']}")
return results_summary
# Usage example:
if __name__ == "__main__":
# Initialize mango disease detector
detector = FruitDiseaseDetector()
# Test different disease types - uncomment to test specific diseases:
# image_path = "SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg" # Should be Healthy
# image_path = "SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg" # Should detect Alternaria
# image_path = "SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg" # Should detect Anthracnose
# image_path = "SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_001.jpg" # Should detect Aspergillus
image_path = "SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_012.jpg" # Should detect Lasiodiplodia
output_path = "mango_disease_detection_result.jpg" # Output file name
try:
print("Processing mango image with calibrated algorithm...")
print(f"Input: {image_path}")
print(f"Output: {output_path}")
print("-" * 60)
results = detector.process_image(image_path)
print(f"\n=== MANGO DISEASE DETECTION RESULTS ===")
print(f"Disease Level: {results['disease_level']}")
print(f"Severity: {results['severity_percentage']:.2f}%")
print(f"Number of Diseased Regions: {results['num_diseased_regions']}")
# Print individual disease information
if results['disease_info']:
print(f"\n=== INDIVIDUAL DISEASE REGIONS ===")
for disease in results['disease_info']:
print(f"Disease D{disease['id']}: Area={disease['area']:.0f} pixels, "
f"Center=({disease['center'][0]}, {disease['center'][1]})")
else:
print("\n=== MANGO HEALTH STATUS ===")
print("No significant disease regions detected - mango appears healthy!")
# Save results
detector.save_results(output_path, include_mask=True)
# Save detailed report
detector.save_detailed_report(output_path, results)
print(f"\n=== FILES SAVED ===")
print(f"Main result: {output_path}")
print(f"Disease mask: {output_path.replace('.', '_disease_mask.')}")
print(f"Background removed: {output_path.replace('.', '_no_background.')}")
print(f"Fruit mask: {output_path.replace('.', '_fruit_mask.')}")
print(f"Detailed report: {output_path.replace('.jpg', '_report.txt')}")
print("\nMango disease detection completed successfully!")
print("The algorithm has been calibrated for mango-specific diseases:")
print("- Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)")
except FileNotFoundError:
print(f"Error: Could not find mango image file '{image_path}'")
print("Please check the file path and make sure the image exists.")
print("\nAvailable test images:")
print("- SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg")
print("- SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg")
print("- SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg")
except Exception as e:
print(f"Error processing mango image: {e}")
# Uncomment below to see algorithm demonstration
# demonstrate_disease_detection()
# Uncomment below to run batch testing
# test_mango_detection_algorithm()