Spaces:
Runtime error
Runtime error
luigi12345
commited on
Commit
•
115ff6c
1
Parent(s):
cff8897
After new EVo
Browse files- app.py +196 -161
- 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
|
121 |
-
|
|
|
|
|
|
|
|
|
122 |
v = 1336.3 / (337.5 / k_avg)
|
123 |
-
iol_power = (1.336
|
124 |
return round(iol_power + (a_const - 118.4) - (target_ref * 1.458), 2)
|
125 |
-
except:
|
|
|
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
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
return round(iol_power, 2)
|
136 |
-
except:
|
|
|
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 |
-
|
143 |
-
|
|
|
|
|
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 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
|
192 |
def calculate_iol_powers(eye_data, settings):
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
|
|
|
|
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():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
}
|
263 |
}"""
|
264 |
|
265 |
def format_eye_data(measurements, results):
|
266 |
-
"""
|
267 |
-
if not measurements or not
|
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,
|
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 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"
|
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 |
-
<
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
<
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
</div>
|
168 |
</div>
|
169 |
|
@@ -620,93 +677,78 @@
|
|
620 |
window.scrollTo({ top: 0, behavior: 'smooth' });
|
621 |
});
|
622 |
|
623 |
-
//
|
624 |
-
|
625 |
-
|
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 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
640 |
resultDiv.innerHTML = `
|
641 |
-
<div class="
|
642 |
-
<
|
643 |
-
<i class="fas fa-
|
644 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
645 |
</div>
|
646 |
</div>
|
647 |
`;
|
648 |
-
|
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 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
<
|
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 |
-
|
683 |
-
|
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 |
-
//
|
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', '
|
707 |
btn.classList.add('text-gray-500');
|
708 |
});
|
709 |
-
|
|
|
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>
|