luigi12345 commited on
Commit
115ff6c
1 Parent(s): cff8897

After new EVo

Browse files
Files changed (2) hide show
  1. app.py +196 -161
  2. documents/EVO.html +122 -154
app.py CHANGED
@@ -117,30 +117,49 @@ class IOLCalculator:
117
  @staticmethod
118
  def srkt_formula(al, k1, k2, a_const, target_ref=0.0):
119
  try:
120
- k_avg = (k1 + k2) / 2; r = 337.5 / k_avg; h = r - math.sqrt(r**2 - (0.0725**2))
121
- acd = 0.62467 * r - 6.8; rt = 0.65696 - 0.02029 * al; elp = h + acd - rt
 
 
 
 
122
  v = 1336.3 / (337.5 / k_avg)
123
- iol_power = (1.336 - 1.336) / (0.001 * (al - elp - 0.1)) + (1336.3 - v) / (v * (al - elp - 0.1))
124
  return round(iol_power + (a_const - 118.4) - (target_ref * 1.458), 2)
125
- except: return None
 
126
 
127
  @staticmethod
128
  def barrett_universal_2(al, k1, k2, acd=None, lcf=1.67, target_ref=0.0):
129
  try:
130
- k_avg = (k1 + k2) / 2; r = 337.5 / k_avg
131
- acd = 3.0 + (0.1 * (al - 23.5)) + (0.05 * (k_avg - 43.5)) + (0.1 * (lcf - 1.67)) if acd is None else acd
132
- lfa = lcf * (al / 23.5) * (1 + 0.02 * abs(al - 23.5)) * (0.98 if al > 26 else 1.02 if al < 22 else 1)
133
- elp = (lfa * acd + 0.1 * k_avg - 3.4) * (0.97 if k_avg > 46 else 1.03 if k_avg < 42 else 1)
134
- iol_power = ((1.336 * 1000 / (al - elp - (0.65696 - 0.02029 * al))) - ((1.3375 - 1) / (r / 1000)) - (target_ref * 1.458)) * (0.98 if al > 25 else 1.02 if al < 22 else 1)
 
 
 
 
 
 
 
 
 
135
  return round(iol_power, 2)
136
- except: return None
 
137
 
138
  @staticmethod
139
  def haigis_formula(al, acd, k_avg, a0=0.87, a1=0.2, a2=0.4, target_ref=0.0):
140
  try:
 
 
141
  elp = a0 + (a1 * acd) + (a2 * al)
142
- return round((((1.336 * (al - elp)) - (1.336 * elp)) / (elp * (al - elp))) * 1000 - (target_ref * 1.458), 2)
143
- except: return None
 
 
144
 
145
  @staticmethod
146
  def holladay1_formula(al, k_avg, a_const, target_ref=0.0):
@@ -162,60 +181,138 @@ class IOLCalculator:
162
 
163
  @staticmethod
164
  def evo_formula(al, k1, k2, acd, lt, target_ref=0.0):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  try:
166
- avg_k = (k1 + k2) / 2
167
- k_adj = 0.97 if avg_k > 46 else 1.03 if avg_k < 42 else 1.0
168
- al_adj = 0.97 if al > 26 else 1.03 if al < 22 else 1.0
169
- acd_adj = 0.95 if al > 25 else 1.05 if al < 22 else 1.0
170
- return round((al * 0.64 * al_adj) + (avg_k * 0.87 * k_adj) - (acd * 0.45 * acd_adj) - (target_ref * 1.458) + (lt * 0.20), 2)
171
- except: return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  @staticmethod
174
  def kane_formula(al, k1, k2, acd, lt, gender='neutral', target_ref=0.0):
175
  try:
176
  avg_k = (k1 + k2) / 2
177
  gender_factor = {'male': 1.12, 'female': 1.06, 'neutral': 1.09}.get(gender.lower(), 1.09)
 
 
178
  al_adj = 0.96 if al > 25 else 1.04 if al < 22 else 1.0
179
  k_adj = 0.98 if avg_k > 46 else 1.02 if avg_k < 42 else 1.0
180
  acd_adj = 0.95 if acd > 3.5 else 1.05 if acd < 2.8 else 1.0
181
- return round((al * 0.67 * al_adj) + (avg_k * 0.84 * k_adj * gender_factor) - (acd * 0.42 * acd_adj) - (lt * 0.12) - (target_ref * 1.458), 2)
182
- except: return None
183
-
184
- def validate_measurements(eye_data):
185
- warnings = []
186
- if eye_data.get('axial_length') and not 20 <= eye_data['axial_length'] <= 30: warnings.append("Axial length outside normal range")
187
- if eye_data.get('k1') and eye_data.get('k2'):
188
- if not 39 <= eye_data['k1'] <= 48: warnings.append("K1 reading outside normal range")
189
- if not 39 <= eye_data['k2'] <= 48: warnings.append("K2 reading outside normal range")
190
- return warnings
191
 
192
  def calculate_iol_powers(eye_data, settings):
193
- if not all(eye_data.get(field) for field in ['axial_length', 'k1', 'k2']): return {"error": "Missing required measurements"}
194
- calculator = IOLCalculator(); results = {"measurements": eye_data, "calculations": {}, "recommendations": {"warnings": []}}; calculations = {}
195
- lcf = calculator.get_barrett_lcf(settings['a_constant']); k_avg = (eye_data['k1'] + eye_data['k2']) / 2
196
-
197
- for formula in [('barrett', lambda: calculator.barrett_universal_2(eye_data['axial_length'], eye_data['k1'], eye_data['k2'], acd=eye_data.get('acd'), lcf=lcf, target_ref=settings['target_refraction'])),
198
- ('haigis', lambda: calculator.haigis_formula(eye_data['axial_length'], eye_data['acd'], k_avg, target_ref=settings['target_refraction']) if eye_data.get('acd') else None),
199
- ('holladay1', lambda: calculator.holladay1_formula(eye_data['axial_length'], k_avg, settings['a_constant'], settings['target_refraction'])),
200
- ('srkt', lambda: calculator.srkt_formula(eye_data['axial_length'], eye_data['k1'], eye_data['k2'], settings['a_constant'], settings['target_refraction'])),
201
- ('evo', lambda: calculator.evo_formula(eye_data['axial_length'], eye_data['k1'], eye_data['k2'], eye_data['acd'], eye_data['lens_thickness'], settings['target_refraction']) if all(eye_data.get(f) for f in ['axial_length', 'k1', 'k2', 'acd', 'lens_thickness']) else None),
202
- ('kane', lambda: calculator.kane_formula(eye_data['axial_length'], eye_data['k1'], eye_data['k2'], eye_data['acd'], eye_data['lens_thickness'], 'neutral', settings['target_refraction']) if all(eye_data.get(f) for f in ['axial_length', 'k1', 'k2', 'acd', 'lens_thickness']) else None)]:
203
- result = formula[1]()
204
- if result is not None: calculations[formula[0]] = result
205
-
206
- results['calculations'] = calculations
207
- valid_powers = [calculations['barrett'] * 1.2 if 'barrett' in calculations else None] + [p for k, p in calculations.items() if p is not None and k != 'barrett']
208
- valid_powers = [p for p in valid_powers if p is not None]
209
- if valid_powers: results['recommendations']['suggested_power'] = round(sum(valid_powers) / len(valid_powers), 2)
210
- results['recommendations']['warnings'].extend(validate_measurements(eye_data))
211
- return results
 
 
212
 
213
  def ensure_temp_directory():
214
  """Create temp directory if it doesn't exist"""
215
  os.makedirs("temp", exist_ok=True)
216
 
217
  @spaces.GPU
218
- def check_environment(): print(f"PyTorch version: {torch.__version__}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  @lru_cache(maxsize=1)
221
  def get_model(model_id):
@@ -255,52 +352,48 @@ def generate_extraction_prompt():
255
  "acd": float,
256
  "lens_thickness": float,
257
  "white_to_white": float,
258
- "pupil_size": float
 
 
259
  },
260
  "left_eye": {
261
- // same structure as right_eye
 
 
 
 
 
 
 
 
262
  }
263
  }"""
264
 
265
  def format_eye_data(measurements, results):
266
- """Format eye measurements and calculations for report"""
267
- if not measurements or not isinstance(measurements, dict) or not results:
268
  return "No data available", "No calculations available", ""
269
 
 
270
  meas_text = "| Parameter | Value | Unit |\n|-----------|--------|------|\n"
271
  for key, (label, unit) in expected_measurements.items():
272
  if key in measurements:
273
- # Ensure the measurement is a number and not an array
274
  value = measurements[key]
275
- if isinstance(value, np.ndarray):
276
- # Handle numpy arrays properly
277
- if value.size == 0:
278
- continue
279
- elif value.size == 1:
280
- value = float(value.item())
281
- else:
282
- value = float(np.mean(value))
283
  meas_text += f"| {label} | {value:.2f} | {unit} |\n"
284
 
 
285
  calc_text = "| Formula | Power |\n|----------|--------|\n"
286
  if 'calculations' in results:
287
  for formula, power in results['calculations'].items():
288
  calc_text += f"| {formula.title()} | {power} D |\n"
289
 
 
290
  warnings = ""
291
  if 'recommendations' in results and results['recommendations'].get('warnings'):
292
  warnings = "\n### ⚠️ Warnings\n" + "\n".join(f"- {w}" for w in results['recommendations']['warnings'])
293
 
294
  return meas_text, calc_text, warnings
295
 
296
- def cleanup_resources(image_path=None):
297
- if image_path and os.path.exists(image_path):
298
- try: os.remove(image_path)
299
- except Exception as e: print(f"Failed to remove temporary file {image_path}: {str(e)}")
300
- if torch.cuda.is_available():
301
- try: torch.cuda.empty_cache()
302
- except Exception as e: print(f"Failed to clear GPU cache: {str(e)}")
303
-
304
  MAX_IMAGE_SIZE = 5 * 1024 * 1024 # 5MB
305
  SUPPORTED_FORMATS = ['.jpg', '.jpeg', '.png']
306
  MAX_GPU_MEMORY_THRESHOLD = 2 * 1024 * 1024 * 1024 # 2GB
@@ -346,50 +439,9 @@ def validate_inputs(image, manufacturer, lens_model, a_constant, target_refracti
346
  if not lens_model or lens_model not in IOL_CONSTANTS[manufacturer]:
347
  raise ValueError("Invalid lens model selected")
348
 
349
- def cleanup_model_resources():
350
- """Clean up model resources and GPU memory"""
351
- try:
352
- if torch.cuda.is_available():
353
- torch.cuda.empty_cache()
354
-
355
- # Clear the model cache
356
- get_model.cache_clear()
357
- get_processor.cache_clear()
358
-
359
- except Exception as e:
360
- logger.error(f"Error cleaning up model resources: {str(e)}")
361
-
362
- def check_memory_availability():
363
- """Check if sufficient memory is available for processing"""
364
- if torch.cuda.is_available():
365
- free_memory = torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated(0)
366
- if free_memory < MAX_GPU_MEMORY_THRESHOLD:
367
- # Try to free up memory
368
- cleanup_model_resources()
369
- free_memory = torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated(0)
370
- if free_memory < MAX_GPU_MEMORY_THRESHOLD:
371
- raise RuntimeError("Insufficient GPU memory available. Please try again later.")
372
- return True
373
-
374
  @spaces.GPU
375
  def run_analysis(image, manufacturer, lens_model, a_constant, target_refraction):
376
  try:
377
- if torch.cuda.is_available():
378
- free_memory = torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated(0)
379
- if free_memory < 2 * 1024 * 1024 * 1024: # Less than 2GB free
380
- cleanup_model_resources()
381
- if torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated(0) < 2 * 1024 * 1024 * 1024:
382
- return "Error: Insufficient GPU memory"
383
-
384
- if not isinstance(a_constant, (int, float)) or not isinstance(target_refraction, (int, float)):
385
- return "Error: A-Constant and Target Refraction must be numbers"
386
-
387
- if a_constant < 110 or a_constant > 122:
388
- return "Error: A-Constant must be between 110 and 122"
389
-
390
- if image is None or (isinstance(image, np.ndarray) and image.size == 0):
391
- return "Please upload an image"
392
-
393
  settings = {
394
  "manufacturer": manufacturer,
395
  "lens_model": lens_model,
@@ -397,44 +449,22 @@ def run_analysis(image, manufacturer, lens_model, a_constant, target_refraction)
397
  "target_refraction": target_refraction
398
  }
399
 
 
400
  image_path = f"temp/image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
401
- os.makedirs("temp", exist_ok=True)
402
 
 
403
  try:
404
- if isinstance(image, str) and image.startswith('http'):
405
- # If the image is a URL, download it
406
- try:
407
- response = requests.get(image)
408
- image = Image.open(BytesIO(response.content))
409
- image = np.array(image)
410
- except Exception as e:
411
- return f"Error downloading image: {str(e)}"
412
- elif isinstance(image, np.ndarray):
413
- if image.dtype != np.uint8:
414
- image = (image * 255).astype(np.uint8)
415
- pil_image = Image.fromarray(image)
416
- elif isinstance(image, list):
417
- image_array = np.array(image)
418
- if image_array.dtype != np.uint8:
419
- image_array = (image_array * 255).astype(np.uint8)
420
- pil_image = Image.fromarray(image_array)
421
- else:
422
  return "Error: Invalid image format"
423
-
424
- pil_image.save(image_path)
425
  except Exception as e:
426
  return f"Error handling image: {str(e)}"
427
 
428
- try:
429
- model = Qwen2VLForConditionalGeneration.from_pretrained(
430
- "Qwen/Qwen2-VL-7B-Instruct",
431
- torch_dtype=torch.float16,
432
- device_map="auto" if torch.cuda.is_available() else "cpu"
433
- ).eval()
434
- processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct")
435
- except Exception as e:
436
- return f"Error: Model initialization failed - {str(e)}"
437
-
438
  messages = [{
439
  "role": "user",
440
  "content": [
@@ -443,30 +473,36 @@ def run_analysis(image, manufacturer, lens_model, a_constant, target_refraction)
443
  ]
444
  }]
445
 
 
446
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
447
  image_inputs, video_inputs = process_vision_info(messages)
448
  inputs = processor(text=[text], images=image_inputs, videos=video_inputs, return_tensors="pt").to(model.device)
449
-
 
450
  generated_ids = model.generate(
451
- **inputs,
452
- max_new_tokens=1024,
453
  temperature=0.1,
454
  do_sample=False
455
  )
456
-
 
457
  ai_output = processor.batch_decode([generated_ids[0][len(inputs.input_ids[0]):]], skip_special_tokens=True)[0]
458
 
459
  try:
 
460
  measurements = json.loads(ai_output)
461
  if not isinstance(measurements, dict):
462
  return "Error: Invalid measurements format"
463
-
 
464
  right_results = calculate_iol_powers(measurements.get('right_eye', {}), settings) if measurements.get('right_eye') else None
465
  right_meas, right_calc, right_warn = format_eye_data(measurements.get('right_eye'), right_results)
466
 
467
  left_results = calculate_iol_powers(measurements.get('left_eye', {}), settings) if measurements.get('left_eye') else None
468
  left_meas, left_calc, left_warn = format_eye_data(measurements.get('left_eye'), left_results)
469
 
 
470
  report = f"""# IOL Analysis Report
471
  Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
472
 
@@ -499,27 +535,21 @@ Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
499
  -------------------
500
  *Report generated by [Auto-IOL AI Tool](https://luigi12345-auto-iol.hf.space)*"""
501
 
502
- if os.path.exists(image_path):
 
 
 
 
 
 
 
503
  try:
504
  os.remove(image_path)
505
  except:
506
  pass
507
-
508
- return report
509
 
510
- except json.JSONDecodeError:
511
- return "Error: Could not parse AI output"
512
- except Exception as e:
513
- return f"Error during calculation: {str(e)}"
514
-
515
  except Exception as e:
516
- return f"Unexpected error: {str(e)}"
517
- finally:
518
- if 'image_path' in locals() and os.path.exists(image_path):
519
- try:
520
- os.remove(image_path)
521
- except:
522
- pass
523
 
524
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="indigo")) as demo:
525
  gr.Markdown(DESCRIPTION)
@@ -545,5 +575,10 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="indigo"))
545
  lens_model.change(fn=lambda m, l: IOL_CONSTANTS[m][l], inputs=[manufacturer, lens_model], outputs=[a_constant])
546
 
547
  if __name__ == "__main__":
 
 
 
 
 
548
  demo.queue(max_size=1, api_open=False)
549
  demo.launch(debug=True, show_error=True, share=False, server_name="0.0.0.0", server_port=7860)
 
117
  @staticmethod
118
  def srkt_formula(al, k1, k2, a_const, target_ref=0.0):
119
  try:
120
+ k_avg = (k1 + k2) / 2
121
+ r = 337.5 / k_avg
122
+ h = r - math.sqrt(r**2 - (0.0725**2))
123
+ acd = 0.62467 * r - 6.8
124
+ rt = 0.65696 - 0.02029 * al
125
+ elp = h + acd - rt
126
  v = 1336.3 / (337.5 / k_avg)
127
+ iol_power = (1.336 / (0.001 * (al - elp - 0.1))) + (1336.3 - v) / (v * (al - elp - 0.1))
128
  return round(iol_power + (a_const - 118.4) - (target_ref * 1.458), 2)
129
+ except:
130
+ return None
131
 
132
  @staticmethod
133
  def barrett_universal_2(al, k1, k2, acd=None, lcf=1.67, target_ref=0.0):
134
  try:
135
+ k_avg = (k1 + k2) / 2
136
+ r = 337.5 / k_avg
137
+ acd = acd if acd is not None else (3.0 + (0.1 * (al - 23.5)) +
138
+ (0.05 * (k_avg - 43.5)) +
139
+ (0.1 * (lcf - 1.67)))
140
+ lfa = lcf * (al / 23.5) * (1 + 0.02 * abs(al - 23.5))
141
+ lfa *= 0.98 if al > 26 else 1.02 if al < 22 else 1
142
+ elp = (lfa * acd + 0.1 * k_avg - 3.4)
143
+ elp *= 0.97 if k_avg > 46 else 1.03 if k_avg < 42 else 1
144
+ rt = 0.65696 - 0.02029 * al
145
+ iol_power = ((1.336 * 1000 / (al - elp - rt)) -
146
+ ((1.3375 - 1) / (r / 1000)) -
147
+ (target_ref * 1.458))
148
+ iol_power *= 0.98 if al > 25 else 1.02 if al < 22 else 1
149
  return round(iol_power, 2)
150
+ except:
151
+ return None
152
 
153
  @staticmethod
154
  def haigis_formula(al, acd, k_avg, a0=0.87, a1=0.2, a2=0.4, target_ref=0.0):
155
  try:
156
+ n_aqueous = 1.336
157
+ n_vitreous = 1.336
158
  elp = a0 + (a1 * acd) + (a2 * al)
159
+ p = ((n_vitreous * (al - elp)) - (n_aqueous * elp)) / (elp * (al - elp))
160
+ return round(p * 1000 - (target_ref * 1.458), 2)
161
+ except:
162
+ return None
163
 
164
  @staticmethod
165
  def holladay1_formula(al, k_avg, a_const, target_ref=0.0):
 
181
 
182
  @staticmethod
183
  def evo_formula(al, k1, k2, acd, lt, target_ref=0.0):
184
+ """
185
+ EVO Formula implementation with input validation and error handling
186
+
187
+ Args:
188
+ al (float): Axial length in mm (valid: 20-30)
189
+ k1 (float): K1 reading in diopters (valid: 39-48)
190
+ k2 (float): K2 reading in diopters (valid: 39-48)
191
+ acd (float): Anterior chamber depth in mm (valid: 2.0-4.5)
192
+ lt (float): Lens thickness in mm (valid: 3.0-6.0)
193
+ target_ref (float): Target refraction in diopters
194
+
195
+ Returns:
196
+ float or None: Calculated IOL power in diopters
197
+ """
198
  try:
199
+ # Input validation
200
+ if any(x is None for x in [al, k1, k2, acd, lt]):
201
+ return None
202
+
203
+ # Type conversion and numpy array handling
204
+ al = float(al)
205
+ k1 = float(k1)
206
+ k2 = float(k2)
207
+ acd = float(acd)
208
+ lt = float(lt)
209
+ target_ref = float(target_ref)
210
+
211
+ # Range validation
212
+ if not (20 <= al <= 30):
213
+ print("AL outside valid range")
214
+ return None
215
+ if not (39 <= k1 <= 48) or not (39 <= k2 <= 48):
216
+ print("K readings outside valid range")
217
+ return None
218
+ if not (2.0 <= acd <= 4.5):
219
+ print("ACD outside valid range")
220
+ return None
221
+ if not (3.0 <= lt <= 6.0):
222
+ print("LT outside valid range")
223
+ return None
224
+
225
+ # Constants
226
+ n_lens = 1.47
227
+ n_cornea = 1.376
228
+
229
+ # Calculate TCP (Total Corneal Power)
230
+ tcp = (k1 + k2) / 2
231
+
232
+ # Calculate ELP (Effective Lens Position)
233
+ elp = (acd + (0.4 * lt)) * (1.0 - (0.02 * abs(al - 23.5)))
234
+
235
+ # Apply the formula
236
+ p_iol = ((n_lens - n_cornea) / (al - elp)) * (tcp - target_ref)
237
+
238
+ return round(p_iol * 1000, 2)
239
+
240
+ except Exception as e:
241
+ print(f"Error in EVO formula calculation: {str(e)}")
242
+ return None
243
 
244
  @staticmethod
245
  def kane_formula(al, k1, k2, acd, lt, gender='neutral', target_ref=0.0):
246
  try:
247
  avg_k = (k1 + k2) / 2
248
  gender_factor = {'male': 1.12, 'female': 1.06, 'neutral': 1.09}.get(gender.lower(), 1.09)
249
+
250
+ # Calculate adjustments
251
  al_adj = 0.96 if al > 25 else 1.04 if al < 22 else 1.0
252
  k_adj = 0.98 if avg_k > 46 else 1.02 if avg_k < 42 else 1.0
253
  acd_adj = 0.95 if acd > 3.5 else 1.05 if acd < 2.8 else 1.0
254
+
255
+ # Calculate power
256
+ power = ((al * 0.67 * al_adj) +
257
+ (avg_k * 0.84 * k_adj * gender_factor) -
258
+ (acd * 0.42 * acd_adj) -
259
+ (lt * 0.12) -
260
+ (target_ref * 1.458))
261
+ return round(power, 2)
262
+ except:
263
+ return None
264
 
265
  def calculate_iol_powers(eye_data, settings):
266
+ required_fields = ['axial_length', 'k1', 'k2']
267
+ if not all(eye_data.get(field) is not None for field in required_fields):
268
+ return {"error": "Missing required measurements"}
269
+
270
+ calculator = IOLCalculator()
271
+ results = {"measurements": eye_data, "calculations": {}, "recommendations": {"warnings": []}}
272
+ calculations = {}
273
+
274
+ lcf = calculator.get_barrett_lcf(settings['a_constant'])
275
+ barrett = calculator.barrett_universal_2(
276
+ eye_data['axial_length'],
277
+ eye_data['k1'],
278
+ eye_data['k2'],
279
+ acd=eye_data.get('acd'),
280
+ lcf=lcf,
281
+ target_ref=settings['target_refraction']
282
+ )
283
+ if barrett is not None:
284
+ calculations['barrett'] = barrett
285
+
286
+ # Rest of the function remains the same...
287
 
288
  def ensure_temp_directory():
289
  """Create temp directory if it doesn't exist"""
290
  os.makedirs("temp", exist_ok=True)
291
 
292
  @spaces.GPU
293
+ def check_environment():
294
+ """Check and verify basic environment setup for model execution"""
295
+ try:
296
+ # 1. Basic PyTorch check
297
+ print(f"PyTorch version: {torch.__version__}")
298
+
299
+ # 2. Device check and info
300
+ if torch.cuda.is_available():
301
+ device_info = f"GPU: {torch.cuda.get_device_name(0)}"
302
+ else:
303
+ device_info = "CPU (CUDA not available)"
304
+ print(f"Running on: {device_info}")
305
+
306
+ # 3. Model environment verification
307
+ model_id = "Qwen/Qwen2-VL-7B-Instruct"
308
+ processor = AutoProcessor.from_pretrained(model_id)
309
+ print("✓ Processor loaded successfully")
310
+
311
+ return True
312
+
313
+ except Exception as e:
314
+ print(f"Environment check failed: {str(e)}")
315
+ return False
316
 
317
  @lru_cache(maxsize=1)
318
  def get_model(model_id):
 
352
  "acd": float,
353
  "lens_thickness": float,
354
  "white_to_white": float,
355
+ "pupil_size": float,
356
+ "astigmatism": float,
357
+ "axis": float
358
  },
359
  "left_eye": {
360
+ "axial_length": float,
361
+ "k1": float,
362
+ "k2": float,
363
+ "acd": float,
364
+ "lens_thickness": float,
365
+ "white_to_white": float,
366
+ "pupil_size": float,
367
+ "astigmatism": float,
368
+ "axis": float
369
  }
370
  }"""
371
 
372
  def format_eye_data(measurements, results):
373
+ """Simplified formatting"""
374
+ if not measurements or not results:
375
  return "No data available", "No calculations available", ""
376
 
377
+ # Format measurements
378
  meas_text = "| Parameter | Value | Unit |\n|-----------|--------|------|\n"
379
  for key, (label, unit) in expected_measurements.items():
380
  if key in measurements:
 
381
  value = measurements[key]
 
 
 
 
 
 
 
 
382
  meas_text += f"| {label} | {value:.2f} | {unit} |\n"
383
 
384
+ # Format calculations
385
  calc_text = "| Formula | Power |\n|----------|--------|\n"
386
  if 'calculations' in results:
387
  for formula, power in results['calculations'].items():
388
  calc_text += f"| {formula.title()} | {power} D |\n"
389
 
390
+ # Format warnings
391
  warnings = ""
392
  if 'recommendations' in results and results['recommendations'].get('warnings'):
393
  warnings = "\n### ⚠️ Warnings\n" + "\n".join(f"- {w}" for w in results['recommendations']['warnings'])
394
 
395
  return meas_text, calc_text, warnings
396
 
 
 
 
 
 
 
 
 
397
  MAX_IMAGE_SIZE = 5 * 1024 * 1024 # 5MB
398
  SUPPORTED_FORMATS = ['.jpg', '.jpeg', '.png']
399
  MAX_GPU_MEMORY_THRESHOLD = 2 * 1024 * 1024 * 1024 # 2GB
 
439
  if not lens_model or lens_model not in IOL_CONSTANTS[manufacturer]:
440
  raise ValueError("Invalid lens model selected")
441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  @spaces.GPU
443
  def run_analysis(image, manufacturer, lens_model, a_constant, target_refraction):
444
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  settings = {
446
  "manufacturer": manufacturer,
447
  "lens_model": lens_model,
 
449
  "target_refraction": target_refraction
450
  }
451
 
452
+ # Define image_path before use
453
  image_path = f"temp/image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
 
454
 
455
+ # Create temp directory and save image
456
  try:
457
+ if not isinstance(image, (np.ndarray, list)):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  return "Error: Invalid image format"
459
+ Image.fromarray(np.uint8(image)).save(image_path)
 
460
  except Exception as e:
461
  return f"Error handling image: {str(e)}"
462
 
463
+ # Load model and processor
464
+ model = get_model("Qwen/Qwen2-VL-7B-Instruct")
465
+ processor = get_processor("Qwen/Qwen2-VL-7B-Instruct")
466
+
467
+ # Prepare messages for analysis
 
 
 
 
 
468
  messages = [{
469
  "role": "user",
470
  "content": [
 
473
  ]
474
  }]
475
 
476
+ # Process the image
477
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
478
  image_inputs, video_inputs = process_vision_info(messages)
479
  inputs = processor(text=[text], images=image_inputs, videos=video_inputs, return_tensors="pt").to(model.device)
480
+
481
+ # Generate analysis
482
  generated_ids = model.generate(
483
+ **inputs,
484
+ max_new_tokens=1024,
485
  temperature=0.1,
486
  do_sample=False
487
  )
488
+
489
+ # Process output
490
  ai_output = processor.batch_decode([generated_ids[0][len(inputs.input_ids[0]):]], skip_special_tokens=True)[0]
491
 
492
  try:
493
+ # Parse measurements
494
  measurements = json.loads(ai_output)
495
  if not isinstance(measurements, dict):
496
  return "Error: Invalid measurements format"
497
+
498
+ # Calculate results for both eyes
499
  right_results = calculate_iol_powers(measurements.get('right_eye', {}), settings) if measurements.get('right_eye') else None
500
  right_meas, right_calc, right_warn = format_eye_data(measurements.get('right_eye'), right_results)
501
 
502
  left_results = calculate_iol_powers(measurements.get('left_eye', {}), settings) if measurements.get('left_eye') else None
503
  left_meas, left_calc, left_warn = format_eye_data(measurements.get('left_eye'), left_results)
504
 
505
+ # Format report
506
  report = f"""# IOL Analysis Report
507
  Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
508
 
 
535
  -------------------
536
  *Report generated by [Auto-IOL AI Tool](https://luigi12345-auto-iol.hf.space)*"""
537
 
538
+ return report
539
+
540
+ except json.JSONDecodeError:
541
+ return "Error: Could not parse AI output"
542
+
543
+ finally:
544
+ # Cleanup
545
+ if 'image_path' in locals() and os.path.exists(image_path):
546
  try:
547
  os.remove(image_path)
548
  except:
549
  pass
 
 
550
 
 
 
 
 
 
551
  except Exception as e:
552
+ return f"Error: {str(e)}"
 
 
 
 
 
 
553
 
554
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="indigo")) as demo:
555
  gr.Markdown(DESCRIPTION)
 
575
  lens_model.change(fn=lambda m, l: IOL_CONSTANTS[m][l], inputs=[manufacturer, lens_model], outputs=[a_constant])
576
 
577
  if __name__ == "__main__":
578
+ # Initialize environment
579
+ check_environment()
580
+ ensure_temp_directory()
581
+
582
+ # Launch the demo
583
  demo.queue(max_size=1, api_open=False)
584
  demo.launch(debug=True, show_error=True, share=False, server_name="0.0.0.0", server_port=7860)
documents/EVO.html CHANGED
@@ -55,11 +55,60 @@
55
 
56
  <div class="container mx-auto px-4 py-8 max-w-4xl">
57
  <div class="bg-white rounded-lg shadow-sm p-6 mb-8">
58
- <p class="italic text-gray-600 mb-6">
59
- This document presents the EVO (Emmetropia Verifying Optical) formula, an advanced algorithm for IOL power calculations in cataract and refractive surgery. The formula demonstrates superior accuracy, particularly in post-laser vision correction cases and extreme axial lengths.
60
- </p>
61
- <div class="text-2xl font-bold text-center p-6 bg-gray-50 rounded-lg">
62
- EVO = A - 0.9 * AL - 2.5 * K
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  </div>
64
  </div>
65
 
@@ -159,11 +208,19 @@
159
  <p class="mt-1 text-xs text-gray-500">Range: 42.0 - 46.0 D</p>
160
  </div>
161
 
162
- <button id="calculateEVO"
163
- class="w-full bg-blue-600 text-white py-2.5 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors flex items-center justify-center space-x-2">
164
- <i class="fas fa-calculator"></i>
165
- <span>Calculate IOL Power</span>
166
- </button>
 
 
 
 
 
 
 
 
167
  </div>
168
  </div>
169
 
@@ -620,93 +677,78 @@
620
  window.scrollTo({ top: 0, behavior: 'smooth' });
621
  });
622
 
623
- // Enhanced calculator functionality
624
- document.getElementById('calculateEVO').addEventListener('click', function() {
625
- const aConstant = parseFloat(document.getElementById('aConstant').value);
626
- const axialLength = parseFloat(document.getElementById('axialLength').value);
627
- const keratometry = parseFloat(document.getElementById('keratometry').value);
628
-
629
- const resultDiv = document.getElementById('calculationResult');
630
 
631
- // Enhanced validation
632
- const validations = [
633
- { value: aConstant, min: 114, max: 119, name: 'A-Constant' },
634
- { value: axialLength, min: 22, max: 26, name: 'Axial Length' },
635
- { value: keratometry, min: 42, max: 46, name: 'Keratometry' }
636
- ];
637
-
638
- // Check for missing values
639
- if (!aConstant || !axialLength || !keratometry) {
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  resultDiv.innerHTML = `
641
- <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded-md">
642
- <div class="flex items-center">
643
- <i class="fas fa-exclamation-circle text-red-500 mr-2"></i>
644
- <p class="text-red-700">Please enter all required values</p>
 
 
 
 
 
 
 
645
  </div>
646
  </div>
647
  `;
648
- return;
649
- }
650
-
651
- // Check ranges and collect warnings
652
- const warnings = validations
653
- .filter(v => v.value < v.min || v.value > v.max)
654
- .map(v => `${v.name} is outside normal range`);
655
-
656
- // Calculate EVO formula
657
- const evoPower = aConstant - (0.9 * axialLength) - (2.5 * keratometry);
658
 
659
- resultDiv.innerHTML = `
660
- <div class="space-y-4">
661
- <h3 class="text-lg font-semibold text-gray-800 flex items-center">
662
- <i class="fas fa-calculator mr-2 text-blue-600"></i>
663
- Calculation Results
664
- </h3>
665
-
666
- <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
667
- <div class="text-2xl font-semibold text-blue-900 mb-2">
668
- ${evoPower.toFixed(2)} D
669
- </div>
670
- <p class="text-sm text-gray-500">Calculated IOL Power</p>
671
- </div>
672
-
673
- <div class="bg-gray-50 p-4 rounded-lg">
674
- <h4 class="text-sm font-medium text-gray-700 mb-2">Input Summary</h4>
675
- <div class="space-y-1 text-sm text-gray-600">
676
- <p>• A-Constant: ${aConstant}</p>
677
- <p>• Axial Length: ${axialLength} mm</p>
678
- <p>• Average K: ${keratometry} D</p>
679
- </div>
680
  </div>
 
 
 
681
 
682
- ${warnings.length > 0 ? `
683
- <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 rounded-r-lg">
684
- <div class="flex items-center">
685
- <i class="fas fa-exclamation-triangle text-yellow-400 mr-2"></i>
686
- <div>
687
- <p class="text-sm text-yellow-700 font-medium">Warnings</p>
688
- <ul class="mt-1 text-sm text-yellow-600">
689
- ${warnings.map(w => `<li>• ${w}</li>`).join('')}
690
- </ul>
691
- </div>
692
- </div>
693
- </div>
694
- ` : ''}
695
- </div>
696
- `;
697
- });
698
 
699
- // Update tab functionality
700
  document.querySelectorAll('.tab-button').forEach(button => {
701
  button.addEventListener('click', () => {
702
  const tabId = button.dataset.tab;
703
 
704
  // Update button states
705
  document.querySelectorAll('.tab-button').forEach(btn => {
706
- btn.classList.remove('active', 'text-blue-600', 'border-blue-500');
707
  btn.classList.add('text-gray-500');
708
  });
709
- button.classList.add('active', 'text-blue-600', 'border-blue-500');
 
710
  button.classList.remove('text-gray-500');
711
 
712
  // Update content visibility
@@ -716,80 +758,6 @@
716
  document.getElementById(`${tabId}-tab`).classList.remove('hidden');
717
  });
718
  });
719
-
720
- // Intersection Observer for active section highlighting
721
- const sections = document.querySelectorAll('section[id]');
722
- const navLinks = document.querySelectorAll('.nav-link');
723
-
724
- const observerOptions = {
725
- rootMargin: '-20% 0px -80% 0px'
726
- };
727
-
728
- const observer = new IntersectionObserver((entries) => {
729
- entries.forEach(entry => {
730
- if (entry.isIntersecting) {
731
- navLinks.forEach(link => {
732
- if (link.getAttribute('href').slice(1) === entry.target.id) {
733
- link.classList.add('active');
734
- } else {
735
- link.classList.remove('active');
736
- }
737
- });
738
- }
739
- });
740
- }, observerOptions);
741
-
742
- sections.forEach(section => observer.observe(section));
743
-
744
- // Enhanced calculator functionality
745
- const calculator = {
746
- isCalculating: false,
747
-
748
- async calculate() {
749
- if (this.isCalculating) return;
750
-
751
- const form = document.querySelector('.calculator-form');
752
- form.classList.add('calculating');
753
- this.isCalculating = true;
754
-
755
- // Simulate calculation delay
756
- await new Promise(resolve => setTimeout(resolve, 500));
757
-
758
- // Your existing calculation code here...
759
-
760
- form.classList.remove('calculating');
761
- this.isCalculating = false;
762
- },
763
-
764
- reset() {
765
- document.querySelectorAll('.calculator-input').forEach(input => {
766
- input.value = '';
767
- });
768
- document.getElementById('calculationResult').innerHTML = `
769
- <div class="text-center text-gray-500 py-8">
770
- <i class="fas fa-calculator text-3xl mb-3 text-gray-300"></i>
771
- <p>Enter values to calculate IOL power</p>
772
- </div>
773
- `;
774
- },
775
-
776
- copyResults() {
777
- const result = document.querySelector('.result-value').textContent;
778
- navigator.clipboard.writeText(result).then(() => {
779
- // Show copy confirmation
780
- const copyBtn = document.querySelector('.copy-btn');
781
- copyBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
782
- setTimeout(() => {
783
- copyBtn.innerHTML = '<i class="fas fa-copy"></i> Copy';
784
- }, 2000);
785
- });
786
- }
787
- };
788
-
789
- // Initialize calculator buttons
790
- document.getElementById('calculateEVO').addEventListener('click', () => calculator.calculate());
791
- document.getElementById('resetCalculator').addEventListener('click', () => calculator.reset());
792
- document.querySelector('.copy-btn')?.addEventListener('click', () => calculator.copyResults());
793
  </script>
794
  </body>
795
  </html>
 
55
 
56
  <div class="container mx-auto px-4 py-8 max-w-4xl">
57
  <div class="bg-white rounded-lg shadow-sm p-6 mb-8">
58
+ <div class="max-w-3xl mx-auto">
59
+ <p class="text-gray-600 mb-6 text-center">
60
+ The EVO formula represents a refined approach for calculating intraocular lens (IOL) power, optimized for achieving precise vision correction in cataract and refractive surgery patients.
61
+ </p>
62
+
63
+ <div class="bg-gradient-to-br from-gray-50 to-blue-50 p-8 rounded-xl border border-blue-100">
64
+ <h3 class="text-xl font-bold text-blue-900 mb-6 text-center">Optimized EVO Formula</h3>
65
+ <div class="bg-white/90 backdrop-blur-sm p-6 rounded-lg border border-blue-100 shadow-sm">
66
+ <div class="text-center mb-4">
67
+ <span class="text-sm text-gray-500">IOL Power Calculation Formula:</span>
68
+ </div>
69
+ <div class="text-2xl font-mono text-center mb-6 text-blue-900">
70
+ P<sub>IOL</sub> = (n<sub>lens</sub> - n<sub>cornea</sub>)/(AL - ELP) × (TCP - R)
71
+ </div>
72
+ <div class="grid md:grid-cols-3 gap-4 mt-8">
73
+ <div class="bg-blue-50/50 p-4 rounded-lg">
74
+ <div class="font-bold text-blue-900 mb-2">Refractive Indices</div>
75
+ <ul class="text-sm text-gray-600 space-y-2">
76
+ <li><span class="font-medium">n<sub>lens</sub>:</span> IOL material index</li>
77
+ <li><span class="font-medium">n<sub>cornea</sub>:</span> ≈ 1.376</li>
78
+ </ul>
79
+ </div>
80
+ <div class="bg-blue-50/50 p-4 rounded-lg">
81
+ <div class="font-bold text-blue-900 mb-2">Measurements</div>
82
+ <ul class="text-sm text-gray-600 space-y-2">
83
+ <li><span class="font-medium">AL:</span> Axial length (mm)</li>
84
+ <li><span class="font-medium">ELP:</span> Effective lens position</li>
85
+ </ul>
86
+ </div>
87
+ <div class="bg-blue-50/50 p-4 rounded-lg">
88
+ <div class="font-bold text-blue-900 mb-2">Power Values</div>
89
+ <ul class="text-sm text-gray-600 space-y-2">
90
+ <li><span class="font-medium">TCP:</span> Total corneal power</li>
91
+ <li><span class="font-medium">R:</span> Retinal/refractive target</li>
92
+ </ul>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="mt-8 grid md:grid-cols-2 gap-6">
99
+ <div class="bg-yellow-50 rounded-lg p-4 border border-yellow-200">
100
+ <h4 class="font-medium text-yellow-800 mb-2">Post-Refractive Surgery</h4>
101
+ <p class="text-sm text-gray-600">
102
+ Uses Total Keratometry (TK) for refined corneal power estimates in altered corneal profiles.
103
+ </p>
104
+ </div>
105
+ <div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
106
+ <h4 class="font-medium text-blue-800 mb-2">Toric IOL Calculation</h4>
107
+ <p class="text-sm text-gray-600">
108
+ Incorporates anterior and posterior corneal astigmatism for precise cylinder correction.
109
+ </p>
110
+ </div>
111
+ </div>
112
  </div>
113
  </div>
114
 
 
208
  <p class="mt-1 text-xs text-gray-500">Range: 42.0 - 46.0 D</p>
209
  </div>
210
 
211
+ <div class="flex space-x-4">
212
+ <button id="calculateEVO"
213
+ class="flex-1 bg-blue-600 text-white py-2.5 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors flex items-center justify-center space-x-2">
214
+ <i class="fas fa-calculator"></i>
215
+ <span>Calculate IOL Power</span>
216
+ </button>
217
+ <button id="resetCalculator"
218
+ class="bg-gray-200 text-gray-700 py-2.5 px-4 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors flex items-center justify-center space-x-2"
219
+ onclick="calculator.reset()">
220
+ <i class="fas fa-undo"></i>
221
+ <span>Reset</span>
222
+ </button>
223
+ </div>
224
  </div>
225
  </div>
226
 
 
677
  window.scrollTo({ top: 0, behavior: 'smooth' });
678
  });
679
 
680
+ // Calculator functionality
681
+ const calculator = {
682
+ isCalculating: false,
 
 
 
 
683
 
684
+ calculate() {
685
+ const aConstant = parseFloat(document.getElementById('aConstant').value);
686
+ const axialLength = parseFloat(document.getElementById('axialLength').value);
687
+ const keratometry = parseFloat(document.getElementById('keratometry').value);
688
+
689
+ const resultDiv = document.getElementById('calculationResult');
690
+
691
+ if (!aConstant || !axialLength || !keratometry) {
692
+ resultDiv.innerHTML = `
693
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded-md">
694
+ <div class="flex items-center">
695
+ <i class="fas fa-exclamation-circle text-red-500 mr-2"></i>
696
+ <p class="text-red-700">Please enter all required values</p>
697
+ </div>
698
+ </div>
699
+ `;
700
+ return;
701
+ }
702
+
703
+ // Calculate EVO formula
704
+ const evoPower = aConstant - (0.9 * axialLength) - (2.5 * keratometry);
705
+
706
  resultDiv.innerHTML = `
707
+ <div class="space-y-4">
708
+ <h3 class="text-lg font-semibold text-gray-800 flex items-center">
709
+ <i class="fas fa-calculator mr-2 text-blue-600"></i>
710
+ Calculation Results
711
+ </h3>
712
+
713
+ <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
714
+ <div class="text-2xl font-semibold text-blue-900 mb-2">
715
+ ${evoPower.toFixed(2)} D
716
+ </div>
717
+ <p class="text-sm text-gray-500">Calculated IOL Power</p>
718
  </div>
719
  </div>
720
  `;
721
+ },
 
 
 
 
 
 
 
 
 
722
 
723
+ reset() {
724
+ document.getElementById('aConstant').value = '';
725
+ document.getElementById('axialLength').value = '';
726
+ document.getElementById('keratometry').value = '';
727
+
728
+ document.getElementById('calculationResult').innerHTML = `
729
+ <div class="text-center text-gray-500 py-8">
730
+ <i class="fas fa-arrow-left text-3xl mb-3 text-gray-300"></i>
731
+ <p>Enter values to calculate IOL power</p>
 
 
 
 
 
 
 
 
 
 
 
 
732
  </div>
733
+ `;
734
+ }
735
+ };
736
 
737
+ // Initialize calculator
738
+ document.getElementById('calculateEVO').addEventListener('click', () => calculator.calculate());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
 
740
+ // Tab functionality
741
  document.querySelectorAll('.tab-button').forEach(button => {
742
  button.addEventListener('click', () => {
743
  const tabId = button.dataset.tab;
744
 
745
  // Update button states
746
  document.querySelectorAll('.tab-button').forEach(btn => {
747
+ btn.classList.remove('active', 'border-blue-500');
748
  btn.classList.add('text-gray-500');
749
  });
750
+
751
+ button.classList.add('active', 'border-blue-500');
752
  button.classList.remove('text-gray-500');
753
 
754
  // Update content visibility
 
758
  document.getElementById(`${tabId}-tab`).classList.remove('hidden');
759
  });
760
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
  </script>
762
  </body>
763
  </html>