Spaces:
Build error
Build error
| 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'<a href="data:text/plain;base64,{report_b64}" download="eye_analysis_report_{patient_name}.txt">Download Report</a>' | |
| # 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'<a href="{whatsapp_link}" target="_blank">Send Report via WhatsApp</a>' | |
| # 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) |