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)