import os import cv2 import numpy as np import pytesseract import gradio as gr import io import base64 from datetime import datetime import pytz import urllib.parse import re # Install Tesseract OCR in the Hugging Face container os.system("apt-get update && apt-get install -y tesseract-ocr") # Set timezone to IST ist = pytz.timezone('Asia/Kolkata') current_time = datetime.now(ist).strftime("%Y-%m-%d %I:%M %p IST") # Function to validate phone number (e.g., +91 followed by 10 digits) def validate_phone(phone): pattern = r"^\+\d{1,3}\d{10}$" return bool(re.match(pattern, phone)) # Function to preprocess poor-quality images def preprocess_image(image): try: # Convert to grayscale gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Denoise the image using Gaussian blur denoised = cv2.GaussianBlur(gray, (5, 5), 0) # Sharpen the image using a kernel sharpening_kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) sharpened = cv2.filter2D(denoised, -1, sharpening_kernel) # Adjust contrast using CLAHE clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) contrast_adjusted = clahe.apply(sharpened) # Alternative preprocessing: Otsu's thresholding for better text detection _, otsu_thresh = cv2.threshold(contrast_adjusted, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return otsu_thresh, contrast_adjusted, None except Exception as e: return None, None, f"Error preprocessing image: {str(e)}" # Simulated abnormality detection (placeholder for deep learning model) def detect_abnormalities(image, sharpness, contrast_adjusted): try: # Calculate mean brightness and contrast mean_brightness = np.mean(image) contrast = np.std(contrast_adjusted) warnings = [] if mean_brightness > 200: warnings.append("- Warning: Unusually high brightness detected (mean: {:.1f}). This may indicate overexposure or potential abnormalities like cataracts.".format(mean_brightness)) warnings.append("- Recommendation: Verify with a properly exposed fundus image.") elif mean_brightness < 50: warnings.append("- Warning: Unusually low brightness detected (mean: {:.1f}). This may indicate underexposure or potential issues like retinal detachment.".format(mean_brightness)) warnings.append("- Recommendation: Recapture with better lighting.") if sharpness < 50: warnings.append("- Warning: Poor image sharpness (variance: {:.1f}). This may obscure abnormalities.".format(sharpness)) warnings.append("- Recommendation: Use a higher-quality image with better focus.") if contrast < 20: warnings.append("- Warning: Low contrast detected (std: {:.1f}). This may reduce visibility of anatomical features.".format(contrast)) warnings.append("- Recommendation: Adjust lighting or use a fundus camera.") if not warnings: warnings.append("- No obvious abnormalities detected based on basic image characteristics.") return "\n".join(warnings) except Exception as e: return f"- Error in abnormality detection: {str(e)}" # Function to analyze the eye image def analyze_eye_image(image, patient_name, doctor_email, doctor_phone): try: # Input validation if image is None or not isinstance(image, np.ndarray) or image.size == 0: return None, "Error: No valid image uploaded. Please upload a PNG or JPEG image.", "", "", "" patient_name = patient_name.strip() if patient_name.strip() else "Unknown Patient" if not doctor_email.strip(): return None, "Error: Doctor's email is required.", "", "", "" if not doctor_phone.strip() or not validate_phone(doctor_phone): return None, "Error: Invalid phone number. Use format: +91XXXXXXXXXX", "", "", "" # Convert Gradio image (numpy) to OpenCV format image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) annotated_image = image.copy() # Preprocess the image otsu_thresh, contrast_adjusted, error = preprocess_image(image) if error: return None, error, "", "", "" # Apply adaptive thresholding thresh = cv2.adaptiveThreshold(otsu_thresh, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # Detect text using pytesseract with optimized parameters text_data = pytesseract.image_to_data(thresh, output_type=pytesseract.Output.DICT, config='--psm 6 --oem 3') # Store detected labels and their positions labels = [] for i in range(len(text_data['text'])): confidence = int(text_data['conf'][i]) if confidence > 20: # Lowered threshold to capture iStock, Credit, Gannet77 label = text_data['text'][i].strip() if label: x, y = text_data['left'][i], text_data['top'][i] w, h = text_data['width'][i], text_data['height'][i] labels.append({'label': label, 'position': (x, y), 'size': (w, h)}) # Annotate the image cv2.rectangle(annotated_image, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(annotated_image, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # Merge "Optic" and "nerve" if close merged_labels = [] i = 0 while i < len(labels): if i + 1 < len(labels) and labels[i]['label'].lower() == 'optic' and labels[i + 1]['label'].lower() == 'nerve': merged_label = "Optic Nerve" x, y = labels[i]['position'] merged_labels.append({'label': merged_label, 'position': (x, y)}) i += 2 else: merged_labels.append(labels[i]) i += 1 # Generate report report = f"Eye Image Analysis Report for {patient_name} (Generated on {current_time}):\n\n" report += "Detected Anatomical Features and Their Positions:\n" if merged_labels: for item in merged_labels: report += f"- {item['label']} at position (x: {item['position'][0]}, y: {item['position'][1]})\n" else: report += "No text detected in the image.\n" # Image Quality Assessment report += "\nImage Quality Assessment:\n" sharpness = cv2.Laplacian(contrast_adjusted, cv2.CV_64F).var() if sharpness < 50: report += "- The image quality is poor (blurry or low resolution). This may affect analysis accuracy.\n" report += "- Recommendation: Use a higher-quality image or improve lighting and focus.\n" else: report += "- The image quality is sufficient for analysis.\n" # Detailed Analysis report += "\nDetailed Analysis:\n" detected_labels = [item['label'] for item in merged_labels] if "iStock" in detected_labels and "Credit" in detected_labels and "Gannet77" in detected_labels: report += "- Detected text ('iStock', 'Credit', 'Gannet77') suggests this is a stock image from iStock, credited to 'Gannet77'.\n" report += "- This is likely not a real patient eye scan but a labeled diagram.\n" report += "- No anatomical features (e.g., optic disc, macula) detected, as this is not a real scan.\n" else: report += "- No specific anatomical features detected, possibly due to image quality, type, or lack of text labels.\n" report += "- For poor-quality images or real fundus images, use a fundus camera or smartphone attachment.\n" # Preliminary Abnormality Check report += "\nPreliminary Abnormality Check:\n" report += detect_abnormalities(image, sharpness, contrast_adjusted) report += "- Note: This is a basic check. For accurate diagnosis, integrate a deep learning model (e.g., for diabetic retinopathy or glaucoma) and use a high-quality fundus image.\n" # Recommendations report += "\nRecommendations for the Doctor:\n" report += "- Request a real eye scan (e.g., fundus image) for accurate analysis.\n" report += "- For educational use, confirm anatomical labels manually.\n" report += "- For clinical diagnosis, use high-quality fundus or slit-lamp images.\n" if sharpness < 50: report += "- Advise patient to recapture image with better lighting and resolution.\n" # Convert annotated image back to RGB annotated_image = cv2.cvtColor(annotated_image, cv2.COLOR_BGR2RGB) # Create downloadable report report_bytes = report.encode('utf-8') report_b64 = base64.b64encode(report_bytes).decode('utf-8') report_download_link = f'Download Report' # Generate WhatsApp link whatsapp_message = f"Eye Image Analysis Report for {patient_name}:\n\n{report}" whatsapp_link = f"https://wa.me/{doctor_phone}?text={urllib.parse.quote(whatsapp_message)}" whatsapp_html = f'Send Report via WhatsApp' # Simulate email content email_content = f"Subject: Eye Image Analysis Report for {patient_name}\n\n" email_content += "Dear Doctor,\n\n" email_content += f"Analysis report for patient {patient_name}:\n\n{report}" email_content += "\nThe annotated image and report are attached for review.\n" email_content += "Note: To send this email, deploy with email functionality (e.g., smtplib).\n" return annotated_image, report, report_download_link, email_content, whatsapp_html except Exception as e: return None, f"Error processing image: {str(e)}", "", "", "" # Gradio interface interface = gr.Interface( fn=analyze_eye_image, inputs=[ gr.Image(label="Upload Eye Image (PNG/JPEG)", type="numpy"), gr.Textbox(label="Patient Name (Required)", placeholder="Enter patient's name", lines=1, max_lines=1), gr.Textbox(label="Doctor's Email (Required)", placeholder="Enter doctor's email (e.g., doctor@example.com)"), gr.Textbox(label="Doctor's Phone Number (WhatsApp, Required)", placeholder="Enter phone number (e.g., +919876543210)") ], outputs=[ gr.Image(label="Annotated Image"), gr.Textbox(label="Detailed Analysis Report"), gr.HTML(label="Download Report"), gr.Textbox(label="Email Content (To Be Sent to Doctor)"), gr.HTML(label="WhatsApp Link") ], title="EyeScanIndia: Remote Eye Image Analysis", description=""" Upload an eye image (e.g., fundus image or diagram) to analyze anatomical features. Supports poor-quality images with enhanced preprocessing. Provide patient name, doctor's email, and WhatsApp number to generate a report. **Note**: Ensure compliance with India’s DPDP Act for medical data. For best results, use high-quality fundus images from a fundus camera or smartphone attachment. """, allow_flagging="never" ) # Launch the app for Hugging Face if __name__ == "__main__": interface.launch(server_name="0.0.0.0", server_port=7860)