Spaces:
Sleeping
Sleeping
Fernando Moreno
commited on
Commit
·
2d2d2d9
1
Parent(s):
86ab63d
Enhanced app with multi-timepoint progression analysis and simplified patient data input
Browse files
app.py
CHANGED
@@ -6,6 +6,8 @@ from dotenv import load_dotenv
|
|
6 |
import PyPDF2
|
7 |
import io
|
8 |
from datetime import datetime
|
|
|
|
|
9 |
|
10 |
# Page configuration
|
11 |
st.set_page_config(
|
@@ -86,60 +88,89 @@ st.markdown("""
|
|
86 |
margin: 0.5rem 0;
|
87 |
border-left: 3px solid #03a9f4;
|
88 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
</style>
|
90 |
""", unsafe_allow_html=True)
|
91 |
|
92 |
# System prompts
|
93 |
-
CORNEA_ANALYSIS_PROMPT = """You are an expert ophthalmologist specializing in corneal diseases.
|
94 |
|
95 |
-
|
96 |
-
•
|
97 |
-
•
|
98 |
-
•
|
99 |
-
•
|
100 |
|
101 |
-
|
102 |
-
•
|
103 |
-
•
|
104 |
-
•
|
105 |
-
•
|
106 |
-
• Evaluate focal depression of posterior surface
|
107 |
|
108 |
-
|
|
|
|
|
|
|
109 |
|
110 |
-
|
111 |
|
112 |
-
|
113 |
-
• Thickness variations
|
114 |
-
• Topographic changes
|
115 |
-
• Elevation differences
|
116 |
-
• Progression indicators
|
117 |
|
118 |
-
|
119 |
-
•
|
120 |
-
•
|
121 |
-
•
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
-
|
124 |
-
•
|
125 |
-
•
|
126 |
-
•
|
127 |
|
128 |
-
|
129 |
-
"""Extract text from uploaded PDF file"""
|
130 |
-
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
131 |
-
text = ""
|
132 |
-
for page in pdf_reader.pages:
|
133 |
-
text += page.extract_text()
|
134 |
-
return text
|
135 |
|
136 |
-
def
|
137 |
-
"""
|
138 |
-
|
139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
else:
|
141 |
-
|
|
|
142 |
|
|
|
|
|
|
|
|
|
|
|
143 |
if patient_data:
|
144 |
prompt += f"\nPatient Information:\n{patient_data}\n"
|
145 |
|
@@ -148,15 +179,19 @@ def analyze_cornea_images(images, timepoint=None, patient_data=None):
|
|
148 |
response = model.generate_content(content)
|
149 |
return response.text
|
150 |
|
151 |
-
def
|
152 |
-
"""
|
153 |
-
prompt = f"{
|
|
|
154 |
|
155 |
-
|
156 |
-
|
|
|
|
|
|
|
157 |
|
158 |
-
prompt += "\nPlease
|
159 |
-
content = [prompt] +
|
160 |
response = model.generate_content(content)
|
161 |
return response.text
|
162 |
|
@@ -181,111 +216,99 @@ def main():
|
|
181 |
</div>
|
182 |
""", unsafe_allow_html=True)
|
183 |
|
184 |
-
#
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
with st.
|
191 |
-
st.
|
192 |
-
patient_name = st.text_input("Patient Name")
|
193 |
-
patient_id = st.text_input("Patient ID")
|
194 |
-
dob = st.date_input("Date of Birth")
|
195 |
-
diagnosis = st.text_input("Previous Diagnosis (if any)")
|
196 |
-
|
197 |
-
# Clinical History
|
198 |
-
st.markdown("#### Clinical History")
|
199 |
-
history_text = st.text_area("Enter relevant clinical history")
|
200 |
-
|
201 |
-
# Multiple PDF uploads for patient records
|
202 |
-
st.markdown("#### Additional Patient Records (PDF)")
|
203 |
-
patient_pdfs = st.file_uploader("Upload Patient Records", type=['pdf'], accept_multiple_files=True)
|
204 |
-
if patient_pdfs:
|
205 |
-
for pdf in patient_pdfs:
|
206 |
-
with st.expander(f"View {pdf.name}"):
|
207 |
-
st.text(extract_pdf_text(pdf))
|
208 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
209 |
|
210 |
-
|
211 |
-
|
|
|
|
|
|
|
212 |
st.markdown('<div class="upload-section">', unsafe_allow_html=True)
|
213 |
-
|
|
|
214 |
|
215 |
-
if
|
216 |
-
|
217 |
-
|
|
|
|
|
218 |
|
219 |
-
if
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
elif file.type == 'application/pdf':
|
226 |
-
# Handle PDF files here if needed
|
227 |
-
st.warning(f"PDF processing for {file.name} will be implemented soon")
|
228 |
|
229 |
-
if
|
230 |
-
st.
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
|
|
|
|
|
|
247 |
|
248 |
-
|
249 |
-
|
|
|
|
|
250 |
|
251 |
with col1:
|
252 |
-
st.
|
253 |
-
date1 = st.date_input("Date of First Scan")
|
254 |
-
files1 = st.file_uploader("Upload First Scans", type=['png', 'jpg', 'jpeg', 'pdf'], accept_multiple_files=True)
|
255 |
|
256 |
with col2:
|
257 |
-
st.
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
images2 = [Image.open(f) for f in files2 if f.type.startswith('image')]
|
264 |
|
265 |
-
if
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
st.markdown("#### Preview Second Timepoint")
|
273 |
-
cols2 = st.columns(len(images2))
|
274 |
-
for idx, (col, img) in enumerate(zip(cols2, images2)):
|
275 |
-
with col:
|
276 |
-
st.image(img, caption=f"Scan {idx + 1}", use_column_width=True)
|
277 |
|
278 |
-
if
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
|
290 |
st.markdown('</div>', unsafe_allow_html=True)
|
291 |
|
|
|
6 |
import PyPDF2
|
7 |
import io
|
8 |
from datetime import datetime
|
9 |
+
import pandas as pd
|
10 |
+
from collections import defaultdict
|
11 |
|
12 |
# Page configuration
|
13 |
st.set_page_config(
|
|
|
88 |
margin: 0.5rem 0;
|
89 |
border-left: 3px solid #03a9f4;
|
90 |
}
|
91 |
+
.timeline-container {
|
92 |
+
margin: 2rem 0;
|
93 |
+
padding: 1rem;
|
94 |
+
background-color: #fff;
|
95 |
+
border-radius: 0.5rem;
|
96 |
+
border: 1px solid #e9ecef;
|
97 |
+
}
|
98 |
+
.timepoint-card {
|
99 |
+
background-color: #f8f9fa;
|
100 |
+
padding: 1rem;
|
101 |
+
margin: 0.5rem 0;
|
102 |
+
border-radius: 0.5rem;
|
103 |
+
border-left: 3px solid #2E86C1;
|
104 |
+
}
|
105 |
</style>
|
106 |
""", unsafe_allow_html=True)
|
107 |
|
108 |
# System prompts
|
109 |
+
CORNEA_ANALYSIS_PROMPT = """You are an expert ophthalmologist specializing in corneal diseases. Analyze these Pentacam scans and patient data with focus on:
|
110 |
|
111 |
+
1. Corneal Parameters Analysis:
|
112 |
+
• Thickness mapping and progression
|
113 |
+
• Topographic changes
|
114 |
+
• Elevation data (anterior and posterior)
|
115 |
+
• Keratoconus indices and classification
|
116 |
|
117 |
+
2. Disease Assessment:
|
118 |
+
• ABCD Keratoconus staging
|
119 |
+
• Fuchs Endothelial Corneal Dystrophy evaluation
|
120 |
+
• Subclinical corneal edema (Sun criteria)
|
121 |
+
• Risk assessment
|
|
|
122 |
|
123 |
+
3. Clinical Interpretation:
|
124 |
+
• Pattern recognition
|
125 |
+
• Disease progression markers
|
126 |
+
• Treatment implications
|
127 |
|
128 |
+
Please provide a detailed clinical assessment."""
|
129 |
|
130 |
+
PROGRESSION_ANALYSIS_PROMPT = """Analyze the progression of corneal parameters across multiple timepoints, focusing on:
|
|
|
|
|
|
|
|
|
131 |
|
132 |
+
1. Temporal Changes:
|
133 |
+
• Progressive changes in corneal thickness
|
134 |
+
• Evolution of topographic patterns
|
135 |
+
• Changes in elevation maps
|
136 |
+
• Progression of keratoconus indices
|
137 |
+
|
138 |
+
2. Rate of Progression:
|
139 |
+
• Quantify changes between timepoints
|
140 |
+
• Identify acceleration or stabilization periods
|
141 |
+
• Compare with expected disease progression
|
142 |
+
|
143 |
+
3. Risk Assessment:
|
144 |
+
• Current status evaluation
|
145 |
+
• Future progression risk
|
146 |
+
• Treatment recommendations
|
147 |
|
148 |
+
4. Timeline Analysis:
|
149 |
+
• Key changes between each timepoint
|
150 |
+
• Overall progression pattern
|
151 |
+
• Critical periods of change
|
152 |
|
153 |
+
Please provide a comprehensive progression analysis with clinical recommendations."""
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
|
155 |
+
def extract_patient_data(uploaded_file):
|
156 |
+
"""Extract and process patient data from uploaded file"""
|
157 |
+
patient_data = {}
|
158 |
+
|
159 |
+
if uploaded_file.type == "application/pdf":
|
160 |
+
pdf_reader = PyPDF2.PdfReader(uploaded_file)
|
161 |
+
text = ""
|
162 |
+
for page in pdf_reader.pages:
|
163 |
+
text += page.extract_text()
|
164 |
+
patient_data['raw_text'] = text
|
165 |
else:
|
166 |
+
# Handle other file types if needed
|
167 |
+
patient_data['raw_text'] = "File type not supported for detailed extraction"
|
168 |
|
169 |
+
return patient_data
|
170 |
+
|
171 |
+
def analyze_timepoint(images, date, patient_data=None):
|
172 |
+
"""Analyze a single timepoint"""
|
173 |
+
prompt = f"{CORNEA_ANALYSIS_PROMPT}\n\nTimepoint: {date}\n"
|
174 |
if patient_data:
|
175 |
prompt += f"\nPatient Information:\n{patient_data}\n"
|
176 |
|
|
|
179 |
response = model.generate_content(content)
|
180 |
return response.text
|
181 |
|
182 |
+
def analyze_progression(timepoints_data):
|
183 |
+
"""Analyze progression across multiple timepoints"""
|
184 |
+
prompt = f"{PROGRESSION_ANALYSIS_PROMPT}\n\n"
|
185 |
+
prompt += "Timepoints for analysis:\n"
|
186 |
|
187 |
+
# Add all timepoints to the prompt
|
188 |
+
all_images = []
|
189 |
+
for date, images in timepoints_data.items():
|
190 |
+
prompt += f"\n- {date}:"
|
191 |
+
all_images.extend(images)
|
192 |
|
193 |
+
prompt += "\n\nPlease analyze the progression across these timepoints:"
|
194 |
+
content = [prompt] + all_images
|
195 |
response = model.generate_content(content)
|
196 |
return response.text
|
197 |
|
|
|
216 |
</div>
|
217 |
""", unsafe_allow_html=True)
|
218 |
|
219 |
+
# Patient Information Section
|
220 |
+
st.markdown("### Patient Information")
|
221 |
+
patient_file = st.file_uploader("Upload Patient Information (PDF/Text)", type=['pdf', 'txt'])
|
222 |
+
patient_data = None
|
223 |
+
if patient_file:
|
224 |
+
patient_data = extract_patient_data(patient_file)
|
225 |
+
with st.expander("View Extracted Patient Information"):
|
226 |
+
st.text(patient_data.get('raw_text', 'No text extracted'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
|
228 |
+
# Scan Analysis Section
|
229 |
+
st.markdown("### Pentacam Scan Analysis")
|
230 |
+
analysis_type = st.radio("Select Analysis Type", ["Single Timepoint", "Multiple Timepoint Progression"])
|
231 |
+
|
232 |
+
if analysis_type == "Single Timepoint":
|
233 |
st.markdown('<div class="upload-section">', unsafe_allow_html=True)
|
234 |
+
scan_date = st.date_input("Scan Date")
|
235 |
+
uploaded_files = st.file_uploader("Upload Pentacam Scans", type=['png', 'jpg', 'jpeg'], accept_multiple_files=True)
|
236 |
|
237 |
+
if uploaded_files:
|
238 |
+
images = []
|
239 |
+
for file in uploaded_files:
|
240 |
+
image = Image.open(file)
|
241 |
+
images.append(image)
|
242 |
|
243 |
+
if images:
|
244 |
+
st.markdown("#### Preview Scans")
|
245 |
+
cols = st.columns(len(images))
|
246 |
+
for idx, (col, img) in enumerate(zip(cols, images)):
|
247 |
+
with col:
|
248 |
+
st.image(img, caption=f"Scan {idx + 1}", use_column_width=True)
|
|
|
|
|
|
|
249 |
|
250 |
+
if st.button("Analyze Scans"):
|
251 |
+
with st.spinner("Analyzing..."):
|
252 |
+
analysis = analyze_timepoint(
|
253 |
+
images,
|
254 |
+
scan_date.strftime("%Y-%m-%d"),
|
255 |
+
patient_data.get('raw_text') if patient_data else None
|
256 |
+
)
|
257 |
+
st.markdown("### Analysis Results")
|
258 |
+
st.markdown('<div class="analysis-container">', unsafe_allow_html=True)
|
259 |
+
st.markdown(analysis)
|
260 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
261 |
+
|
262 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
263 |
+
|
264 |
+
else: # Multiple Timepoint Progression
|
265 |
+
st.markdown('<div class="upload-section">', unsafe_allow_html=True)
|
266 |
+
st.info("Upload scans from multiple timepoints to analyze progression")
|
267 |
+
|
268 |
+
# Create a dynamic form for multiple timepoints
|
269 |
+
timepoints_data = defaultdict(list)
|
270 |
+
num_timepoints = st.number_input("Number of Timepoints", min_value=2, max_value=10, value=3)
|
271 |
|
272 |
+
st.markdown('<div class="timeline-container">', unsafe_allow_html=True)
|
273 |
+
for i in range(num_timepoints):
|
274 |
+
st.markdown(f'<div class="timepoint-card">', unsafe_allow_html=True)
|
275 |
+
col1, col2 = st.columns([1, 3])
|
276 |
|
277 |
with col1:
|
278 |
+
date = st.date_input(f"Date - Timepoint {i+1}", key=f"date_{i}")
|
|
|
|
|
279 |
|
280 |
with col2:
|
281 |
+
files = st.file_uploader(
|
282 |
+
f"Upload Scans - Timepoint {i+1}",
|
283 |
+
type=['png', 'jpg', 'jpeg'],
|
284 |
+
accept_multiple_files=True,
|
285 |
+
key=f"files_{i}"
|
286 |
+
)
|
|
|
287 |
|
288 |
+
if files:
|
289 |
+
images = []
|
290 |
+
for file in files:
|
291 |
+
image = Image.open(file)
|
292 |
+
images.append(image)
|
293 |
+
timepoints_data[date.strftime("%Y-%m-%d")].extend(images)
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
|
295 |
+
if images:
|
296 |
+
cols = st.columns(len(images))
|
297 |
+
for idx, (col, img) in enumerate(zip(cols, images)):
|
298 |
+
with col:
|
299 |
+
st.image(img, caption=f"Scan {idx + 1}", use_column_width=True)
|
300 |
+
|
301 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
302 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
303 |
+
|
304 |
+
if timepoints_data and len(timepoints_data) >= 2:
|
305 |
+
if st.button("Analyze Progression"):
|
306 |
+
with st.spinner("Analyzing progression across timepoints..."):
|
307 |
+
progression_analysis = analyze_progression(timepoints_data)
|
308 |
+
st.markdown("### Progression Analysis Results")
|
309 |
+
st.markdown('<div class="analysis-container">', unsafe_allow_html=True)
|
310 |
+
st.markdown(progression_analysis)
|
311 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
312 |
|
313 |
st.markdown('</div>', unsafe_allow_html=True)
|
314 |
|