""" app.py – Clasificador de huellas (EfficientNet / ResNet) + ASR español """ import gradio as gr import numpy as np import tensorflow as tf import torch import torch.nn as nn import torchvision.transforms as T import torchvision.models as tv_models import librosa import soundfile as sf import os # -------------------- Configuración general -------------------- IMG_SIZE = 224 DEVICE = "cuda" if torch.cuda.is_available() else "cpu" FINGERS_EN = ["index", "little", "middle", "ring", "thumb"] FINGERS_ES = ["índice", "meñique", "medio", "anular", "pulgar"] FINGER_MAP_ES = dict(zip(FINGERS_EN, FINGERS_ES)) HANDS_ES = ["izquierda", "derecha"] RESNET_CLASSES = [ "Left_index", "Left_little", "Left_middle", "Left_ring", "Left_thumb", "Right_index", "Right_little", "Right_middle", "Right_ring", "Right_thumb", ] TORCH_MEAN = [0.485, 0.456, 0.406] TORCH_STD = [0.229, 0.224, 0.225] torch_tfms = T.Compose([ T.Grayscale(num_output_channels=3), T.Resize((IMG_SIZE, IMG_SIZE)), T.ToTensor(), T.Normalize(TORCH_MEAN, TORCH_STD), ]) # ---------- ASR parámetros (idénticos al entrenamiento) ---------- FRAME_LENGTH = 256 FRAME_STEP = 160 FFT_LENGTH = 384 TARGET_SR = 16_000 CHARS = [c for c in "abcdefghijklmnopqrstuvwxyzáéíóúüñ'?! "] char_to_num = tf.keras.layers.StringLookup(vocabulary=CHARS, oov_token="") num_to_char = tf.keras.layers.StringLookup( vocabulary=char_to_num.get_vocabulary(), oov_token="", invert=True ) # -------------------- Carga diferida de modelos -------------------- _models_cache = {} def _load_efficientnet(): return tf.keras.models.load_model("models/fingerprint_model_EfficientNet.keras") def _load_resnet(): model = tv_models.resnet18(weights=tv_models.ResNet18_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, len(RESNET_CLASSES)) model.load_state_dict(torch.load("models/resnet.pt", map_location=DEVICE)) model.eval().to(DEVICE) return model def CTCLoss(y_true, y_pred): b = tf.cast(tf.shape(y_true)[0], dtype="int64") t = tf.cast(tf.shape(y_pred)[1], dtype="int64") l = tf.cast(tf.shape(y_true)[1], dtype="int64") t = t * tf.ones(shape=(b, 1), dtype="int64") l = l * tf.ones(shape=(b, 1), dtype="int64") return tf.keras.backend.ctc_batch_cost(y_true, y_pred, t, l) def _load_asr(): return tf.keras.models.load_model("models/audio.keras", custom_objects={"CTCLoss": CTCLoss}) def _get_model(name): if name not in _models_cache: _models_cache[name] = ( _load_efficientnet() if name == "EfficientNet" else _load_resnet() if name == "ResNet" else _load_asr() if name == "ASR" else None ) return _models_cache[name] # -------------------- Clasificación de imágenes -------------------- def classify_fingerprint(image, model_name): if image is None: return "⚠️ Sube una imagen primero." # --- EfficientNet --- if model_name == "EfficientNet": model = _get_model("EfficientNet") img = image.convert("RGB").resize((IMG_SIZE, IMG_SIZE)) arr = tf.keras.applications.efficientnet.preprocess_input( tf.keras.utils.img_to_array(img) )[None, ...] preds = model.predict(arr, verbose=0) finger_probs = np.squeeze(preds[0] if isinstance(preds, (list, tuple)) else preds) idx = int(finger_probs.argmax()) finger_es = FINGERS_ES[idx] conf = finger_probs[idx] hand_es = "N/A" if isinstance(preds, (list, tuple)) and len(preds) > 1: hand_es = HANDS_ES[int(np.squeeze(preds[1]).argmax())] return f"Dedo: {finger_es}\nMano: {hand_es}\nConfianza: {conf:.2%}" # --- ResNet --- if model_name == "ResNet": model = _get_model("ResNet") tensor = torch_tfms(image).unsqueeze(0).to(DEVICE) with torch.no_grad(): probs = torch.softmax(model(tensor), 1)[0].cpu().numpy() idx = int(probs.argmax()) conf = probs[idx] hand_en, finger_en = RESNET_CLASSES[idx].split('_') return ( f"Dedo: {FINGER_MAP_ES[finger_en]}\n" f"Mano: {HANDS_ES[0] if hand_en=='Left' else HANDS_ES[1]}\n" f"Confianza: {conf:.2%}" ) return "🔧 Modelo no reconocido" # -------------------- Transcripción de audio -------------------- def _load_audio_16k(path): audio, sr = sf.read(path) if audio.ndim > 1: audio = audio.mean(axis=1) if sr != TARGET_SR: audio = librosa.resample(audio, orig_sr=sr, target_sr=TARGET_SR) return audio.astype("float32") def _make_spectrogram(path): audio = _load_audio_16k(path) spec = np.abs(librosa.stft(audio, n_fft=FFT_LENGTH, hop_length=FRAME_STEP, win_length=FRAME_LENGTH)) ** 0.5 # (freq, time) spec = spec.T # (time, freq) # 🔑 Normalización por FILA (freq-axis) como en entrenamiento means = spec.mean(axis=1, keepdims=True) # (time, 1) stds = spec.std(axis=1, keepdims=True) + 1e-10 # (time, 1) return ((spec - means) / stds).astype("float32") # (time, freq) def _decode_predictions(pred): decoded, _ = tf.keras.backend.ctc_decode( pred, input_length=np.ones(pred.shape[0]) * pred.shape[1], greedy=True ) seq = decoded[0][0] return tf.strings.reduce_join(num_to_char(seq)).numpy().decode("utf-8").strip() def transcribe_audio(audio_path): if not audio_path or not os.path.exists(audio_path): return "⚠️ Sube o graba un audio primero." model = _get_model("ASR") spec = _make_spectrogram(audio_path) pred = model.predict(spec[None, ...], verbose=0) text = _decode_predictions(pred) return text if text else "(vacío)" # -------------------- Interfaz Gradio -------------------- with gr.Blocks() as demo: gr.Markdown("# 🤖 Aplicación Multimodal con Deep Learning") with gr.Tab("📷 Clasificación de Imágenes"): image_input = gr.Image(type="pil", label="📤 Imagen de entrada") image_model = gr.Dropdown(["ResNet", "EfficientNet"], value="ResNet", label="Selecciona el modelo") image_output = gr.Textbox(label="📈 Resultado") gr.Button("Clasificar Imagen").click( classify_fingerprint, inputs=[image_input, image_model], outputs=image_output, ) with gr.Tab("🎙️ Reconocimiento de Voz"): audio_input = gr.Audio(type="filepath", label="🎧 Audio de entrada") audio_output = gr.Textbox(label="📝 Texto transcrito") gr.Button("Transcribir Audio").click( transcribe_audio, inputs=audio_input, outputs=audio_output, ) demo.launch()