Fernando Moreno
Improved patient information section and simplified single timepoint analysis
b240ed0
import streamlit as st
import google.generativeai as genai
from PIL import Image
import os
from dotenv import load_dotenv
import PyPDF2
import io
from datetime import datetime
import pandas as pd
from collections import defaultdict
import re
# Page configuration
st.set_page_config(
page_title="Cornea AI Pentacam Analyzer",
page_icon="👁️",
layout="wide",
initial_sidebar_state="expanded"
)
# Load environment variables
load_dotenv()
# Configure Gemini API
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
model = genai.GenerativeModel("gemini-2.0-flash-exp")
# Custom CSS
st.markdown("""
<style>
.main {
padding: 2rem;
}
.stButton>button {
width: 100%;
background-color: #2E86C1;
color: white;
padding: 0.5rem;
margin-top: 1rem;
}
.credit-box {
background-color: #f0f2f6;
padding: 1.5rem;
border-radius: 0.5rem;
margin: 1rem 0;
border-left: 5px solid #2E86C1;
}
.header-box {
background: linear-gradient(135deg, #2E86C1, #3498DB);
padding: 2rem;
border-radius: 0.5rem;
color: white;
margin-bottom: 2rem;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.image-container {
margin: 1rem 0;
padding: 1rem;
border-radius: 0.5rem;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
}
.analysis-container {
margin-top: 1rem;
padding: 1.5rem;
border-radius: 0.5rem;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
}
.patient-info {
background-color: #fff;
padding: 1.5rem;
border-radius: 0.5rem;
border: 1px solid #e9ecef;
margin-bottom: 1rem;
}
.upload-section {
background-color: #f8f9fa;
padding: 1.5rem;
border-radius: 0.5rem;
border: 1px dashed #2E86C1;
margin: 1rem 0;
}
.info-box {
background-color: #e1f5fe;
padding: 1rem;
border-radius: 0.5rem;
margin: 0.5rem 0;
border-left: 3px solid #03a9f4;
}
.timeline-container {
margin: 2rem 0;
padding: 1rem;
background-color: #fff;
border-radius: 0.5rem;
border: 1px solid #e9ecef;
}
.timepoint-card {
background-color: #f8f9fa;
padding: 1rem;
margin: 0.5rem 0;
border-radius: 0.5rem;
border-left: 3px solid #2E86C1;
}
</style>
""", unsafe_allow_html=True)
# System prompts
CORNEA_ANALYSIS_PROMPT = """You are an expert ophthalmologist specializing in corneal diseases. Analyze these Pentacam scans and patient data with focus on:
1. Corneal Parameters Analysis:
• Thickness mapping and progression
• Topographic changes
• Elevation data (anterior and posterior)
• Keratoconus indices and classification
2. Disease Assessment:
• ABCD Keratoconus staging
• Fuchs Endothelial Corneal Dystrophy evaluation
• Subclinical corneal edema (Sun criteria)
• Risk assessment
3. Clinical Interpretation:
• Pattern recognition
• Disease progression markers
• Treatment implications
Please provide a detailed clinical assessment."""
PROGRESSION_ANALYSIS_PROMPT = """Analyze the progression of corneal parameters across multiple timepoints, focusing on:
1. Temporal Changes:
• Progressive changes in corneal thickness
• Evolution of topographic patterns
• Changes in elevation maps
• Progression of keratoconus indices
2. Rate of Progression:
• Quantify changes between timepoints
• Identify acceleration or stabilization periods
• Compare with expected disease progression
3. Risk Assessment:
• Current status evaluation
• Future progression risk
• Treatment recommendations
4. Timeline Analysis:
• Key changes between each timepoint
• Overall progression pattern
• Critical periods of change
Please provide a comprehensive progression analysis with clinical recommendations."""
def extract_patient_data(uploaded_file):
"""Extract and process patient data from uploaded file"""
patient_data = {}
if uploaded_file.type == "application/pdf":
pdf_reader = PyPDF2.PdfReader(uploaded_file)
text = ""
for page in pdf_reader.pages:
text += page.extract_text()
patient_data['raw_text'] = text
else:
# Handle other file types if needed
patient_data['raw_text'] = "File type not supported for detailed extraction"
return patient_data
def analyze_timepoint(images, date, patient_data=None):
"""Analyze a single timepoint"""
prompt = f"{CORNEA_ANALYSIS_PROMPT}\n\nTimepoint: {date}\n"
if patient_data:
prompt += f"\nPatient Information:\n{patient_data}\n"
prompt += "\nPlease analyze these corneal scans:"
content = [prompt] + images
response = model.generate_content(content)
return response.text
def analyze_progression(timepoints_data):
"""Analyze progression across multiple timepoints"""
prompt = f"{PROGRESSION_ANALYSIS_PROMPT}\n\n"
prompt += "Timepoints for analysis:\n"
# Add all timepoints to the prompt
all_images = []
for date, images in timepoints_data.items():
prompt += f"\n- {date}:"
all_images.extend(images)
prompt += "\n\nPlease analyze the progression across these timepoints:"
content = [prompt] + all_images
response = model.generate_content(content)
return response.text
def extract_date_from_filename(filename):
"""Extract date from filename using common patterns"""
# Common date patterns (add more patterns if needed)
patterns = [
r'(\d{4}[-_/]\d{2}[-_/]\d{2})', # YYYY-MM-DD, YYYY_MM_DD
r'(\d{2}[-_/]\d{2}[-_/]\d{4})', # DD-MM-YYYY, DD_MM_YYYY
r'(\d{8})', # YYYYMMDD
]
for pattern in patterns:
match = re.search(pattern, filename)
if match:
date_str = match.group(1)
try:
# Try different date formats
for fmt in ['%Y-%m-%d', '%Y_%m_%d', '%d-%m-%Y', '%d_%m_%Y', '%Y%m%d']:
try:
return datetime.strptime(date_str.replace('/', '-'), fmt).strftime('%Y-%m-%d')
except ValueError:
continue
except ValueError:
continue
return None
def organize_scans_by_date(files):
"""Organize uploaded files by their dates"""
organized_files = defaultdict(list)
unorganized_files = []
for file in files:
date = extract_date_from_filename(file.name)
if date:
organized_files[date].append(file)
else:
unorganized_files.append(file)
return organized_files, unorganized_files
def main():
# Header
st.markdown("""
<div class="header-box">
<h1>Cornea AI Pentacam Analyzer</h1>
<p style="font-size: 1.2em; margin-top: 1rem;">Advanced Corneal Analysis & Diagnostics</p>
</div>
""", unsafe_allow_html=True)
# Credits
st.markdown("""
<div class="credit-box">
<h3>About</h3>
<p>Developed by Dr. Verónica Gómez Calleja</p>
<p>Cornea Specialist</p>
<p>This advanced tool assists in the analysis of Pentacam scans and corneal conditions using state-of-the-art AI technology.
It provides comprehensive analysis of corneal parameters and supports clinical decision-making in keratoconus, FECD, and other corneal conditions.</p>
<p><strong>Note:</strong> This tool is for assistance only and should not replace professional medical judgment.</p>
</div>
""", unsafe_allow_html=True)
# Patient Information Section
st.markdown("### Patient Information")
st.markdown("""
<div class="info-box">
Upload patient information including:
• Clinical history
• Previous diagnoses
• Current symptoms
• Family history
• Previous treatments
• Current medications
• Other relevant medical conditions
</div>
""", unsafe_allow_html=True)
patient_file = st.file_uploader("Upload Patient Information (PDF/Text)", type=['pdf', 'txt'])
patient_data = None
if patient_file:
patient_data = extract_patient_data(patient_file)
with st.expander("View Extracted Patient Information"):
st.text(patient_data.get('raw_text', 'No text extracted'))
# Scan Analysis Section
st.markdown("### Pentacam Scan Analysis")
analysis_type = st.radio("Select Analysis Type", ["Single Timepoint", "Progression Analysis"])
if analysis_type == "Single Timepoint":
st.markdown('<div class="upload-section">', unsafe_allow_html=True)
uploaded_files = st.file_uploader("Upload Pentacam Scans", type=['png', 'jpg', 'jpeg'], accept_multiple_files=True)
if uploaded_files:
images = []
for file in uploaded_files:
image = Image.open(file)
images.append(image)
if images:
st.markdown("#### Preview Scans")
cols = st.columns(len(images))
for idx, (col, img) in enumerate(zip(cols, images)):
with col:
st.image(img, caption=f"Scan {idx + 1}", use_column_width=True)
if st.button("Analyze Scans"):
with st.spinner("Analyzing..."):
analysis = analyze_timepoint(
images,
datetime.now().strftime("%Y-%m-%d"), # Current date for reference
patient_data.get('raw_text') if patient_data else None
)
st.markdown("### Analysis Results")
st.markdown('<div class="analysis-container">', unsafe_allow_html=True)
st.markdown(analysis)
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
else: # Progression Analysis
st.markdown('<div class="upload-section">', unsafe_allow_html=True)
st.info("""Upload all your Pentacam scans at once. The system will automatically organize them by date and analyze progression.
For best results, ensure your scan filenames include dates (e.g., 'scan_2023-01-15.jpg' or 'pentacam_20230115.png')""")
uploaded_files = st.file_uploader(
"Upload All Pentacam Scans",
type=['png', 'jpg', 'jpeg'],
accept_multiple_files=True
)
if uploaded_files:
organized_files, unorganized_files = organize_scans_by_date(uploaded_files)
if organized_files:
st.markdown("### Organized Scans by Date")
st.markdown('<div class="timeline-container">', unsafe_allow_html=True)
timepoints_data = defaultdict(list)
dates = sorted(organized_files.keys())
for date in dates:
st.markdown(f'<div class="timepoint-card">', unsafe_allow_html=True)
st.markdown(f"#### Timepoint: {date}")
files = organized_files[date]
images = []
cols = st.columns(len(files))
for idx, (file, col) in enumerate(zip(files, cols)):
with col:
image = Image.open(file)
images.append(image)
st.image(image, caption=f"Scan {idx + 1}", use_column_width=True)
timepoints_data[date].extend(images)
st.markdown('</div>', unsafe_allow_html=True)
if unorganized_files:
st.warning(f"{len(unorganized_files)} files couldn't be automatically dated. Please ensure filenames include dates.")
with st.expander("Manually Assign Dates"):
for file in unorganized_files:
col1, col2 = st.columns([2, 1])
with col1:
st.text(file.name)
with col2:
date = st.date_input(f"Date for {file.name}", key=f"manual_{file.name}")
image = Image.open(file)
timepoints_data[date.strftime("%Y-%m-%d")].append(image)
if len(timepoints_data) >= 2:
if st.button("Analyze Progression"):
with st.spinner("Analyzing progression across timepoints..."):
progression_analysis = analyze_progression(timepoints_data)
st.markdown("### Progression Analysis Results")
st.markdown('<div class="analysis-container">', unsafe_allow_html=True)
st.markdown(progression_analysis)
st.markdown('</div>', unsafe_allow_html=True)
else:
st.warning("Please upload scans from at least 2 different timepoints for progression analysis.")
st.markdown('</div>', unsafe_allow_html=True)
else:
st.error("No dated scans found. Please ensure your filenames include dates (e.g., 'scan_2023-01-15.jpg').")
st.markdown('</div>', unsafe_allow_html=True)
if __name__ == "__main__":
main()