herdnet-app / app.py
jaimevera1107's picture
Versi贸n final lista para Hugging Face Space
d04393d
import warnings
import gradio as gr
from PIL import Image
import pandas as pd
import os
from inference.herdnet_infer import HerdNetInference
from inference.utils_io import load_yaml_config, mkdir
from animaloc.utils.seed import set_seed
# Ignorar advertencias no cr铆ticas
warnings.filterwarnings(
"ignore",
message="Got processor for keypoints, but no transform to process it",
)
# Fijar semilla para reproducibilidad
set_seed(9292)
# ===============================================================
# Configuraci贸n inicial
# ===============================================================
CONFIG_PATH = "resources/configs/default.yaml"
cfg = load_yaml_config(CONFIG_PATH)
mkdir(cfg["paths"]["uploads_dir"])
print("[INIT] Cargando modelo HerdNet... esto puede tardar unos segundos.")
infer_engine = HerdNetInference(CONFIG_PATH)
print("[READY] Modelo cargado y listo para inferencia.")
# Informaci贸n del modelo (actualizada con tablas)
MODEL_INFO = """
### Arquitectura y datos
- **Modelo:** HerdNet (FPN + Density Maps)
- **Dataset:** ULi猫ge-AIR (6 especies + background)
- **脷ltima actualizaci贸n:** 07 Nov 2025
### Desempe帽o general (Fine-Tuning oficial)
| M茅trica | Valor |
|--------------|---------|
| F1-score | 0.8405 |
| Precision | 0.8407 |
| Recall | 0.8404 |
| MAE | 1.8023 |
| RMSE | 3.4892 |
### Matriz de confusi贸n (normalizada)
| Real \\ Predicha | buffalo | elephant | kob | topi | warthog | waterbuck |
|------------------|----------|-----------|------|------|----------|-------------|
| **buffalo** | 0.94 | 0.00 | 0.05 | 0.01 | 0.00 | 0.00 |
| **elephant** | 0.01 | 0.91 | 0.00 | 0.07 | 0.01 | 0.00 |
| **kob** | 0.08 | 0.00 | 0.92 | 0.00 | 0.00 | 0.00 |
| **topi** | 0.03 | 0.00 | 0.00 | 0.94 | 0.03 | 0.00 |
| **warthog** | 0.06 | 0.06 | 0.06 | 0.00 | 0.81 | 0.00 |
| **waterbuck** | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 1.00 |
"""
def run_inference(image: Image.Image):
"""
Ejecuta la inferencia sobre una imagen PIL y devuelve los resultados.
"""
if image is None:
return None, pd.DataFrame(), None, None
annotated_img, counts = infer_engine.infer_single(image)
# Construir tabla completa de especies
all_species = list(infer_engine.classes.values())
df_counts = pd.DataFrame({
"Especie": all_species,
"Conteo": [counts.get(sp, 0) for sp in all_species],
})
df_counts.loc[len(df_counts)] = ["Total", df_counts["Conteo"].sum()]
# Guardar conteos
csv_counts_path = os.path.join(infer_engine.output_dir, "species_counts.csv")
df_counts.to_csv(csv_counts_path, index=False)
# Comprobar existencia de detecciones
detections_csv = os.path.join(infer_engine.output_dir, "detections.csv")
if not os.path.exists(detections_csv):
detections_csv = None
return annotated_img, df_counts, csv_counts_path, detections_csv
# ===============================================================
# Interfaz de Gradio (versi贸n est茅tica mejorada)
# ===============================================================
custom_css = """
#main-title h1 {
font-size: 2.2em !important;
color: #e0f2fe !important;
font-weight: 700 !important;
margin-bottom: 0.3em;
}
h2 {
color: #93c5fd !important;
font-weight: 600 !important;
margin-top: 1em;
margin-bottom: 0.3em;
}
.block-section {
background-color: #0f172a;
border-radius: 10px;
padding: 15px 20px;
margin-bottom: 25px;
box-shadow: 0 0 10px #00000040;
}
.img-bordered img {
border-radius: 10px;
box-shadow: 0 0 10px #00000040;
}
.data-table table {
font-size: 15px !important;
}
.download-btn {
background-color: #1e3a8a !important;
color: #f8fafc !important;
font-weight: 600 !important;
border-radius: 8px !important;
padding: 8px 14px !important;
border: 1px solid #3b82f6 !important;
transition: all 0.2s ease-in-out;
}
.download-btn:hover {
background-color: #2563eb !important;
transform: scale(1.05);
}
.big-button button {
background-color: #1d4ed8 !important;
color: #f8fafc !important;
font-weight: 700 !important;
font-size: 20px !important;
padding: 16px 28px !important;
border-radius: 10px !important;
width: 100% !important;
transition: all 0.25s ease-in-out;
}
.big-button button:hover {
background-color: #2563eb !important;
transform: scale(1.03);
}
"""
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),
css=custom_css,
) as demo:
# Encabezado principal
gr.Markdown("# Detecci贸n y Conteo de Mam铆feros Africanos", elem_id="main-title")
# Informaci贸n del modelo (colapsable con tablas)
with gr.Accordion("Informaci贸n del modelo", open=False):
gr.Markdown(MODEL_INFO)
# Bloque: resultados visuales
gr.Markdown("## Resultados de inferencia")
with gr.Row(elem_classes=["block-section"]):
image_input = gr.Image(
type="pil",
label="Subir imagen a茅rea",
height=380,
elem_classes=["img-bordered"],
)
image_output = gr.Image(
label="Detecciones (puntos resaltados)",
height=380,
elem_classes=["img-bordered"],
)
# Bot贸n principal m谩s grande y visible
btn = gr.Button(
"Ejecutar detecci贸n y conteo",
variant="primary",
elem_classes=["big-button"],
)
# Bloque: conteo detallado
gr.Markdown("## Conteo detallado por especie")
with gr.Column(elem_classes=["block-section"]):
counts_output = gr.Dataframe(
headers=["Especie", "Conteo"],
label="Resultados de detecci贸n",
interactive=False,
elem_classes=["data-table"],
)
with gr.Row():
download_counts = gr.File(
label="Descargar conteos (CSV)",
elem_classes=["download-btn"],
)
download_detections = gr.File(
label="Descargar anotaciones (detections.csv)",
elem_classes=["download-btn"],
)
btn.click(
fn=run_inference,
inputs=image_input,
outputs=[image_output, counts_output, download_counts, download_detections],
)
if __name__ == "__main__":
demo.launch(share=False, server_port=7860)