Spaces:
Running
on
Zero
Running
on
Zero
luigi12345
commited on
Commit
•
1f2f701
1
Parent(s):
06b73eb
Update app.py
Browse files
app.py
CHANGED
@@ -60,6 +60,10 @@ class TempImageFile:
|
|
60 |
try:
|
61 |
if self.path and os.path.exists(self.path):
|
62 |
os.remove(self.path)
|
|
|
|
|
|
|
|
|
63 |
except Exception as e:
|
64 |
print(f"Error cleaning up temporary file: {e}")
|
65 |
finally:
|
@@ -160,130 +164,15 @@ MEASUREMENT_RANGES = {
|
|
160 |
"astigmatism": (0.0, 6.0) # D
|
161 |
}
|
162 |
|
163 |
-
|
164 |
-
"""
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
axial_length = measurements.get('axial_length', 23.5)
|
169 |
-
astigmatism = measurements.get('astigmatism', 0.0)
|
170 |
-
|
171 |
-
# Adjust settings based on measurements
|
172 |
-
if axial_length < 22.0:
|
173 |
-
settings.update(LENS_MODELS['short_eye'])
|
174 |
-
elif astigmatism > 2.0:
|
175 |
-
settings.update(LENS_MODELS['toric'])
|
176 |
-
|
177 |
-
return settings
|
178 |
-
|
179 |
-
DESCRIPTION = """
|
180 |
-
# IOL Report Analyzer
|
181 |
-
Upload an IOL screenshot to get a detailed analysis of measurements and recommendations.
|
182 |
-
|
183 |
-
## Instructions:
|
184 |
-
1. Upload your IOL report image
|
185 |
-
2. Click Submit to generate analysis
|
186 |
-
3. Review the detailed output below
|
187 |
-
"""
|
188 |
-
|
189 |
-
SYSTEM_PROMPT = """Please analyze the uploaded IOL report image carefully and provide a detailed analysis using these settings:
|
190 |
-
Manufacturer: {manufacturer}
|
191 |
-
Lens Model: {lens_model}
|
192 |
-
A-Constant: {a_constant}
|
193 |
-
Target Refraction: {target_refraction} D
|
194 |
-
Surgeon Factor: {surgeon_factor}
|
195 |
-
Personalization Factor: {personalization_factor}
|
196 |
-
AC Constant: {anterior_chamber_constant}
|
197 |
-
Vertex Distance: {vertex_distance} mm
|
198 |
-
|
199 |
-
Please provide analysis in the following format:
|
200 |
-
|
201 |
-
1. BIOMETRY MEASUREMENTS:
|
202 |
-
- Axial Length (AL): [value] mm
|
203 |
-
- Keratometry: K1: [value] D @ [axis]°, K2: [value] D @ [axis]°
|
204 |
-
- Mean K: [value] D
|
205 |
-
- Astigmatism: [value] D
|
206 |
-
- Anterior Chamber Depth (ACD): [value] mm
|
207 |
-
- Lens Thickness (LT): [value] mm
|
208 |
-
- White-to-White Distance (WTW): [value] mm
|
209 |
-
- Central Corneal Thickness (CCT): [value] μm
|
210 |
-
|
211 |
-
2. IOL POWER CALCULATIONS:
|
212 |
-
A. Barrett Universal II Formula:
|
213 |
-
- Recommended IOL Power: [value] D
|
214 |
-
- Predicted Refraction: [value] D
|
215 |
-
- Target Refraction: [value] D
|
216 |
-
|
217 |
-
B. Other Formula Calculations:
|
218 |
-
- Cooke K6: [value] D
|
219 |
-
- EVO Formula: [value] D
|
220 |
-
- Hill-RBF Method: [value] D
|
221 |
-
- Hoffer QST: [value] D
|
222 |
-
- Kane Formula: [value] D
|
223 |
-
- PEARL-DGS: [value] D
|
224 |
-
|
225 |
-
3. ANALYSIS:
|
226 |
-
- Optimal IOL Power Range: [range] D
|
227 |
-
- Recommended A-Constant: [value]
|
228 |
-
- Risk Factors: [list any concerns]
|
229 |
-
|
230 |
-
4. RECOMMENDATIONS:
|
231 |
-
- Primary IOL Choice: [power] D ([manufacturer/model])
|
232 |
-
- Alternative Options: [list alternatives]
|
233 |
-
- Special Considerations: [note any relevant factors]
|
234 |
-
|
235 |
-
Please ensure all measurements are accurately extracted and calculations are precise. If any values are missing or unclear, please indicate this in your response."""
|
236 |
-
|
237 |
-
css = """
|
238 |
-
#component-0 {
|
239 |
-
max-width: 1200px;
|
240 |
-
margin: auto;
|
241 |
-
padding: 20px;
|
242 |
-
}
|
243 |
-
|
244 |
-
.container {
|
245 |
-
border: 1px solid #e0e0e0;
|
246 |
-
border-radius: 10px;
|
247 |
-
padding: 20px;
|
248 |
-
margin-bottom: 20px;
|
249 |
-
background: white;
|
250 |
-
}
|
251 |
-
|
252 |
-
.output-box {
|
253 |
-
height: 600px;
|
254 |
-
overflow: auto;
|
255 |
-
font-family: 'Source Code Pro', monospace;
|
256 |
-
line-height: 1.5;
|
257 |
-
padding: 15px;
|
258 |
-
background: #f8f9fa;
|
259 |
-
border-radius: 5px;
|
260 |
}
|
261 |
|
262 |
-
.title {
|
263 |
-
font-size: 24px;
|
264 |
-
font-weight: bold;
|
265 |
-
margin-bottom: 20px;
|
266 |
-
color: #2c3e50;
|
267 |
-
}
|
268 |
-
|
269 |
-
.submit-btn {
|
270 |
-
background: #2ecc71 !important;
|
271 |
-
border: none !important;
|
272 |
-
}
|
273 |
-
|
274 |
-
.submit-btn:hover {
|
275 |
-
background: #27ae60 !important;
|
276 |
-
}
|
277 |
-
"""
|
278 |
-
|
279 |
def format_error(error_type, message):
|
280 |
-
|
281 |
-
"ValueError": "⚠️ Invalid Input",
|
282 |
-
"RuntimeError": "🔧 Processing Error",
|
283 |
-
"torch.cuda.OutOfMemoryError": "💾 Memory Error",
|
284 |
-
"Exception": "❌ Unexpected Error"
|
285 |
-
}
|
286 |
-
return f"{error_messages.get(error_type.__name__, '❌ Error')}: {message}"
|
287 |
|
288 |
def validate_image(image_array):
|
289 |
if image_array is None:
|
@@ -337,16 +226,6 @@ def normalize_image_size(image, max_size=1024):
|
|
337 |
image = image.resize(new_size, Image.LANCZOS)
|
338 |
return image
|
339 |
|
340 |
-
def validate_examples():
|
341 |
-
example_files = ["example1.png", "example2.png"]
|
342 |
-
valid_examples = []
|
343 |
-
|
344 |
-
for file in example_files:
|
345 |
-
if os.path.exists(file):
|
346 |
-
valid_examples.append([file])
|
347 |
-
|
348 |
-
return valid_examples if valid_examples else None
|
349 |
-
|
350 |
def extract_measurements(image_array):
|
351 |
"""
|
352 |
Extract measurements from the image using OCR or vision model
|
@@ -466,280 +345,250 @@ EXTRACTED MEASUREMENTS:
|
|
466 |
"""
|
467 |
|
468 |
def generate_analysis_prompt(measurements, settings, warnings=None, risk_factors=None, quality_score=None):
|
469 |
-
"""Enhanced prompt generation
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
- Identify potential complications
|
517 |
-
- Special considerations
|
518 |
-
|
519 |
-
4. RECOMMENDATIONS
|
520 |
-
- Specific IOL choice and power
|
521 |
-
- Alternative options
|
522 |
-
- Surgical approach suggestions
|
523 |
-
|
524 |
-
Please be thorough and highlight any concerns or special considerations."""
|
525 |
-
|
526 |
return prompt
|
527 |
|
528 |
def validate_settings(settings):
|
|
|
529 |
if not isinstance(settings, dict):
|
530 |
-
|
531 |
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
|
|
|
|
|
|
|
|
|
|
542 |
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
if not isinstance(settings[field], expected_type):
|
547 |
-
raise ValueError(f"Invalid type for {field}: expected {expected_type}, got {type(settings[field])}")
|
548 |
-
|
549 |
-
# Validate numeric ranges
|
550 |
-
if isinstance(settings[field], (int, float)):
|
551 |
-
if field == 'a_constant' and not 115 <= settings[field] <= 122:
|
552 |
-
raise ValueError("A-constant must be between 115 and 122")
|
553 |
-
elif field == 'target_refraction' and not -10 <= settings[field] <= 10:
|
554 |
-
raise ValueError("Target refraction must be between -10 and 10")
|
555 |
-
|
556 |
-
@spaces.GPU(duration=120)
|
557 |
def run_example(image, model_id="Qwen/Qwen2-VL-7B-Instruct", settings=None, progress=gr.Progress()):
|
|
|
|
|
|
|
|
|
558 |
try:
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
# Enhanced validation flow with quality feedback
|
563 |
-
validate_image(image)
|
564 |
-
quality_score, quality_feedback = validate_image_quality(image)
|
565 |
-
|
566 |
-
if quality_score < 0.3:
|
567 |
-
return f"⚠️ Image quality too low for reliable analysis: {quality_feedback}"
|
568 |
-
|
569 |
-
progress(0.2, "Extracting measurements...")
|
570 |
-
measurements = extract_measurements(image)
|
571 |
-
|
572 |
-
# Add error handling for measurement extraction
|
573 |
-
if not measurements:
|
574 |
-
return "❌ Failed to extract measurements from image. Please ensure the image contains clear IOL measurements."
|
575 |
|
576 |
-
|
|
|
577 |
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
warnings=warnings,
|
585 |
-
risk_factors=risk_factors,
|
586 |
-
quality_score=quality_score
|
587 |
-
)
|
588 |
-
|
589 |
-
progress(0.4, "Loading model...")
|
590 |
-
model = model_manager.get_model(model_id)
|
591 |
-
processor = model_manager.get_processor(model_id)
|
592 |
|
593 |
-
progress(0.
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
|
|
|
|
|
|
|
|
|
|
599 |
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
|
|
|
|
|
|
|
|
608 |
)
|
|
|
|
|
609 |
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
615 |
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
620 |
except Exception as e:
|
621 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
622 |
|
623 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
624 |
gr.Markdown(DESCRIPTION)
|
625 |
|
626 |
with gr.Row():
|
627 |
-
with gr.Column(
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
634 |
)
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
manufacturer = gr.Dropdown(
|
639 |
-
choices=["Alcon", "Johnson & Johnson", "Zeiss", "Bausch & Lomb"],
|
640 |
-
value=DEFAULT_SETTINGS["manufacturer"],
|
641 |
-
label="Manufacturer"
|
642 |
-
)
|
643 |
-
lens_model = gr.Textbox(
|
644 |
-
value=DEFAULT_SETTINGS["lens_model"],
|
645 |
-
label="Lens Model"
|
646 |
-
)
|
647 |
-
a_constant = gr.Number(
|
648 |
-
value=DEFAULT_SETTINGS["a_constant"],
|
649 |
-
label="A-Constant"
|
650 |
-
)
|
651 |
-
target_refraction = gr.Number(
|
652 |
-
value=DEFAULT_SETTINGS["target_refraction"],
|
653 |
-
label="Target Refraction (D)"
|
654 |
-
)
|
655 |
-
with gr.Row():
|
656 |
-
surgeon_factor = gr.Number(
|
657 |
-
value=DEFAULT_SETTINGS["surgeon_factor"],
|
658 |
-
label="Surgeon Factor"
|
659 |
-
)
|
660 |
-
personalization_factor = gr.Number(
|
661 |
-
value=DEFAULT_SETTINGS["personalization_factor"],
|
662 |
-
label="Personalization Factor"
|
663 |
-
)
|
664 |
-
with gr.Row():
|
665 |
-
ac_constant = gr.Number(
|
666 |
-
value=DEFAULT_SETTINGS["anterior_chamber_constant"],
|
667 |
-
label="AC Constant"
|
668 |
-
)
|
669 |
-
vertex_distance = gr.Number(
|
670 |
-
value=DEFAULT_SETTINGS["vertex_distance"],
|
671 |
-
label="Vertex Distance (mm)"
|
672 |
-
)
|
673 |
-
|
674 |
-
model_selector = gr.Dropdown(
|
675 |
-
choices=model_manager.available_models,
|
676 |
-
value="Qwen/Qwen2-VL-7B-Instruct",
|
677 |
-
label="Model Selection",
|
678 |
-
interactive=True
|
679 |
)
|
680 |
-
|
681 |
-
"
|
682 |
-
|
683 |
-
elem_classes="submit-btn"
|
684 |
)
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
gr.Markdown("### Analysis Results")
|
689 |
-
output_text = gr.Textbox(
|
690 |
-
label="IOL Analysis",
|
691 |
-
lines=25,
|
692 |
-
show_copy_button=True,
|
693 |
-
elem_classes="output-box"
|
694 |
)
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
gr.
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
# Event handler
|
708 |
submit_btn.click(
|
709 |
fn=run_example,
|
710 |
inputs=[
|
711 |
input_img,
|
712 |
-
|
713 |
-
gr.State(
|
714 |
-
"manufacturer": manufacturer.value,
|
715 |
-
"lens_model": lens_model.value,
|
716 |
-
"a_constant": a_constant.value,
|
717 |
-
"target_refraction": target_refraction.value,
|
718 |
-
"surgeon_factor": surgeon_factor.value,
|
719 |
-
"personalization_factor": personalization_factor.value,
|
720 |
-
"anterior_chamber_constant": ac_constant.value,
|
721 |
-
"vertex_distance": vertex_distance.value
|
722 |
-
})
|
723 |
],
|
724 |
-
outputs=output_text
|
725 |
-
api_name="analyze"
|
726 |
)
|
727 |
|
728 |
-
#
|
729 |
-
demo.queue(
|
730 |
-
|
731 |
-
api_open=False,
|
732 |
-
concurrency_count=1, # Limit concurrent executions
|
733 |
-
status_update_rate=0.1, # More frequent updates
|
734 |
-
timeout=600, # 10 minute timeout
|
735 |
-
default_concurrency_limit=1
|
736 |
-
)
|
737 |
-
|
738 |
-
# Launch with queue
|
739 |
-
demo.launch(
|
740 |
-
debug=True,
|
741 |
-
show_error=True,
|
742 |
-
share=False,
|
743 |
-
server_name="0.0.0.0",
|
744 |
-
server_port=7860,
|
745 |
-
)
|
|
|
60 |
try:
|
61 |
if self.path and os.path.exists(self.path):
|
62 |
os.remove(self.path)
|
63 |
+
# Also remove parent directory if empty
|
64 |
+
parent_dir = os.path.dirname(self.path)
|
65 |
+
if os.path.exists(parent_dir) and not os.listdir(parent_dir):
|
66 |
+
os.rmdir(parent_dir)
|
67 |
except Exception as e:
|
68 |
print(f"Error cleaning up temporary file: {e}")
|
69 |
finally:
|
|
|
164 |
"astigmatism": (0.0, 6.0) # D
|
165 |
}
|
166 |
|
167 |
+
ERROR_MESSAGES = {
|
168 |
+
"ValueError": "⚠️ Invalid Input",
|
169 |
+
"RuntimeError": "🔧 Processing Error",
|
170 |
+
"torch.cuda.OutOfMemoryError": "💾 Memory Error",
|
171 |
+
"Exception": "❌ Unexpected Error"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
}
|
173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
def format_error(error_type, message):
|
175 |
+
return f"{ERROR_MESSAGES.get(error_type.__name__, '❌ Error')}: {message}"
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
|
177 |
def validate_image(image_array):
|
178 |
if image_array is None:
|
|
|
226 |
image = image.resize(new_size, Image.LANCZOS)
|
227 |
return image
|
228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
def extract_measurements(image_array):
|
230 |
"""
|
231 |
Extract measurements from the image using OCR or vision model
|
|
|
345 |
"""
|
346 |
|
347 |
def generate_analysis_prompt(measurements, settings, warnings=None, risk_factors=None, quality_score=None):
|
348 |
+
"""Enhanced prompt generation for IOL Master and similar biometry reports"""
|
349 |
+
|
350 |
+
# Detect case type
|
351 |
+
case_types = detect_case_type(measurements)
|
352 |
+
|
353 |
+
# Build comprehensive analysis request
|
354 |
+
prompt = f"""Analyze this IOL biometry report image.
|
355 |
+
|
356 |
+
TASK:
|
357 |
+
1. Verify all visible measurements
|
358 |
+
2. Confirm measurement accuracy and consistency
|
359 |
+
3. Provide IOL power calculation using:
|
360 |
+
- SRK/T formula
|
361 |
+
- Barrett Universal II
|
362 |
+
- Hill-RBF if applicable
|
363 |
+
- Other formulas as appropriate for this case
|
364 |
+
|
365 |
+
CONSIDERATIONS:
|
366 |
+
1. Image Quality Assessment
|
367 |
+
2. Measurement Validation
|
368 |
+
3. Formula Selection Rationale
|
369 |
+
4. Risk Factor Analysis
|
370 |
+
|
371 |
+
{format_measurements_text(measurements)}
|
372 |
+
|
373 |
+
SELECTED IOL PARAMETERS:
|
374 |
+
• Manufacturer: {settings.get('manufacturer')}
|
375 |
+
• Model: {settings.get('lens_model')}
|
376 |
+
• A-Constant: {settings.get('a_constant')}
|
377 |
+
• Target: {settings.get('target_refraction', 0)} D
|
378 |
+
|
379 |
+
Please provide:
|
380 |
+
1. Measurement verification
|
381 |
+
2. IOL power calculations with multiple formulas
|
382 |
+
3. Specific recommendations for:
|
383 |
+
- Optimal IOL power
|
384 |
+
- Formula selection rationale
|
385 |
+
- Surgical approach
|
386 |
+
4. Risk assessment and special considerations
|
387 |
+
5. Alternative options if needed"""
|
388 |
+
|
389 |
+
# Add case-specific guidance if detected
|
390 |
+
if case_types:
|
391 |
+
prompt += "\n\nSpecial Considerations:"
|
392 |
+
for _, description in case_types:
|
393 |
+
prompt += f"\n• {description}"
|
394 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
395 |
return prompt
|
396 |
|
397 |
def validate_settings(settings):
|
398 |
+
"""Enhanced settings validation with better error handling"""
|
399 |
if not isinstance(settings, dict):
|
400 |
+
return DEFAULT_SETTINGS # Fallback to defaults instead of raising error
|
401 |
|
402 |
+
validated_settings = DEFAULT_SETTINGS.copy()
|
403 |
+
for key, value in settings.items():
|
404 |
+
if key in validated_settings:
|
405 |
+
try:
|
406 |
+
# Type conversion and validation
|
407 |
+
if isinstance(DEFAULT_SETTINGS[key], (int, float)):
|
408 |
+
value = float(value)
|
409 |
+
# Validate ranges
|
410 |
+
if key == 'a_constant' and not 115 <= value <= 122:
|
411 |
+
continue # Skip invalid value, keep default
|
412 |
+
if key == 'target_refraction' and not -10 <= value <= 10:
|
413 |
+
continue # Skip invalid value, keep default
|
414 |
+
validated_settings[key] = value
|
415 |
+
except (ValueError, TypeError):
|
416 |
+
continue # Keep default value if conversion fails
|
417 |
|
418 |
+
return validated_settings
|
419 |
+
|
420 |
+
@spaces.GPU
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
421 |
def run_example(image, model_id="Qwen/Qwen2-VL-7B-Instruct", settings=None, progress=gr.Progress()):
|
422 |
+
"""Enhanced error handling and resource management"""
|
423 |
+
cleanup_temp_files()
|
424 |
+
model = None
|
425 |
+
|
426 |
try:
|
427 |
+
progress(0, desc="Validating input...")
|
428 |
+
if image is None:
|
429 |
+
return "Error: No image provided"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
430 |
|
431 |
+
image = validate_image(image)
|
432 |
+
settings = validate_settings(settings or {})
|
433 |
|
434 |
+
progress(0.2, desc="Loading model...")
|
435 |
+
try:
|
436 |
+
model = model_manager.get_model(model_id)
|
437 |
+
processor = model_manager.get_processor(model_id)
|
438 |
+
except Exception as e:
|
439 |
+
return f"Error loading model: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
|
441 |
+
progress(0.4, desc="Processing image...")
|
442 |
+
with TempImageFile(image) as image_path:
|
443 |
+
if not image_path:
|
444 |
+
return "Error: Failed to save temporary image"
|
445 |
+
|
446 |
+
# Convert and normalize image
|
447 |
+
try:
|
448 |
+
image = Image.fromarray(image).convert("RGB")
|
449 |
+
image = normalize_image_size(image)
|
450 |
+
except Exception as e:
|
451 |
+
return f"Error processing image: {str(e)}"
|
452 |
|
453 |
+
# Generate analysis prompt
|
454 |
+
try:
|
455 |
+
measurements = extract_measurements(image)
|
456 |
+
warnings, risk_factors = validate_measurements(measurements)
|
457 |
+
quality_score, quality_feedback = validate_image_quality(image)
|
458 |
+
|
459 |
+
prompt = generate_analysis_prompt(
|
460 |
+
measurements=measurements,
|
461 |
+
settings=settings,
|
462 |
+
warnings=warnings,
|
463 |
+
risk_factors=risk_factors,
|
464 |
+
quality_score=quality_score
|
465 |
)
|
466 |
+
except Exception as e:
|
467 |
+
return f"Error analyzing image: {str(e)}"
|
468 |
|
469 |
+
# Prepare model inputs
|
470 |
+
try:
|
471 |
+
messages = [{
|
472 |
+
"role": "user",
|
473 |
+
"content": [
|
474 |
+
{"type": "image", "image": image_path},
|
475 |
+
{"type": "text", "text": prompt}
|
476 |
+
]
|
477 |
+
}]
|
478 |
+
|
479 |
+
text = processor.apply_chat_template(
|
480 |
+
messages,
|
481 |
+
tokenize=False,
|
482 |
+
add_generation_prompt=True
|
483 |
+
)
|
484 |
+
|
485 |
+
image_inputs, video_inputs = process_vision_info(messages)
|
486 |
+
inputs = processor(
|
487 |
+
text=[text],
|
488 |
+
images=image_inputs,
|
489 |
+
videos=video_inputs,
|
490 |
+
padding=True,
|
491 |
+
return_tensors="pt"
|
492 |
+
).to(model.device)
|
493 |
+
except Exception as e:
|
494 |
+
return f"Error preparing model inputs: {str(e)}"
|
495 |
|
496 |
+
# Model inference with proper resource management
|
497 |
+
try:
|
498 |
+
progress(0.6, desc="Generating analysis...")
|
499 |
+
with torch.inference_mode():
|
500 |
+
generated_ids = model.generate(
|
501 |
+
**inputs,
|
502 |
+
max_new_tokens=1024,
|
503 |
+
do_sample=True,
|
504 |
+
temperature=0.7,
|
505 |
+
top_p=0.9
|
506 |
+
)
|
507 |
+
|
508 |
+
generated_ids_trimmed = [
|
509 |
+
out_ids[len(in_ids):]
|
510 |
+
for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
|
511 |
+
]
|
512 |
+
|
513 |
+
output_text = processor.batch_decode(
|
514 |
+
generated_ids_trimmed,
|
515 |
+
skip_special_tokens=True,
|
516 |
+
clean_up_tokenization_spaces=False
|
517 |
+
)[0]
|
518 |
+
|
519 |
+
progress(1.0, desc="Complete!")
|
520 |
+
return output_text
|
521 |
+
|
522 |
+
except torch.cuda.OutOfMemoryError:
|
523 |
+
return "Error: GPU memory exhausted. Please try with a smaller image."
|
524 |
+
except Exception as e:
|
525 |
+
return f"Error during analysis: {str(e)}"
|
526 |
+
|
527 |
except Exception as e:
|
528 |
+
return f"Unexpected error: {str(e)}"
|
529 |
+
|
530 |
+
finally:
|
531 |
+
# Ensure proper cleanup
|
532 |
+
try:
|
533 |
+
if model is not None:
|
534 |
+
model_manager.cleanup_unused_models(keep_model_id=model_id)
|
535 |
+
torch.cuda.empty_cache()
|
536 |
+
gc.collect()
|
537 |
+
except Exception as e:
|
538 |
+
print(f"Cleanup error: {e}")
|
539 |
|
540 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
541 |
gr.Markdown(DESCRIPTION)
|
542 |
|
543 |
with gr.Row():
|
544 |
+
with gr.Column():
|
545 |
+
# Image upload
|
546 |
+
input_img = gr.Image(
|
547 |
+
label="IOL Report Image",
|
548 |
+
type="numpy"
|
549 |
+
)
|
550 |
+
|
551 |
+
# Essential settings only
|
552 |
+
with gr.Accordion("Settings", open=False):
|
553 |
+
manufacturer = gr.Dropdown(
|
554 |
+
choices=["Alcon", "Johnson & Johnson", "Zeiss", "Bausch & Lomb"],
|
555 |
+
value=DEFAULT_SETTINGS["manufacturer"],
|
556 |
+
label="Manufacturer"
|
557 |
)
|
558 |
+
lens_model = gr.Textbox(
|
559 |
+
value=DEFAULT_SETTINGS["lens_model"],
|
560 |
+
label="Lens Model"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
561 |
)
|
562 |
+
a_constant = gr.Number(
|
563 |
+
value=DEFAULT_SETTINGS["a_constant"],
|
564 |
+
label="A-Constant"
|
|
|
565 |
)
|
566 |
+
target_refraction = gr.Number(
|
567 |
+
value=DEFAULT_SETTINGS["target_refraction"],
|
568 |
+
label="Target Refraction (D)"
|
|
|
|
|
|
|
|
|
|
|
|
|
569 |
)
|
570 |
+
|
571 |
+
submit_btn = gr.Button("Analyze Report", variant="primary")
|
572 |
+
|
573 |
+
# Output column
|
574 |
+
with gr.Column():
|
575 |
+
output_text = gr.Textbox(
|
576 |
+
label="Analysis Results",
|
577 |
+
lines=20,
|
578 |
+
show_copy_button=True
|
579 |
+
)
|
580 |
+
|
581 |
+
# Simplified event handler
|
|
|
582 |
submit_btn.click(
|
583 |
fn=run_example,
|
584 |
inputs=[
|
585 |
input_img,
|
586 |
+
gr.State("Qwen/Qwen2-VL-7B-Instruct"),
|
587 |
+
gr.State(DEFAULT_SETTINGS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
588 |
],
|
589 |
+
outputs=output_text
|
|
|
590 |
)
|
591 |
|
592 |
+
# Simplified launch configuration
|
593 |
+
demo.queue(concurrency_count=1)
|
594 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|