Klinapps commited on
Commit
aafc662
·
verified ·
1 Parent(s): ed71526

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -16
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  Cephalometric Landmark Detection API
3
  HRNet-W32 - 19 Landmarks Cefalométricos
4
- Versión Final con Visualización Mejorada
5
  """
6
 
7
  import os
@@ -67,6 +67,18 @@ def get_color_for_landmark(idx):
67
  return group_data["color"]
68
  return (255, 255, 255)
69
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # ============================================================================
71
  # ARQUITECTURA HRNET-W32
72
  # ============================================================================
@@ -365,7 +377,7 @@ def detect_landmarks(image):
365
  "abbrev": LANDMARK_ABBREV[i],
366
  "x": round(float(preds[0, i, 0] * scale_x), 1),
367
  "y": round(float(preds[0, i, 1] * scale_y), 1),
368
- "confidence": round(float(maxvals[0, i]), 2)
369
  })
370
 
371
  return landmarks
@@ -392,8 +404,21 @@ def draw_landmarks_clean(image, landmarks, show_labels=True):
392
  x, y = lm['x'], lm['y']
393
  color = get_color_for_landmark(lm['id'])
394
 
395
- # Círculo con borde negro
396
- draw.ellipse([x-radius-1, y-radius-1, x+radius+1, y+radius+1], fill=(0, 0, 0))
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  draw.ellipse([x-radius, y-radius, x+radius, y+radius], fill=color)
398
 
399
  if show_labels:
@@ -469,18 +494,20 @@ def find_label_position(x, y, text, existing_positions, font, draw, img_size):
469
  return (x + 12, y - 5)
470
 
471
  def create_legend_image(landmarks):
472
- """Crea imagen con leyenda de landmarks"""
473
- width = 280
474
- height = 520
475
  legend = Image.new('RGB', (width, height), (30, 30, 30))
476
  draw = ImageDraw.Draw(legend)
477
 
478
  try:
479
  font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
480
  font_text = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
 
481
  except:
482
  font_title = ImageFont.load_default()
483
  font_text = font_title
 
484
 
485
  y_pos = 10
486
  draw.text((10, y_pos), "LANDMARKS DETECTADOS", fill=(255, 255, 255), font=font_title)
@@ -495,15 +522,42 @@ def create_legend_image(landmarks):
495
 
496
  for idx in group_data["indices"]:
497
  lm = landmarks[idx]
498
- # Círculo de color
 
 
 
499
  draw.ellipse([15, y_pos+2, 23, y_pos+10], fill=group_data["color"])
500
- # Texto
501
- text = f"{lm['abbrev']}: ({lm['x']:.0f}, {lm['y']:.0f})"
502
- draw.text((30, y_pos), text, fill=(220, 220, 220), font=font_text)
 
 
 
 
 
 
 
 
 
 
 
 
503
  y_pos += 16
504
 
505
  y_pos += 8
506
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  return legend
508
 
509
  def combine_images(main_image, legend):
@@ -537,14 +591,27 @@ def process_image(image, show_labels):
537
  legend = create_legend_image(landmarks)
538
  combined = combine_images(annotated, legend)
539
 
540
- # JSON output
 
 
 
 
 
 
541
  result = {
542
  "num_landmarks": len(landmarks),
 
 
 
 
 
543
  "landmarks": [{
544
  "id": lm["id"],
545
  "name": lm["name"],
 
546
  "x": lm["x"],
547
- "y": lm["y"]
 
548
  } for lm in landmarks]
549
  }
550
 
@@ -555,15 +622,18 @@ def process_image(image, show_labels):
555
  return None, f"Error: {e}\n{traceback.format_exc()}"
556
 
557
  print("=" * 50)
558
- print("Cephalometric Landmark Detection v1.0")
 
559
  print("=" * 50)
560
  load_model()
561
 
562
- with gr.Blocks(title="Cephalometric Landmark Detection", theme=gr.themes.Soft()) as demo:
563
  gr.Markdown("""
564
  # 🦷 Detección de Landmarks Cefalométricos
565
 
566
  Detección automática de **19 puntos anatómicos** en radiografías cefalométricas laterales usando HRNet-W32.
 
 
567
  """)
568
 
569
  with gr.Row():
@@ -576,7 +646,7 @@ with gr.Blocks(title="Cephalometric Landmark Detection", theme=gr.themes.Soft())
576
  output_image = gr.Image(label="📍 Resultado con Leyenda", height=500)
577
 
578
  with gr.Accordion("📋 Datos JSON", open=False):
579
- output_json = gr.Code(label="Coordenadas", language="json", lines=10)
580
 
581
  gr.Markdown("""
582
  ---
@@ -589,6 +659,13 @@ with gr.Blocks(title="Cephalometric Landmark Detection", theme=gr.themes.Soft())
589
  | 🟣 | Mandibular | Point B, Pogonion, Menton, Gnathion, Gonion |
590
  | 🟡 | Dental | Upper Incisor (U1), Lower Incisor (L1) |
591
  | 🩵 | Tejido Blando | Upper Lip, Lower Lip, Subnasale, Soft Tissue Pog |
 
 
 
 
 
 
 
592
  """)
593
 
594
  detect_btn.click(fn=process_image, inputs=[input_image, show_labels], outputs=[output_image, output_json])
 
1
  """
2
  Cephalometric Landmark Detection API
3
  HRNet-W32 - 19 Landmarks Cefalométricos
4
+ Versión 1.1 - Con Indicadores de Confianza
5
  """
6
 
7
  import os
 
67
  return group_data["color"]
68
  return (255, 255, 255)
69
 
70
+ def get_confidence_indicator(confidence):
71
+ """Retorna color e indicador según nivel de confianza"""
72
+ # Normalizar confianza (los heatmaps pueden tener valores > 1)
73
+ conf_pct = min(confidence * 100, 100)
74
+
75
+ if conf_pct >= 80:
76
+ return (80, 255, 80), "✓", conf_pct # Verde - Alta
77
+ elif conf_pct >= 50:
78
+ return (255, 255, 80), "~", conf_pct # Amarillo - Media
79
+ else:
80
+ return (255, 80, 80), "!", conf_pct # Rojo - Baja
81
+
82
  # ============================================================================
83
  # ARQUITECTURA HRNET-W32
84
  # ============================================================================
 
377
  "abbrev": LANDMARK_ABBREV[i],
378
  "x": round(float(preds[0, i, 0] * scale_x), 1),
379
  "y": round(float(preds[0, i, 1] * scale_y), 1),
380
+ "confidence": round(float(maxvals[0, i]), 4)
381
  })
382
 
383
  return landmarks
 
404
  x, y = lm['x'], lm['y']
405
  color = get_color_for_landmark(lm['id'])
406
 
407
+ # Obtener info de confianza
408
+ conf_color, conf_indicator, conf_pct = get_confidence_indicator(lm['confidence'])
409
+
410
+ # Círculo con borde según confianza
411
+ # Borde más grueso y de color diferente para baja confianza
412
+ if conf_pct < 50:
413
+ # Borde rojo grueso para baja confianza
414
+ draw.ellipse([x-radius-3, y-radius-3, x+radius+3, y+radius+3], fill=(255, 80, 80))
415
+ elif conf_pct < 80:
416
+ # Borde amarillo para confianza media
417
+ draw.ellipse([x-radius-2, y-radius-2, x+radius+2, y+radius+2], fill=(255, 255, 80))
418
+ else:
419
+ # Borde negro normal para alta confianza
420
+ draw.ellipse([x-radius-1, y-radius-1, x+radius+1, y+radius+1], fill=(0, 0, 0))
421
+
422
  draw.ellipse([x-radius, y-radius, x+radius, y+radius], fill=color)
423
 
424
  if show_labels:
 
494
  return (x + 12, y - 5)
495
 
496
  def create_legend_image(landmarks):
497
+ """Crea imagen con leyenda de landmarks incluyendo confianza"""
498
+ width = 320
499
+ height = 580
500
  legend = Image.new('RGB', (width, height), (30, 30, 30))
501
  draw = ImageDraw.Draw(legend)
502
 
503
  try:
504
  font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
505
  font_text = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
506
+ font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 9)
507
  except:
508
  font_title = ImageFont.load_default()
509
  font_text = font_title
510
+ font_small = font_title
511
 
512
  y_pos = 10
513
  draw.text((10, y_pos), "LANDMARKS DETECTADOS", fill=(255, 255, 255), font=font_title)
 
522
 
523
  for idx in group_data["indices"]:
524
  lm = landmarks[idx]
525
+ conf = lm.get('confidence', 0)
526
+ conf_color, conf_indicator, conf_pct = get_confidence_indicator(conf)
527
+
528
+ # Círculo de color del grupo
529
  draw.ellipse([15, y_pos+2, 23, y_pos+10], fill=group_data["color"])
530
+
531
+ # Texto con coordenadas
532
+ coord_text = f"{lm['abbrev']}: ({lm['x']:.0f}, {lm['y']:.0f})"
533
+ draw.text((30, y_pos), coord_text, fill=(220, 220, 220), font=font_text)
534
+
535
+ # Indicador de confianza con color
536
+ conf_text = f"{conf_pct:.0f}%"
537
+ # Posicionar a la derecha
538
+ conf_x = width - 60
539
+ draw.text((conf_x, y_pos), conf_text, fill=conf_color, font=font_text)
540
+
541
+ # Indicador visual
542
+ indicator_x = width - 20
543
+ draw.text((indicator_x, y_pos), conf_indicator, fill=conf_color, font=font_text)
544
+
545
  y_pos += 16
546
 
547
  y_pos += 8
548
 
549
+ # Leyenda de confianza al final
550
+ y_pos += 5
551
+ draw.line([(10, y_pos), (width-10, y_pos)], fill=(100, 100, 100), width=1)
552
+ y_pos += 8
553
+ draw.text((10, y_pos), "Confianza:", fill=(200, 200, 200), font=font_small)
554
+ y_pos += 14
555
+ draw.text((15, y_pos), "✓ ≥80% Alta", fill=(80, 255, 80), font=font_small)
556
+ y_pos += 12
557
+ draw.text((15, y_pos), "~ 50-79% Media", fill=(255, 255, 80), font=font_small)
558
+ y_pos += 12
559
+ draw.text((15, y_pos), "! <50% Baja (revisar)", fill=(255, 80, 80), font=font_small)
560
+
561
  return legend
562
 
563
  def combine_images(main_image, legend):
 
591
  legend = create_legend_image(landmarks)
592
  combined = combine_images(annotated, legend)
593
 
594
+ # Calcular estadísticas de confianza
595
+ confidences = [lm['confidence'] for lm in landmarks]
596
+ avg_conf = np.mean(confidences) * 100
597
+ min_conf = np.min(confidences) * 100
598
+ low_conf_count = sum(1 for c in confidences if c * 100 < 50)
599
+
600
+ # JSON output con confianza
601
  result = {
602
  "num_landmarks": len(landmarks),
603
+ "confidence_stats": {
604
+ "average": round(avg_conf, 1),
605
+ "minimum": round(min_conf, 1),
606
+ "low_confidence_count": low_conf_count
607
+ },
608
  "landmarks": [{
609
  "id": lm["id"],
610
  "name": lm["name"],
611
+ "abbrev": lm["abbrev"],
612
  "x": lm["x"],
613
+ "y": lm["y"],
614
+ "confidence": round(lm["confidence"] * 100, 1)
615
  } for lm in landmarks]
616
  }
617
 
 
622
  return None, f"Error: {e}\n{traceback.format_exc()}"
623
 
624
  print("=" * 50)
625
+ print("Cephalometric Landmark Detection v1.1")
626
+ print("Con indicadores de confianza")
627
  print("=" * 50)
628
  load_model()
629
 
630
+ with gr.Blocks(title="Cephalometric Landmark Detection") as demo:
631
  gr.Markdown("""
632
  # 🦷 Detección de Landmarks Cefalométricos
633
 
634
  Detección automática de **19 puntos anatómicos** en radiografías cefalométricas laterales usando HRNet-W32.
635
+
636
+ **Nuevo:** Indicadores de confianza para cada punto detectado.
637
  """)
638
 
639
  with gr.Row():
 
646
  output_image = gr.Image(label="📍 Resultado con Leyenda", height=500)
647
 
648
  with gr.Accordion("📋 Datos JSON", open=False):
649
+ output_json = gr.Code(label="Coordenadas y Confianza", language="json", lines=15)
650
 
651
  gr.Markdown("""
652
  ---
 
659
  | 🟣 | Mandibular | Point B, Pogonion, Menton, Gnathion, Gonion |
660
  | 🟡 | Dental | Upper Incisor (U1), Lower Incisor (L1) |
661
  | 🩵 | Tejido Blando | Upper Lip, Lower Lip, Subnasale, Soft Tissue Pog |
662
+
663
+ ### 🎯 Indicadores de Confianza
664
+ | Indicador | Significado |
665
+ |-----------|-------------|
666
+ | ✓ (verde) | Alta confianza (≥80%) - Punto bien detectado |
667
+ | ~ (amarillo) | Confianza media (50-79%) - Verificar posición |
668
+ | ! (rojo) | Baja confianza (<50%) - Requiere revisión manual |
669
  """)
670
 
671
  detect_btn.click(fn=process_image, inputs=[input_image, show_labels], outputs=[output_image, output_json])