import os import gradio as gr import pandas as pd from PIL import Image import io import datetime import re import traceback import base64 from openai import OpenAI # Initialize OpenAI client try: openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) except Exception as e: print(f"Error initializing OpenAI client: {e}") openai_client = None # Function to encode the image def encode_image(image): if isinstance(image, Image.Image): # Convert PIL Image to bytes buffered = io.BytesIO() # Ensure image is in RGB format if image.mode in ('RGBA', 'P', 'LA'): image = image.convert('RGB') image.save(buffered, format="JPEG") image_bytes = buffered.getvalue() return base64.b64encode(image_bytes).decode("utf-8") elif isinstance(image, str) and os.path.exists(image): with open(image, "rb") as image_file: return base64.b64encode(image_file.read()).decode("utf-8") return None # Process patient history file def process_patient_history(file): if file is None: return "" try: # Check file extension file_path = file.name file_ext = os.path.splitext(file_path)[1].lower() if file_ext == '.txt': # Read text file with open(file_path, 'r', encoding='utf-8') as f: content = f.read() return content elif file_ext in ['.csv', '.xlsx', '.xls']: # Read spreadsheet file if file_ext == '.csv': df = pd.read_csv(file_path) else: try: df = pd.read_excel(file_path) except ImportError: return "Error: `openpyxl` needed for .xlsx files. Install with `pip install openpyxl`" except Exception as e_excel: return f"Error reading Excel file: {e_excel}" # Convert dataframe to formatted string formatted_data = "PATIENT INFORMATION:\n\n" if not df.empty: for column in df.columns: value = df.iloc[0].get(column, 'N/A') formatted_data += f"{column}: {str(value)}\n" else: formatted_data += "Spreadsheet is empty or format is not recognized correctly." return formatted_data else: return f"Unsupported file format ({file_ext}). Please upload a .txt, .csv, or .xlsx file." except AttributeError: return "Error: Could not get file path from Gradio File object. Ensure a file was uploaded." except FileNotFoundError: return f"Error: File not found at path: {file_path}" except Exception as e: print(f"Error processing patient history file:\n{traceback.format_exc()}") return f"Error processing patient history file: {str(e)}" # Extract ECG readings from image using GPT-4.1 def analyze_ecg_image(image): if image is None: return "No image provided." # Ensure OpenAI client is initialized if openai_client is None: return "OpenAI client not initialized. Check API Key." # Ensure image is PIL Image if not isinstance(image, Image.Image): try: if isinstance(image, str) and os.path.exists(image): image = Image.open(image) elif hasattr(image, 'name'): image = Image.open(image.name) else: return f"Unrecognized image input format: {type(image)}" except Exception as e: print(f"Error opening image:\n{traceback.format_exc()}") return f"Error opening image: {str(e)}" try: # Get current timestamp timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Convert image to base64 base64_image = encode_image(image) if not base64_image: return "Failed to encode image." # Create prompt for GPT-4.1 vision_prompt = f"""Analyze this ECG image carefully. You are a cardiologist analyzing an electrocardiogram (ECG). IMPORTANT: Only report what you can actually see clearly displayed in this specific ECG screen. Do not include any measurements or values that are not visible or not displayed digitally in the image. Only create sections for values that are actually shown in the image. Look for and extract visible measurements from the ECG display, which may include: - Heart rate (if displayed digitally) - Any numeric measurements shown on the screen - Visible rhythm patterns - Any clearly labeled values or measurements Report exact numerical values where visible. If a value is not displayed or not visible, DO NOT include that section at all in your response. Format your response strictly like this:

ECG Report

Visible Findings

Visual Assessment

[Brief summary based ONLY on what is visible in this specific ECG display]

Critical rules: - Do NOT add sections for measurements not visible in the image - Do NOT write "Not determinable from image" for any parameter - Only include data that you can actually see in this ECG screen - Report only the exact values or descriptions visible in the image - If certain standard ECG parameters are not shown, simply don't include them """ # Generate content using GPT-4.1 response = openai_client.responses.create( model="gpt-4.1", input=[ { "role": "user", "content": [ { "type": "input_text", "text": vision_prompt }, { "type": "input_image", "image_url": f"data:image/jpeg;base64,{base64_image}", }, ], } ] ) ecg_analysis = response.output_text # Basic post-processing to ensure HTML format ecg_analysis = re.sub(r'\*\*(.*?)\*\*', r'\1', ecg_analysis) ecg_analysis = re.sub(r'^\s*#+\s+(.*?)\s*$', r'

\1

', ecg_analysis, flags=re.MULTILINE) ecg_analysis = re.sub(r'^\s*[\*-]\s+(.*?)\s*$', r'
  • \1
  • ', ecg_analysis, flags=re.MULTILINE) # Check if the response looks like the requested HTML structure if not ("

    " in ecg_analysis and "