#!/usr/bin/env python3 """ CropScan - Plant Disease Detection Hugging Face Space Demo """ import gradio as gr import numpy as np from PIL import Image from pathlib import Path import torch # Model paths MODEL_DIR = Path("models") RFDETR_CHECKPOINT = MODEL_DIR / "rfdetr" / "checkpoint_best_total.pth" SAM2_CHECKPOINT = MODEL_DIR / "sam2" / "sam2.1_hiera_small.pt" # Lazy loaded components segmenter = None leaf_segmenter = None # Clean CSS - green theme, no effects CUSTOM_CSS = """ .gradio-container { background: #0d1117 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .main-header { text-align: center; padding: 1.5rem 0; border-bottom: 1px solid #21262d; margin-bottom: 1.5rem; } .main-header h1 { font-size: 2rem; font-weight: 600; color: #3fb950; margin: 0; } .main-header p { color: #8b949e; margin-top: 0.25rem; font-size: 0.9rem; } .section-label { color: #c9d1d9; font-size: 0.875rem; font-weight: 500; margin-bottom: 0.75rem; } .result-box { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1rem; color: #c9d1d9; } .healthy-status { background: #0d1117; border: 1px solid #238636; border-radius: 6px; padding: 1rem; margin-bottom: 1rem; } .healthy-status h4 { color: #3fb950; margin: 0 0 0.25rem 0; font-size: 1rem; } .warning-status { background: #0d1117; border: 1px solid #9e6a03; border-radius: 6px; padding: 1rem; margin-bottom: 1rem; } .warning-status h4 { color: #d29922; margin: 0 0 0.25rem 0; font-size: 1rem; } .danger-status { background: #0d1117; border: 1px solid #da3633; border-radius: 6px; padding: 1rem; margin-bottom: 1rem; } .danger-status h4 { color: #f85149; margin: 0 0 0.25rem 0; font-size: 1rem; } button.primary { background: #238636 !important; border: none !important; } button.primary:hover { background: #2ea043 !important; } input[type="range"] { accent-color: #238636 !important; } input[type="checkbox"] { accent-color: #238636 !important; } .footer-text { text-align: center; color: #484f58; font-size: 0.8rem; padding: 1rem; border-top: 1px solid #21262d; margin-top: 1rem; } .examples-title { color: #3fb950 !important; font-size: 1.1rem !important; font-weight: 600 !important; margin-bottom: 1rem !important; } .gallery img { height: 120px !important; width: auto !important; object-fit: cover !important; } footer { display: none !important; } """ def load_models(): """Load models on first use.""" global segmenter, leaf_segmenter if segmenter is not None: return print("Loading models...") from src.sam3_segmentation import RFDETRSegmenter from src.leaf_segmenter import SAM2LeafSegmenter segmenter = RFDETRSegmenter( checkpoint_path=str(RFDETR_CHECKPOINT), model_size="medium" ) leaf_segmenter = SAM2LeafSegmenter( checkpoint_path=str(SAM2_CHECKPOINT) ) print("Models loaded!") def get_care_recommendations(num_detections: int, affected_percent: float) -> str: """Generate care recommendations based on detection results.""" if num_detections == 0: return """

Healthy

No disease symptoms detected.

**Care tips:** - Continue regular watering - Ensure adequate sunlight - Monitor for changes """ if affected_percent < 10: severity = "Low" status_class = "warning-status" elif affected_percent < 30: severity = "Moderate" status_class = "warning-status" else: severity = "High" status_class = "danger-status" return f"""

Disease Detected - {severity}

{affected_percent:.1f}% affected | {num_detections} region(s)

**Recommended actions:** 1. **Isolate** - Separate from healthy plants 2. **Remove affected leaves** - Prune with sterilized tools 3. **Treatment** - Copper-based fungicide - Neem oil spray - Improve air circulation 4. **Monitor** - Check daily for 1-2 weeks """ def detect_disease( image: np.ndarray, use_leaf_segmentation: bool = True, confidence_threshold: float = 0.3 ) -> tuple: """Detect plant diseases in an image.""" if image is None: return None, "Upload an image to start." load_models() pil_image = Image.fromarray(image) original_image = image.copy() segmented_image = None leaf_mask = None if use_leaf_segmentation: segmented_pil, leaf_mask = leaf_segmenter.auto_segment_leaf( pil_image, return_mask=True ) segmented_image = np.array(segmented_pil) detection_input = segmented_pil else: detection_input = pil_image prompts = ["diseased plant tissue", "leaf spot", "disease symptom"] seg_result = segmenter.segment_with_concepts( detection_input, prompts, confidence_threshold=confidence_threshold ) num_detections = len(seg_result.boxes) if num_detections > 0: refined_masks = leaf_segmenter.refine_boxes_to_masks( detection_input, seg_result.boxes ) else: refined_masks = np.zeros((0, image.shape[0], image.shape[1]), dtype=bool) from src.visualization import create_mask_overlay if use_leaf_segmentation and segmented_image is not None: base_image = segmented_image else: base_image = original_image if num_detections > 0: annotated = create_mask_overlay(base_image, refined_masks, alpha=0.5) else: annotated = base_image affected_percent = 0 if num_detections > 0: total_mask = np.zeros(refined_masks[0].shape, dtype=bool) for mask in refined_masks: total_mask |= mask if use_leaf_segmentation and leaf_mask is not None: affected_percent = (total_mask & leaf_mask).sum() / max(leaf_mask.sum(), 1) * 100 else: affected_percent = total_mask.sum() / (image.shape[0] * image.shape[1]) * 100 recommendations = get_care_recommendations(num_detections, affected_percent) return annotated, recommendations def create_demo(): """Create Gradio interface.""" with gr.Blocks( title="CropScan", css=CUSTOM_CSS, theme=gr.themes.Base( primary_hue="green", neutral_hue="gray", ).set( body_background_fill="#0d1117", block_background_fill="#161b22", block_border_width="1px", block_border_color="#30363d", input_background_fill="#0d1117", button_primary_background_fill="#238636", ) ) as demo: gr.HTML("""

CropScan

Plant disease detection

""") with gr.Row(): with gr.Column(): input_image = gr.Image( label="Upload", type="numpy", height=350, sources=["upload", "webcam"] ) confidence_slider = gr.Slider( minimum=0.1, maximum=0.9, value=0.3, step=0.05, label="Sensitivity" ) leaf_seg_checkbox = gr.Checkbox( value=False, label="SAM2 precision mode" ) detect_btn = gr.Button("Scan", variant="primary", size="lg") with gr.Column(): output_image = gr.Image( label="Result", type="numpy", height=350 ) detection_info = gr.Markdown() gr.HTML('

Examples - click to load

') example_images = [ ["img1.jpg"], ["img2.jpg"], ["img3.jpg"], ["img4.jpg"], ["img5.jpg"], ] gr.Examples( examples=example_images, inputs=[input_image], examples_per_page=5, ) detect_btn.click( fn=detect_disease, inputs=[input_image, leaf_seg_checkbox, confidence_slider], outputs=[output_image, detection_info] ) gr.HTML('') return demo if __name__ == "__main__": demo = create_demo() demo.launch()