{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":20561,"databundleVersionId":1187889,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"deepnote_notebook_id":"e3f1277953744970b419605bd6814e14"},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Reto COVID-19","metadata":{"cell_id":"6b1432044b0c443e934cf8a292bc9a71","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"markdown","source":"Equipo Concentrados:","metadata":{"cell_id":"f9e0f029b8cd4ddcae9475b63ee60b20","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"- Diego Isaac Fuentes Juvera - A01705506","metadata":{"cell_id":"bfe547f5528844179064211159cce0ae","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"- Daniel Queijeiro Albo - A01710441","metadata":{"cell_id":"f51d1ce29ac7407eabcfca31886712df","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"- Diego Ricardo Alfaro Pinto - A01709971","metadata":{"cell_id":"d8cadfec495a47f5a74ba855d7316471","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"- Mauricio Anguiano Juarez - A01703337","metadata":{"cell_id":"83b90a5955b04c349070104e314f8e81","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"# Introducción al problema","metadata":{"cell_id":"f79480497b1449dd9e0cd2a7ddbcf9e7","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"markdown","source":"El reto de Kaggle COVID-19 CT Images Segmentation busca desarrollar modelos capaces de segmentar en tomografías computarizadas las regiones pulmonares afectadas por COVID-19. El problema no se limita a detectar la enfermedad, sino a identificar de manera precisa dónde aparecen las lesiones y cuál es su extensión dentro del pulmón.","metadata":{"cell_id":"3a5c50e6d1254fa888175b56409c7fd9","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"El objetivo de la competencia es impulsar algoritmos de segmentación que apoyen al diagnóstico médico y a la evaluación de la progresión del virus. La relevancia de esta tarea radica en que una segmentación confiable permite cuantificar de forma objetiva el daño pulmonar y agilizar la atención en contextos de alta demanda clínica.","metadata":{"cell_id":"f144dca9755a4dbca584bce4f696e5fc","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"El dataset es reducido, con apenas 100 imágenes anotadas, lo que plantea un reto en cuanto a generalización y obliga a aplicar técnicas como aumento de datos o transfer learning. Muchos participantes trabajan sus soluciones en Jupyter Notebooks, ya que facilitan el desarrollo, la visualización y la reproducibilidad de los experimentos. En conjunto, la competencia ilustra cómo la inteligencia artificial puede ser una herramienta útil para enfrentar emergencias sanitarias y apoyar la práctica médica.\n","metadata":{"cell_id":"a185ece0f4e349549b800b9fef3830dc","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Cada pixel de cada imagen cuenta con una etiqueta descriptiva, la cual puede indicar que lo que vemos corresponde a uno de los siguientes hallazgos:","metadata":{"cell_id":"735951ee5245462e83b4e8bf4cf67ad5","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"- Ground glass: Un área de mayor opacidad dentro del pulmón, puede indicar inflamación, edema, colapso parcial, o hemorragia. Es un hallazgo común en las fases iniciales del COVID-19","metadata":{"cell_id":"33bb61f583444828bf7ffac47657d24e","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"- Consolidation: Área de opacidad máxima donde no se distinguen los vasos sanguíneos que conforman el pulmón. Representa un colpaso de los alviolos con prescencia de líquido dentro de los pulmones. Se presenta en neumonías bacterianas o en fases avanzadas del COVID-19","metadata":{"cell_id":"274d48f0e3784be1bdabbcb37e1fd9bc","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"- Pulmón/Fondo.","metadata":{"cell_id":"e47ae0d9ba6f436888e1885748974807","deepnote_cell_type":"text-cell-bullet","formattedRanges":[]}},{"cell_type":"markdown","source":"El objetivo principal es clasificar correctamente las áreas con ground glass o consolidation para poder alertar sobre posibles enfermedades, intentando reducir al máximo los falsos positivos.","metadata":{"cell_id":"048eb30bf5814f169f04b58649a1694e","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"## Implicaciones éticas","metadata":{"cell_id":"df94f0b390a948d880dc728c90c434d5","deepnote_cell_type":"text-cell-h2","formattedRanges":[]}},{"cell_type":"markdown","source":"El uso de imágenes médicas para entrenar un modelo de machine learning con el fin de predecir COVID-19 plantea varias implicaciones éticas que deben considerarse cuidadosamente. En primer lugar, la privacidad y la protección de los datos de los pacientes representan un aspecto central. Las imágenes médicas contienen información altamente sensible, por lo que es fundamental garantizar que los datos se anonimicen adecuadamente, se almacenen de manera segura y solo se utilicen con fines autorizados. El consentimiento informado de los pacientes es igualmente relevante, ya que ellos deben comprender cómo se emplearán sus datos y qué riesgos o beneficios implica su participación.","metadata":{"cell_id":"3302d9e401954b45ae830eeb694b4fe8","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Otro punto crítico es la equidad y la representatividad del conjunto de datos. Si las imágenes utilizadas provienen de poblaciones limitadas o desbalanceadas, el modelo podría desarrollar sesgos que afecten negativamente a ciertos grupos de pacientes, lo que podría traducirse en diagnósticos menos precisos o en una atención desigual. Esto implica la responsabilidad de los investigadores de evaluar la diversidad de los datos y de implementar técnicas que reduzcan los sesgos.","metadata":{"cell_id":"6e89828741f6409d91a8e095cc1687ab","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Además, existe la cuestión de la transparencia y la interpretabilidad del modelo. En el ámbito médico, las decisiones basadas en algoritmos deben ser comprensibles para los profesionales de la salud, de modo que puedan respaldar sus juicios clínicos y no depender ciegamente de una “caja negra”. La rendición de cuentas también es clave: se debe establecer quién es responsable en caso de errores diagnósticos derivados del uso del modelo.","metadata":{"cell_id":"b7a75ad33e1243b191326243f9d6500d","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Por último, es necesario reflexionar sobre el propósito final del proyecto. Aunque la intención de mejorar la detección de COVID-19 es positiva, debe evitarse que el modelo se utilice de manera inapropiada o fuera de contexto clínico. El desarrollo de estas herramientas debe estar siempre orientado al beneficio del paciente y al fortalecimiento del sistema de salud, respetando principios de justicia, beneficencia y no maleficencia.","metadata":{"cell_id":"e45726c6efa9458a89a8042a5eafdd04","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"## Análisis de Datos\n\n### ETL/EDA\n\n#### Conjunto de Datos\n\nEl dataset proporcionado en la competencia presenta las siguientes características:\n\n- **Fuentes de datos**:\n  - **Radiopedia**: Conjunto de imágenes de referencia\n  - **MedSeg**: Conjunto principal de entrenamiento y validación\n  - **Test MedSeg**: 10 imágenes para evaluación final\n\n- **Dimensiones**:\n  - Imágenes originales: 512x512 píxeles\n  - Imágenes procesadas: 256x256 píxeles (redimensionadas)\n  - Canal único (escala de grises)\n\n- **Número de muestras**:\n  - Total de imágenes de entrenamiento: ~100 imágenes anotadas\n  - Imágenes de validación: 24 imágenes (del conjunto MedSeg)\n  - Imágenes de prueba: 10 imágenes\n\n- **Clases**: 4 clases (Ground glass, Consolidation, Pulmón normal, Fondo)\n\n#### Visualización de Datos\n\nEl análisis exploratorio incluyó la visualización de ejemplos representativos de las imágenes y sus máscaras de segmentación correspondientes, permitiendo entender la distribución de las clases y la calidad de las anotaciones.\n\n### Preprocesamiento de Datos\n\n#### Normalización y Limpieza\n\n1. **Clipping de valores atípicos**:\n   ```python\n   images_arr = np.clip(images_arr, -1500, 500)\n   ```\n   - Se limitaron los valores de intensidad para eliminar outliers extremos\n\n2. **Normalización estadística**:\n   - Cálculo del percentil 5 y 95 para determinar el rango válido de intensidades\n   - Normalización Z-score usando media y desviación estándar de píxeles válidos\n   - Fórmula: `(pixel - mean) / std`\n\n#### Conversión de Máscaras\n\n- Conversión de máscaras one-hot encoded a single-channel usando una paleta de colores\n- Transformación de máscaras single-channel de vuelta a one-hot para entrenamiento\n\n#### Técnicas de Aumento de Datos (Data Augmentation)\n\nPara abordar el dataset limitado de 100 imágenes, se aplicaron las siguientes transformaciones usando la librería Albumentations:\n\n1. **Rotación**:\n   - Rango: hasta 360 grados\n   - Probabilidad: 90%\n   - Relleno de bordes: BORDER_REPLICATE\n\n2. **Recorte aleatorio redimensionado (RandomSizedCrop)**:\n   - Tamaño mínimo: 75% del tamaño original (384x384)\n   - Tamaño máximo: 100% del tamaño original (512x512)\n   - Redimensionado final: 256x256\n   - Interpolación: INTER_NEAREST (para preservar las etiquetas)\n\n3. **Reflejo horizontal**:\n   - Probabilidad: 50%\n\n---","metadata":{}},{"cell_type":"code","source":"import torch\n\n# Diagnóstico completo de GPU\nprint(\"=== DIAGNÓSTICO GPU ===\")\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA disponible: {torch.cuda.is_available()}\")\nprint(f\"Versión CUDA compilada con PyTorch: {torch.version.cuda}\")\n\nif torch.cuda.is_available():\n    print(f\"Número de GPUs disponibles: {torch.cuda.device_count()}\")\n    print(f\"GPU actual: {torch.cuda.current_device()}\")\n    print(f\"Nombre de la GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"Memoria total: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\")\nelse:\n    print(\"❌ CUDA no está disponible\")\n\n# Configuración del dispositivo\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Dispositivo seleccionado: {device}\")\n\n# Test básico\nif device.type == 'cuda':\n    test_tensor = torch.randn(3, 3).to(device)\n    print(\"✅ Test de tensor en GPU exitoso\")\nelse:\n    print(\"⚠️ Usando CPU\")","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:24:11.403449Z","iopub.execute_input":"2025-09-18T06:24:11.403771Z","iopub.status.idle":"2025-09-18T06:24:13.235531Z","shell.execute_reply.started":"2025-09-18T06:24:11.403752Z","shell.execute_reply":"2025-09-18T06:24:13.234691Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport os","metadata":{"cell_id":"48c339ba4655432db689ca2c24626f3f","deepnote_cell_type":"code","execution":{"iopub.status.busy":"2025-09-18T06:24:13.236551Z","iopub.execute_input":"2025-09-18T06:24:13.237033Z","iopub.status.idle":"2025-09-18T06:24:14.056569Z","shell.execute_reply.started":"2025-09-18T06:24:13.237005Z","shell.execute_reply":"2025-09-18T06:24:14.055777Z"},"execution_context_id":"cd1c139d-9988-413d-94dd-bad445b08cc2","execution_millis":3539,"execution_start":1758138121831,"source_hash":"ed31445a","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"prefix = '/kaggle/input/covid-segmentation/'\n\nimages_radiopedia = np.load(os.path.join(prefix, 'images_radiopedia.npy')).astype(np.float32)\nmasks_radiopedia = np.load(os.path.join(prefix, 'masks_radiopedia.npy')).astype(np.int8)\nimages_medseg = np.load(os.path.join(prefix, 'images_medseg.npy')).astype(np.float32)\nmasks_medseg = np.load(os.path.join(prefix, 'masks_medseg.npy')).astype(np.int8)\n\ntest_images_medseg = np.load(os.path.join(prefix, 'test_images_medseg.npy')).astype(np.float32)","metadata":{"cell_id":"e148bd53fbee414ba9106bf9e8133b80","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:14.057487Z","iopub.execute_input":"2025-09-18T06:24:14.057860Z","iopub.status.idle":"2025-09-18T06:24:15.665710Z","shell.execute_reply.started":"2025-09-18T06:24:14.057832Z","shell.execute_reply":"2025-09-18T06:24:15.665098Z"},"execution_context_id":"4b2b41c3-2027-4e70-bb8c-32a43a0b7c90","execution_millis":2618,"execution_start":1758130969533,"source_hash":"99840565","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize(image_batch, mask_batch=None, pred_batch=None, num_samples=8, hot_encode=True):\n    print(\"Corriendo funcion visualize\")\n    \n    # Move tensors to CPU and convert to numpy if needed\n    if torch.is_tensor(image_batch):\n        image_batch = image_batch.cpu().numpy()\n    if mask_batch is not None and torch.is_tensor(mask_batch):\n        mask_batch = mask_batch.cpu().numpy()\n    if pred_batch is not None and torch.is_tensor(pred_batch):\n        pred_batch = pred_batch.cpu().numpy()\n    \n    num_classes = mask_batch.shape[-1] if mask_batch is not None else 0\n    fix, ax = plt.subplots(num_classes + 1, num_samples, figsize=(num_samples * 2, (num_classes + 1) * 2))\n\n    for i in range(num_samples):\n        ax_image = ax[0, i] if num_classes > 0 else ax[i]\n        if hot_encode: \n            ax_image.imshow(image_batch[i,:,:,0], cmap='Greys')\n        else: \n            ax_image.imshow(image_batch[i,:,:])\n        ax_image.set_xticks([]) \n        ax_image.set_yticks([])\n        \n        if mask_batch is not None:\n            for j in range(num_classes):\n                if pred_batch is None:\n                    mask_to_show = mask_batch[i,:,:,j]\n                else:\n                    mask_to_show = np.zeros(shape=(*mask_batch.shape[1:-1], 3)) \n                    mask_to_show[..., 0] = pred_batch[i,:,:,j] > 0.5\n                    mask_to_show[..., 1] = mask_batch[i,:,:,j]\n                ax[j + 1, i].imshow(mask_to_show, vmin=0, vmax=1)\n                ax[j + 1, i].set_xticks([]) \n                ax[j + 1, i].set_yticks([]) \n\n    plt.tight_layout()\n    plt.show()","metadata":{"cell_id":"0af364f94b404cddadaac305275be69b","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:15.666430Z","iopub.execute_input":"2025-09-18T06:24:15.666633Z","iopub.status.idle":"2025-09-18T06:24:15.674513Z","shell.execute_reply.started":"2025-09-18T06:24:15.666617Z","shell.execute_reply":"2025-09-18T06:24:15.673839Z"},"execution_context_id":"4b2b41c3-2027-4e70-bb8c-32a43a0b7c90","execution_millis":0,"execution_start":1758130973083,"source_hash":"29f05424","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize(images_radiopedia[30:], masks_radiopedia[30:])","metadata":{"cell_id":"2b2237da85ad4f879c5e9e9d5fb07667","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:15.675245Z","iopub.execute_input":"2025-09-18T06:24:15.675520Z","iopub.status.idle":"2025-09-18T06:24:17.735559Z","shell.execute_reply.started":"2025-09-18T06:24:15.675496Z","shell.execute_reply":"2025-09-18T06:24:17.734864Z"},"execution_context_id":"4b2b41c3-2027-4e70-bb8c-32a43a0b7c90","execution_millis":1280,"execution_start":1758130975790,"source_hash":"f17e5b4b","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nusa una mascara que esta one-hot encoded y la convierte a una single-channel mask usando un color o paleta\nde clases\n\nInput: \n    mask: height width numero_de_clases\ncada pixel es representada como un one-hot vector de largo K\n    palette: una lista de colores para cada clase\n\nMasks: una manera de label pixeles en una imagen para indicar que pixeles son de que clase u objeto\n\nSingle-channel masks: una mask en la que cada pixel contiene una class label como un solo numero, solo tiene 2 dimensiones\n\nOne-hot encoded masks: es una manera de representar datos categoricos como un vector, usando un vector de largo K para cada pixel\ndonde K es el numuero de clases\n\"\"\"\n\ndef onehot_to_mask(mask, palette):\n    print(\"Corriendo funcion onehot_to_mask\")\n    indices = mask.argmax(axis=-1) # por cada pixel (i,j) encuentra el indice del maximo valor en el eje K, como es onehot encuentra la clase en la que el pixel esta\n    colors = np.array(palette, dtype=np.uint8) \n    return np.take(colors, indices, axis=0) # np.take selecciona elementos en el axis, como colors tiene shape (K,3) e indices (H,W) cambia los indices con su fila correspondiente de colors\n    # retorna (H,W,3) como si fuera una mascara RGB\n    # 1 es escala de grises, 3 es RGB\n    \npalette = [[0], [1], [2],[3]]\n\nmasks_radiopedia_recover = onehot_to_mask(masks_radiopedia, palette).squeeze()  # shape = (H, W)\n\nmasks_medseg_recover = onehot_to_mask(masks_medseg, palette).squeeze()  # shape = (H, W)\n\nprint('Hot encoded mask size: ',masks_radiopedia.shape)\nprint('Paletted mask size:',masks_medseg_recover.shape)\n\nvisualize(masks_medseg_recover[30:],hot_encode=False)\n\n","metadata":{"cell_id":"5f815724a187401988f019309e218283","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:17.738408Z","iopub.execute_input":"2025-09-18T06:24:17.738665Z","iopub.status.idle":"2025-09-18T06:24:21.769913Z","shell.execute_reply.started":"2025-09-18T06:24:17.738644Z","shell.execute_reply":"2025-09-18T06:24:21.769166Z"},"execution_context_id":"4b2b41c3-2027-4e70-bb8c-32a43a0b7c90","execution_millis":2197,"execution_start":1758130981587,"source_hash":"ffad795","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nFuncion para preprocesar funciones \n\nRecibe un array de Numpy de imagenes y mean_std para normalizacion si se le paso no se calcula en la funcion\n\nremueve los outliers con np.clip\n\ncomputa el 5to y 95to percentil del array de imagenes\n\ncrea images_arr_valid que contiene solo los pixels en medio de esos percentiles para enfocarse en los rangos de intensidad tipicos\ne ignora los extremos\n\ncomputa los mean y std si no se le paso a la funcion\n\nnormaliza las imagenes usando pixel_normalizado = pixel - mean / std\n\"\"\"\n\ndef preprocess_images(images_arr, mean_std=None):\n    print(\"Corriendo funcion preprocess_images\")\n    images_arr = np.clip(images_arr, -1500, 500)\n\n    # Normalizar los valores de 0 a 1\n    # min_val = images_arr.min()\n    # max_val = images_arr.max()\n    # images_arr = (images_arr - min_val) / (max_val - min_val)\n    \n    low,high = np.percentile(images_arr, [5,95])\n    valid_pixels = images_arr[(images_arr > low) & (images_arr < high)]\n\n    if mean_std is None:\n        mean,std = valid_pixels.mean(), valid_pixels.std()\n    else:\n        mean,std = mean_std\n    \n    images_arr = (images_arr - mean) / std\n\n    return images_arr, (mean,std)\n\nimages_radiopedia, mean_std = preprocess_images(images_radiopedia)\nimages_medseg, _ = preprocess_images(images_medseg, mean_std)\ntest_images_medseg, _ = preprocess_images(test_images_medseg, mean_std)","metadata":{"cell_id":"cbbcda3f824041e9a091cfca2e938062","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:21.770679Z","iopub.execute_input":"2025-09-18T06:24:21.770879Z","iopub.status.idle":"2025-09-18T06:24:27.627600Z","shell.execute_reply.started":"2025-09-18T06:24:21.770862Z","shell.execute_reply":"2025-09-18T06:24:27.626887Z"},"execution_context_id":"4b2b41c3-2027-4e70-bb8c-32a43a0b7c90","execution_millis":320,"execution_start":1758130990469,"source_hash":"42ff823f","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nConvierte radiopedia y medseg a las one-hot encoded masks para usar el segmentation\n\ncreamos los indices para crear validation y training\n\ncrear los datasets usando los indexes\n\nlos datasets estan combinados usando imagenes: medseg y radiopedia, y las masks\n\nlos datasets de validation solo seran usando medseg\n\nle pone el tamano al batch y despues borra los arrays grandes para no comerse la ram\n\"\"\"\n\nmasks_radiopedia_recover = onehot_to_mask(masks_radiopedia, palette).squeeze()  # shape = (H, W)\nmasks_medseg_recover = onehot_to_mask(masks_medseg, palette).squeeze()  # shape = (H, W)\n\n\nval_indexes, train_indexes = list(range(24)), list(range(24, 100))\n\ntrain_images = np.concatenate((images_medseg[train_indexes], images_radiopedia))\ntrain_masks = np.concatenate((masks_medseg_recover[train_indexes], masks_radiopedia_recover))\nval_images = images_medseg[val_indexes]\nval_masks = masks_medseg_recover[val_indexes]\n\nbatch_size = len(val_masks)\n\ndel masks_medseg_recover\ndel masks_radiopedia_recover\ndel images_radiopedia\ndel masks_radiopedia\ndel images_medseg\ndel masks_medseg","metadata":{"cell_id":"7e0d6efd2a874810bb53110119d15fa5","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:27.628352Z","iopub.execute_input":"2025-09-18T06:24:27.628587Z","iopub.status.idle":"2025-09-18T06:24:31.718268Z","shell.execute_reply.started":"2025-09-18T06:24:27.628568Z","shell.execute_reply":"2025-09-18T06:24:31.717452Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nEste codigo crea un pipeline para el training y validation, usando la libreria albumentations \n\nAumentations transformaciones random que se le aplican a las imagenes para hacer el modelo mas robusto\n\nSe crea una pipeline con el compose:\n    .Rotate: rota la imagen hasta 360 grados con una probabilidad de 90%, cuando la rotacion hace que haya espacios en blanco\n             los rellena con los pixeles de los ejes en lugar de ponerlo en negro\n    .RandomSizedCrop: cropea una subregion de la imagen, el tamano del crop se escoge random entre el 75% y el 100% del tamano original de la imagen\n                      la region se hace otra vez al tamano del target\n    .HorizontalFlip: flipea la imagen horizontalmente en un 50%\n\n\"\"\"\n\nimport albumentations as A\nimport cv2\n\nSOURCE_SIZE = 512\nTARGET_SIZE = 256\n\ntrain_augs = A.Compose([\n    A.Rotate(limit=360, p=0.9, border_mode=cv2.BORDER_REPLICATE),\n    A.RandomSizedCrop(\n        min_max_height=(int(SOURCE_SIZE * 0.75), SOURCE_SIZE),\n        size=(TARGET_SIZE, TARGET_SIZE),  # Cambio: usar size en lugar de height/width separados\n        interpolation=cv2.INTER_NEAREST\n    ),\n    A.HorizontalFlip(p=0.5)\n])\n\nval_augs = A.Compose([\n    A.Resize(height=TARGET_SIZE, width=TARGET_SIZE, interpolation=cv2.INTER_NEAREST)\n])","metadata":{"cell_id":"77f089808b8a43cb8c9039a142ab06d5","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:31.719149Z","iopub.execute_input":"2025-09-18T06:24:31.719383Z","iopub.status.idle":"2025-09-18T06:24:32.487439Z","shell.execute_reply.started":"2025-09-18T06:24:31.719365Z","shell.execute_reply":"2025-09-18T06:24:32.486658Z"},"execution_context_id":"cd1c139d-9988-413d-94dd-bad445b08cc2","execution_millis":19913,"execution_start":1758136340110,"source_hash":"fe272c0c","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Modelo\n\n### Arquitectura del Modelo\n\n#### Selección de Arquitectura: U-Net con EfficientNet-B4\n\nSe eligió una arquitectura **U-Net** con encoder **EfficientNet-B4** por las siguientes razones:\n\n1. **U-Net**: Arquitectura especializada en segmentación semántica\n   - Estructura encoder-decoder con conexiones skip\n   - Preserva información espacial de alta resolución\n   - Excelente rendimiento en imágenes biomédicas\n\n2. **EfficientNet-B4**: Encoder preentrenado eficiente\n   - Balance óptimo entre precisión y eficiencia computacional\n   - Pesos preentrenados en ImageNet para transfer learning\n   - Arquitectura optimizada con compound scaling\n\n#### Especificaciones Técnicas\n\n```python\nmodel = smp.Unet(\n    'efficientnet-b4',\n    in_channels=1,           # Entrada en escala de grises\n    encoder_weights='imagenet',\n    classes=4,               # 4 clases de segmentación\n    activation=None,         # Sin activación (logits para CrossEntropyLoss)\n    encoder_depth=5,         # 5 niveles en el encoder\n    decoder_channels=[512, 256, 128, 64, 32]  # Canales del decoder\n)\n```\n\n**Detalles de la arquitectura**:\n\n- **Encoder (EfficientNet-B4)**:\n  - 5 niveles de profundidad\n  - Kernels de convolución: 3x3 y 1x1 (depthwise separable)\n  - Stride: variable según el nivel\n  - Padding: same padding para preservar dimensiones\n  - Activación: Swish (SiLU)\n\n- **Decoder (U-Net)**:\n  - Upsampling progresivo: 512→256→128→64→32 canales\n  - Conexiones skip desde el encoder\n  - Convoluciones transpuestas para upsampling\n  - Kernel size: 3x3, stride: 1, padding: 1\n\n- **Conexiones Skip**: Concatenación de features del encoder con el decoder en cada nivel correspondiente\n\n### Referencias\n\n- **U-Net Original**: Ronneberger, O., Fischer, P., & Brox, T. (2015). U-Net: Convolutional networks for biomedical image segmentation. *arXiv preprint arXiv:1505.04597*.\n- **EfficientNet**: Tan, M., & Le, Q. (2019). EfficientNet: Rethinking model scaling for convolutional neural networks. *International Conference on Machine Learning*.\n\n---","metadata":{}},{"cell_type":"code","source":"import numpy as np  # Agregado: necesario para operaciones con arrays\nfrom PIL import Image  # numpy a PIL images\nimport segmentation_models_pytorch as smp  # libreria con modelos pre entrenados de segmentacion\nimport torch.nn.functional as F\nimport torch.nn as nn\nfrom tqdm.notebook import tqdm\nimport time\n\nfrom torchvision import transforms as T\nfrom torch.utils.data import Dataset as TorchDataset, DataLoader  # Renombrado para evitar conflictos\nimport torch\n\n\n\nclass SegmentationDataset(TorchDataset):  # Renombrado para evitar conflictos con Dataset de PyTorch\n    \"\"\"\n    Dataset personalizado para segmentación de imágenes\n    \"\"\"\n    def __init__(\n            self, \n            images, \n            masks,\n            augmentations=None,\n            mean=None,\n            std=None\n    ):\n        \"\"\"\n        Args:\n            images: lista o array de imagenes (numpy arrays)\n            masks: mascaras de segmentacion (numpy arrays)\n            augmentations: transformaciones usando albumentations (opcional)\n            mean: valores de media para normalización (default: [0.485] para grayscale)\n            std: valores de desviación estándar para normalización (default: [0.229] para grayscale)\n        \"\"\"\n        self.images = images\n        self.masks = masks\n        self.augmentations = augmentations\n        # Valores por defecto para imágenes en escala de grises\n        self.mean = mean if mean is not None else [0.485]\n        self.std = std if std is not None else [0.229]\n        \n    \n    def __getitem__(self, i):\n        \"\"\"\n        Obtiene la imagen y máscara en el índice i\n        Si las augmentations se pasan, las aplica \n        \n        Args:\n            i: índice de la muestra\n            \n        Returns:\n            tuple: (imagen_tensor, mascara_tensor)\n        \"\"\"\n        # Obtener imagen y máscara originales\n        image = self.images[i].copy()  # Copia para evitar modificar datos originales\n        mask = self.masks[i].copy()\n        \n        # Aplicar augmentaciones si están disponibles\n        if self.augmentations is not None:\n            # Aplica las augmentaciones usando albumentations\n            sample = self.augmentations(image=image, mask=mask)\n            \n            # Obtiene las imagenes y mascaras augmentadas\n            # np.squeeze remueve dimensiones unitarias y convierte a PIL image\n            augmented_image = sample['image']\n            augmented_mask = sample['mask']\n            \n            # Convertir a PIL Image, manejando diferentes casos de dimensiones\n            if len(augmented_image.shape) == 3 and augmented_image.shape[2] == 1:\n                # Imagen en escala de grises con canal unitario\n                image = Image.fromarray(np.squeeze(augmented_image, axis=2))\n            elif len(augmented_image.shape) == 2:\n                # Imagen en escala de grises sin canal\n                image = Image.fromarray(augmented_image)\n            else:\n                # Imagen a color\n                image = Image.fromarray(augmented_image)\n                \n            mask = augmented_mask\n        else:\n            # Si no hay augmentaciones, solo convierte numpy a PIL\n            if len(image.shape) == 3 and image.shape[2] == 1:\n                image = Image.fromarray(np.squeeze(image, axis=2))\n            elif len(image.shape) == 2:\n                image = Image.fromarray(image)\n            else:\n                image = Image.fromarray(image)\n        \n        # Pipeline de transformaciones: convierte PIL a tensor[C,H,W] y normaliza\n        transform_pipeline = T.Compose([\n            T.ToTensor(),\n            T.Normalize(self.mean, self.std)\n        ])\n        image_tensor = transform_pipeline(image)\n        \n        # Convierte la máscara a torch tensor de tipo long (requerido para CrossEntropyLoss)\n        mask_tensor = torch.from_numpy(mask).long()\n    \n        return image_tensor, mask_tensor\n    \n    def __len__(self):\n        \"\"\"\n        Retorna la cantidad total de muestras en el dataset\n        \"\"\"\n        return len(self.images)\n\n    def tiles(self, image, mask, tile_height=512, tile_width=768):\n        \"\"\"\n        Función para dividir imágenes o máscaras grandes en tiles más pequeños\n        \n        Args:\n            image: tensor de imagen [C, H, W]\n            mask: tensor de máscara [H, W]\n            tile_height: altura de cada tile (default: 512)\n            tile_width: ancho de cada tile (default: 768)\n            \n        Returns:\n            tuple: (image_patches, mask_patches)\n        \"\"\"\n        # Extrae ventanas deslizantes en las dimensiones de altura y ancho\n        # unfold(dimension, size, step) - extrae ventanas de tamaño 'size' con paso 'step'\n        img_patches = image.unfold(1, tile_height, tile_height).unfold(2, tile_width, tile_width)\n        \n        # Reorganiza las dimensiones: [C, n_patches_h, n_patches_w, tile_h, tile_w] -> [n_patches, C, tile_h, tile_w]\n        img_patches = img_patches.contiguous().view(image.shape[0], -1, tile_height, tile_width)\n        img_patches = img_patches.permute(1, 0, 2, 3)\n        \n        # Procesa las máscaras de la misma manera\n        mask_patches = mask.unfold(0, tile_height, tile_height).unfold(1, tile_width, tile_width)\n        mask_patches = mask_patches.contiguous().view(-1, tile_height, tile_width)\n        \n        return img_patches, mask_patches\n\ndef create_dataloaders(train_images, train_masks, val_images, val_masks, \n                      train_augs=None, val_augs=None, batch_size=4, \n                      num_workers=2, pin_memory=True):\n    \"\"\"\n    Función helper para crear los DataLoaders de entrenamiento y validación\n    \n    Args:\n        train_images, train_masks: datos de entrenamiento\n        val_images, val_masks: datos de validación\n        train_augs, val_augs: augmentaciones para entrenamiento y validación\n        batch_size: tamaño del batch\n        num_workers: número de workers para carga paralela\n        pin_memory: usar memoria pinned para transferencia GPU más rápida\n        \n    Returns:\n        tuple: (train_dataloader, val_dataloader)\n    \"\"\"\n\n    print(\"Corriendo funcion create_dataloaders\")\n\n    # Crea los datasets usando las augmentaciones correspondientes\n    train_dataset = SegmentationDataset(train_images, train_masks, train_augs)\n    val_dataset = SegmentationDataset(val_images, val_masks, val_augs)\n    \n    # Crea los dataloaders con configuraciones optimizadas\n    train_dataloader = DataLoader(\n        train_dataset, \n        batch_size=batch_size, \n        shuffle=True,           # Mezclar datos de entrenamiento\n        num_workers=num_workers,\n        pin_memory=pin_memory,  # Acelera transferencia a GPU\n        drop_last=True          # Evita batches incompletos en entrenamiento\n    )\n    \n    val_dataloader = DataLoader(\n        val_dataset, \n        batch_size=batch_size, \n        shuffle=False,          # No mezclar datos de validación\n        num_workers=num_workers,\n        pin_memory=pin_memory\n    )\n    \n    return train_dataloader, val_dataloader\n\n\ntrain_dataloader, val_dataloader = create_dataloaders(\n    train_images, train_masks, val_images, val_masks,\n    train_augs, val_augs, batch_size\n)\n","metadata":{"cell_id":"bcfd3463f8d94f8da7cd1a5520881335","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:32.488279Z","iopub.execute_input":"2025-09-18T06:24:32.488468Z","iopub.status.idle":"2025-09-18T06:24:34.768515Z","shell.execute_reply.started":"2025-09-18T06:24:32.488454Z","shell.execute_reply":"2025-09-18T06:24:34.767873Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef mask_to_onehot(mask, palette):\n    \"\"\"\n    Convierte una máscara de segmentación a formato one-hot usando una paleta de colores.\n    \n    Args:\n        mask: máscara con shape (H, W, C) donde C=1 (grises/IDs) o C=3 (RGB)\n        palette: lista de colores/valores que representan cada clase\n    \n    Returns:\n        torch.Tensor: máscara one-hot con shape (H, W, K) donde K es número de clases\n    \"\"\"\n    print(\"Corriendo funcion mask_to_onehot\")\n\n    # Acumulamos un mapa binario por cada clase (H, W)\n    semantic_map = []  \n\n    for colour in palette:\n        # Compara pixel a pixel si 'mask' es igual al color 'colour'\n        # np.equal hace comparación elemento por elemento\n        equality = np.equal(mask, colour)\n\n        # (H, W) booleano con True donde el pixel es exactamente 'colour'\n        # np.all colapsa el último eje (canal) - True solo si todos los canales coinciden\n        class_map = np.all(equality, axis=-1)\n\n        # Guardamos el mapa binario de esta clase\n        semantic_map.append(class_map)\n\n    # Apilamos a lo largo del último eje → (H, W, K) donde K = número de clases\n    # axis=-1 apila en la nueva dimensión de clases\n    semantic_map = np.stack(semantic_map, axis=-1).astype(np.float32)\n\n    # Devolvemos tensor de PyTorch listo para usar\n    return torch.from_numpy(semantic_map)\n","metadata":{"cell_id":"5bf01c0a254e4451ad67611edad07adf","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.769242Z","iopub.execute_input":"2025-09-18T06:24:34.769466Z","iopub.status.idle":"2025-09-18T06:24:34.774614Z","shell.execute_reply.started":"2025-09-18T06:24:34.769449Z","shell.execute_reply":"2025-09-18T06:24:34.773818Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\ndef pixel_accuracy(output, mask):\n    \"\"\"\n    Calcula la precisión por píxel para segmentación semántica.\n    \n    Args:\n        output: tensor de salida del modelo con shape [B, C, H, W] (logits)\n        mask: tensor con las máscaras ground truth con shape [B, H, W]\n    \n    Returns:\n        float: precisión por píxel (porcentaje de píxeles correctos)\n    \"\"\"\n    print(\"Corriendo funcion pixel_acurracy\")\n\n    # No se calculan gradientes durante la evaluación\n    with torch.no_grad():\n        # Predicción = clase con mayor probabilidad por píxel\n        # Convierte logits a probabilidades y selecciona la clase con mayor prob\n        output = F.softmax(output, dim=1)\n        output = torch.argmax(output, dim=1)\n\n        # Comparación con la máscara real (ground truth)\n        # torch.eq compara elemento por elemento y retorna True/False\n        correct = torch.eq(output, mask).int()\n\n        # Porcentaje de píxeles correctos\n        # .sum() cuenta píxeles correctos, .numel() cuenta total de píxeles\n        accuracy = float(correct.sum()) / float(correct.numel())\n\n    return accuracy","metadata":{"cell_id":"d2739bca3f054198803961eb2d0330a9","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.775431Z","iopub.execute_input":"2025-09-18T06:24:34.775625Z","iopub.status.idle":"2025-09-18T06:24:34.795857Z","shell.execute_reply.started":"2025-09-18T06:24:34.775611Z","shell.execute_reply":"2025-09-18T06:24:34.795164Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def mIoU(pred_mask, mask, smooth=1e-10, n_classes=4):\n    print(\"Corriendo funcion mIoU\")\n\n    with torch.no_grad():\n        # Clase más probable por píxel\n        pred_mask = F.softmax(pred_mask, dim=1)\n        pred_mask = torch.argmax(pred_mask, dim=1)\n\n        # Aplanamos (B, H, W) → (B*H*W)\n        pred_mask = pred_mask.contiguous().view(-1)\n        mask = mask.contiguous().view(-1)\n\n        iou_per_class = []\n        for clas in range(0, n_classes):  # por clase\n            true_class = pred_mask == clas   # predicciones de esa clase\n            true_label = mask == clas        # etiquetas de esa clase\n\n            if true_label.long().sum().item() == 0:  \n                # si no hay píxeles de esta clase en el ground truth\n                iou_per_class.append(np.nan)\n            else:\n                # Intersección y unión\n                intersect = torch.logical_and(true_class, true_label).sum().float().item()\n                union = torch.logical_or(true_class, true_label).sum().float().item()\n\n                # IoU de esta clase\n                iou = (intersect + smooth) / (union + smooth)\n                iou_per_class.append(iou)\n\n        # Promedio ignorando clases ausentes\n        return np.nanmean(iou_per_class)\n","metadata":{"cell_id":"c61458fc66f5412f98a8bbcbb706650a","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.796634Z","iopub.execute_input":"2025-09-18T06:24:34.796860Z","iopub.status.idle":"2025-09-18T06:24:34.812776Z","shell.execute_reply.started":"2025-09-18T06:24:34.796837Z","shell.execute_reply":"2025-09-18T06:24:34.811977Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def iou(pred, gt):\n    print(\"Corriendo funcion iou\")\n\n    # Quita dims de tamaño 1, mueve a CPU y pasa a NumPy\n    pred = pred.squeeze().cpu().data.numpy()\n    pred = ToLabel(pred)\n\n    # GT a NumPy\n    gt = gt.squeeze().cpu().data.numpy()\n\n    agg = pred + gt\n\n    # Intersección: píxeles 1 en pred y 1 en gt\n    i = float(np.sum(agg == 2))\n    # Unión: píxeles 1 en pred o 1 en gt\n    u = float(np.sum(agg > 0))\n\n    # Si no hay unión, IoU = 1 (caso vacío por convención)\n    if u == 0:\n        result = 1\n    else:\n        result = i / u\n\n    return result\n","metadata":{"cell_id":"2c4edcf0354d4272b36385e5e4fd1193","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.813613Z","iopub.execute_input":"2025-09-18T06:24:34.814243Z","iopub.status.idle":"2025-09-18T06:24:34.828932Z","shell.execute_reply.started":"2025-09-18T06:24:34.814217Z","shell.execute_reply":"2025-09-18T06:24:34.828333Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def f1_score(pred_mask, mask, n_classes=4, smooth=1e-10):\n    print(\"Corriendo funcion f1_score\")\n    \n    with torch.no_grad():\n        # Get predictions\n        pred_mask = F.softmax(pred_mask, dim=1)\n        pred_mask = torch.argmax(pred_mask, dim=1)\n        \n        # Flatten tensors\n        pred_mask = pred_mask.contiguous().view(-1)\n        mask = mask.contiguous().view(-1)\n        \n        f1_per_class = []\n        for clas in range(n_classes):\n            true_class = pred_mask == clas\n            true_label = mask == clas\n            \n            # Calculate TP, FP, FN\n            tp = torch.logical_and(true_class, true_label).sum().float().item()\n            fp = torch.logical_and(true_class, ~true_label).sum().float().item()\n            fn = torch.logical_and(~true_class, true_label).sum().float().item()\n            \n            # Calculate precision and recall\n            precision = (tp + smooth) / (tp + fp + smooth)\n            recall = (tp + smooth) / (tp + fn + smooth)\n            \n            # Calculate F1\n            f1 = 2 * (precision * recall) / (precision + recall + smooth)\n            f1_per_class.append(f1)\n        \n        return np.mean(f1_per_class)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.829652Z","iopub.execute_input":"2025-09-18T06:24:34.829867Z","iopub.status.idle":"2025-09-18T06:24:34.842289Z","shell.execute_reply.started":"2025-09-18T06:24:34.829844Z","shell.execute_reply":"2025-09-18T06:24:34.841652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def class_accuracy(pred_mask, mask, n_classes=4):\n\n    print(\"Corriendo funcion class_accuracy\")\n    \n    with torch.no_grad():\n        pred_mask = F.softmax(pred_mask, dim=1)\n        pred_mask = torch.argmax(pred_mask, dim=1)\n        \n        pred_mask = pred_mask.contiguous().view(-1)\n        mask = mask.contiguous().view(-1)\n        \n        acc_per_class = []\n        for clas in range(n_classes):\n            true_class = pred_mask == clas\n            true_label = mask == clas\n            \n            if true_label.sum().item() == 0:\n                acc_per_class.append(np.nan)\n            else:\n                correct = torch.logical_and(true_class, true_label).sum().float().item()\n                total = true_label.sum().float().item()\n                accuracy = correct / total\n                acc_per_class.append(accuracy)\n        \n        return np.nanmean(acc_per_class)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.842915Z","iopub.execute_input":"2025-09-18T06:24:34.843181Z","iopub.status.idle":"2025-09-18T06:24:34.859892Z","shell.execute_reply.started":"2025-09-18T06:24:34.843162Z","shell.execute_reply":"2025-09-18T06:24:34.859166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = smp.Unet('efficientnet-b4',in_channels=1, encoder_weights='imagenet',classes=4, activation=None, encoder_depth=5, decoder_channels=[512, 256, 128, 64, 32])\n#decoder_attention_type ='scse'","metadata":{"cell_id":"59e7edef1ec74e01a7a8ca2573759aa7","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:34.860654Z","iopub.execute_input":"2025-09-18T06:24:34.861371Z","iopub.status.idle":"2025-09-18T06:24:35.223220Z","shell.execute_reply.started":"2025-09-18T06:24:34.861354Z","shell.execute_reply":"2025-09-18T06:24:35.222590Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_lr(optimizer):\n    print(\"Corriendo funcion get_lr\")\n\n    for param_group in optimizer.param_groups:\n        return param_group['lr']\n\ndef fit(epochs, model, train_loader, val_loader, criterion, optimizer, scheduler, patch=False):\n    print(\"Corriendo funcion fit\")\n\n    #torch.cuda.empty_cache()\n    train_losses = []\n    test_losses = []\n    val_iou = []; val_acc = []\n    train_iou = []; train_acc = []\n    lrs = []\n    min_loss = np.inf\n    decrease = 1 ; not_improve=0\n    train_f1 = []; val_f1 = []\n    train_class_acc = []; val_class_acc = []\n\n    model.to(device)\n    fit_time = time.time()\n    for e in range(epochs):\n        since = time.time()\n        running_loss = 0\n        iou_score = 0\n        accuracy = 0\n        f1_score_train = 0\n        class_acc_train = 0\n        #training loop\n        model.train()\n        for i, data in enumerate(tqdm(train_loader)):\n            #training phase\n            image_tiles, mask_tiles = data\n            \n            image = image_tiles.to(device); mask =mask_tiles.to(device);\n            #forward\n            output = model(image)\n            \n            loss = criterion(output, mask)\n            #evaluation metrics\n            iou_score += mIoU(output, mask)\n            accuracy += pixel_accuracy(output, mask)\n            f1_score_train += f1_score(output, mask)\n            class_acc_train += class_accuracy(output, mask)\n            #backward\n            loss.backward()\n            optimizer.step() #update weight          \n            optimizer.zero_grad() #reset gradient\n            \n            #step the learning rate\n            lrs.append(get_lr(optimizer))\n            scheduler.step() \n            \n            running_loss += loss.item()\n            \n\n        else:\n            model.eval()\n            test_loss = 0\n            test_accuracy = 0\n            val_iou_score = 0\n            f1_score_val = 0\n            class_acc_val = 0\n            #validation loop\n            with torch.no_grad():\n                for i, data in enumerate(tqdm(val_loader)):\n                    image_tiles, mask_tiles = data\n\n                    image = image_tiles.to(device); mask =mask_tiles.to(device);\n                    output = model(image)\n                    #evaluation metrics\n                    val_iou_score +=  mIoU(output, mask)\n                    test_accuracy += pixel_accuracy(output, mask)\n                    f1_score_val += f1_score(output, mask)\n                    class_acc_val += class_accuracy(output, mask)\n                    #loss\n                    loss = criterion(output, mask)                                  \n                    test_loss += loss.item()\n            \n            #calculatio mean for each batch\n            train_losses.append(running_loss/len(train_loader))\n            test_losses.append(test_loss/len(val_loader))\n\n\n            if min_loss > (test_loss/len(val_loader)):\n                print('Loss Decreasing.. {:.3f} >> {:.3f} '.format(min_loss, (test_loss/len(val_loader))))\n                min_loss = (test_loss/len(val_loader))\n                decrease += 1\n                if decrease % 5 == 0:\n                    print('saving model...')\n                    torch.save(model, 'Unet_efficientnet_b2_mIoU-{:.3f}.pt'.format(val_iou_score/len(val_loader)))\n                    \n\n            if (test_loss/len(val_loader)) > min_loss:\n                not_improve += 1\n                min_loss = (test_loss/len(val_loader))\n                print(f'Loss Not Decrease for {not_improve} time')\n                #if not_improve == 7:\n                #    print('Loss not decrease for 7 times, Stop Training')\n                #    break\n            \n            #iou\n            val_iou.append(val_iou_score/len(val_loader))\n            train_iou.append(iou_score/len(train_loader))\n            train_acc.append(accuracy/len(train_loader))\n            val_acc.append(test_accuracy/ len(val_loader))\n            train_f1.append(f1_score_train/len(train_loader))\n            val_f1.append(f1_score_val/len(val_loader))\n            train_class_acc.append(class_acc_train/len(train_loader))\n            val_class_acc.append(class_acc_val/len(val_loader))\n            \n            print(\"Epoch:{}/{}..\".format(e+1, epochs),\n                \"Train Loss: {:.3f}..\".format(running_loss/len(train_loader)),\n                \"Val Loss: {:.3f}..\".format(test_loss/len(val_loader)),\n                \"Train mIoU:{:.3f}..\".format(iou_score/len(train_loader)),\n                \"Val mIoU: {:.3f}..\".format(val_iou_score/len(val_loader)),\n                \"Train Acc:{:.3f}..\".format(accuracy/len(train_loader)),\n                \"Val Acc:{:.3f}..\".format(test_accuracy/len(val_loader)),\n                \"Train F1:{:.3f}..\".format(f1_score_train/len(train_loader)),\n                \"Val F1: {:.3f}..\".format(f1_score_val/len(val_loader)),\n                \"Time: {:.2f}m\".format((time.time()-since)/60))\n        \n    history = {'train_loss': train_losses, 'val_loss': test_losses,\n           'train_miou': train_iou, 'val_miou': val_iou,\n           'train_acc': train_acc, 'val_acc': val_acc,\n           'train_f1': train_f1, 'val_f1': val_f1,\n           'train_class_acc': train_class_acc, 'val_class_acc': val_class_acc,\n           'lrs': lrs}\n    print('Total time: {:.2f} m' .format((time.time()- fit_time)/60))\n    return history","metadata":{"cell_id":"9ea6e5bc43aa4aec8e7330ed55cf4261","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:35.223877Z","iopub.execute_input":"2025-09-18T06:24:35.224126Z","iopub.status.idle":"2025-09-18T06:24:35.237532Z","shell.execute_reply.started":"2025-09-18T06:24:35.224093Z","shell.execute_reply":"2025-09-18T06:24:35.236685Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Entrenamiento\n\n### Definición de Conjuntos de Datos\n\n#### División de Datos\n\n- **Conjunto de Entrenamiento**:\n  - MedSeg: índices 24-99 (76 imágenes)\n  - Radiopedia: todas las imágenes disponibles\n  - Total: ~100 imágenes combinadas\n\n- **Conjunto de Validación**:\n  - MedSeg: índices 0-23 (24 imágenes)\n  - Sin augmentación para evaluación consistente\n\n- **Conjunto de Pruebas**:\n  - Test MedSeg: 10 imágenes sin etiquetas\n  - Evaluación final del modelo\n\n### Proceso de Entrenamiento\n\n#### Función de Pérdida\n\n**CrossEntropyLoss**: Seleccionada por ser la función estándar para segmentación multiclase\n- Combina softmax y negative log-likelihood\n- Adecuada para problemas de clasificación multiclase\n- Penaliza fuertemente las predicciones incorrectas con alta confianza\n\n#### Optimizador y Hiperparámetros\n\n**AdamW Optimizer**:\n```python\noptimizer = torch.optim.AdamW(\n    model.parameters(), \n    lr=1e-3,              # Learning rate máximo\n    weight_decay=1e-4     # Regularización L2\n)\n```\n\n**One Cycle Learning Rate Scheduler**:\n```python\nscheduler = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer, \n    max_lr=1e-3, \n    epochs=10,\n    steps_per_epoch=len(train_dataloader)\n)\n```\n\n#### Configuración de Entrenamiento\n\n- **Épocas**: 10\n- **Batch size**: 24 (tamaño del conjunto de validación)\n- **Learning rate máximo**: 1e-3\n- **Weight decay**: 1e-4 (regularización L2)\n\n#### Técnicas de Regularización\n\n1. **Weight Decay**: 1e-4 para prevenir overfitting\n2. **Early Stopping**: \n   - Detención si la pérdida no mejora por 7 épocas consecutivas\n   - Guardado del mejor modelo cada 5 mejoras\n\n3. **Data Augmentation**: Como se describió en la sección de preprocesamiento\n\n#### Hiperparámetros con Mayor Impacto\n\n1. **Learning Rate (1e-3)**: \n   - Valor óptimo encontrado experimentalmente\n   - Permite convergencia rápida sin overshooting\n\n2. **Batch Size (24)**:\n   - Limitado por memoria GPU (Tesla P100-16GB)\n   - Proporciona gradientes estables\n\n3. **Weight Decay (1e-4)**:\n   - Crucial para prevenir overfitting dado el dataset pequeño\n   - Balance entre regularización y capacidad del modelo\n\n4. **Data Augmentation**:\n   - Impacto crítico en la generalización\n   - Especialmente la rotación y el recorte aleatorio\n\n### Consideraciones de Hardware\n\n- **GPU Utilizada**: Tesla P100-PCIE-16GB\n- **Memoria disponible**: 15.9 GB\n- **Framework**: PyTorch 2.6.0+cu124\n- **CUDA**: Versión 12.4\n\n**Optimizaciones de hardware**:\n- Pin memory habilitado para transferencia CPU-GPU más rápida\n- Múltiples workers para carga paralela de datos\n- Tensor operations ejecutadas en GPU para máximo rendimiento\n\n### Métricas de Evaluación\n\n1. **Mean Intersection over Union (mIoU)**:\n   - Métrica principal para segmentación\n   - Promedio de IoU por clase\n   - Valor final en validación: ~0.659\n\n2. **Pixel Accuracy**:\n   - Porcentaje de píxeles clasificados correctamente\n   - Métrica complementaria para evaluación general\n\n**Resultados finales**:\n- **Training mIoU**: Convergencia progresiva durante el entrenamiento\n- **Validation mIoU**: 0.659 (resultado competitivo para el tamaño del dataset)\n- **Tiempo de entrenamiento**: Aproximadamente 2 minutos por época\n\n---","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"max_lr = 3e-3\nepoch = 18\nweight_decay = 1e-3\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=max_lr, weight_decay=weight_decay)\nsched = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr, epochs=epoch,\n                                            steps_per_epoch=len(train_dataloader))\n\nhistory = fit(epoch, model, train_dataloader, val_dataloader, criterion, optimizer, sched)","metadata":{"cell_id":"8b692e354fc44098881bc83de43697b6","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:24:35.238276Z","iopub.execute_input":"2025-09-18T06:24:35.238477Z","iopub.status.idle":"2025-09-18T06:29:48.532798Z","shell.execute_reply.started":"2025-09-18T06:24:35.238458Z","shell.execute_reply":"2025-09-18T06:29:48.531886Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model, 'Unet-efficientnet.pt')","metadata":{"cell_id":"2f651460296e49af9a124c88e8af7e05","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:29:48.534230Z","iopub.execute_input":"2025-09-18T06:29:48.534523Z","iopub.status.idle":"2025-09-18T06:29:48.882754Z","shell.execute_reply.started":"2025-09-18T06:29:48.534497Z","shell.execute_reply":"2025-09-18T06:29:48.881846Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_loss(history):\n    plt.plot(history['val_loss'], label='val', marker='o')\n    plt.plot( history['train_loss'], label='train', marker='o')\n    plt.title('Loss per epoch'); plt.ylabel('loss');\n    plt.xlabel('epoch')\n    plt.legend(), plt.grid()\n    plt.show()\n    \ndef plot_score(history):\n    plt.plot(history['train_miou'], label='train_mIoU', marker='*')\n    plt.plot(history['val_miou'], label='val_mIoU',  marker='*')\n    plt.title('Score per epoch'); plt.ylabel('mean IoU')\n    plt.xlabel('epoch')\n    plt.legend(), plt.grid()\n    plt.show()\n    \ndef plot_acc(history):\n    plt.plot(history['train_acc'], label='train_accuracy', marker='*')\n    plt.plot(history['val_acc'], label='val_accuracy',  marker='*')\n    plt.title('Accuracy per epoch'); plt.ylabel('Accuracy')\n    plt.xlabel('epoch')\n    plt.legend(), plt.grid()\n    plt.show()\n\ndef plot_f1(history):\n    plt.plot(history['train_f1'], label='train_f1', marker='*')\n    plt.plot(history['val_f1'], label='val_f1', marker='*')\n    plt.title('F1 Score per epoch'); plt.ylabel('F1 Score')\n    plt.xlabel('epoch')\n    plt.legend(), plt.grid()\n    plt.show()\n\ndef plot_class_acc(history):\n    plt.plot(history['train_class_acc'], label='train_class_acc', marker='o')\n    plt.plot(history['val_class_acc'], label='val_class_acc', marker='o')\n    plt.title('Class Accuracy per epoch'); plt.ylabel('Class Accuracy')\n    plt.xlabel('epoch')\n    plt.legend(), plt.grid()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:48.887168Z","iopub.execute_input":"2025-09-18T06:29:48.887413Z","iopub.status.idle":"2025-09-18T06:29:48.895264Z","shell.execute_reply.started":"2025-09-18T06:29:48.887395Z","shell.execute_reply":"2025-09-18T06:29:48.894439Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_loss(history)\nplot_score(history)\nplot_acc(history)\nplot_f1(history)\nplot_class_acc(history)","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:48.896107Z","iopub.execute_input":"2025-09-18T06:29:48.896392Z","iopub.status.idle":"2025-09-18T06:29:50.104865Z","shell.execute_reply.started":"2025-09-18T06:29:48.896362Z","shell.execute_reply":"2025-09-18T06:29:50.103885Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Entrenamiento","metadata":{"cell_id":"ec293ffbc0064a06a6453de4fa2415b6","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"code","source":"# pytorch train","metadata":{"cell_id":"cad436db5e204b1badc7bcef49c7a96f","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:29:50.105854Z","iopub.execute_input":"2025-09-18T06:29:50.106109Z","iopub.status.idle":"2025-09-18T06:29:50.109651Z","shell.execute_reply.started":"2025-09-18T06:29:50.106091Z","shell.execute_reply":"2025-09-18T06:29:50.108954Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_image_mask_miou(model, image, mask, mean=[0.485], std=[0.229]):\n    model.eval()\n    #t = T.Compose([T.ToTensor(), T.Normalize(mean, std)])\n    #image = t(image)\n    model.to(device); image=image.to(device)\n    mask = mask.to(device)\n    \n    \n    with torch.no_grad():\n        \n        #image = image.unsqueeze(0)\n        #mask = mask.unsqueeze(0)\n        \n        output = model(image)\n        a,b,c,d = output.shape\n        score = mIoU(output, mask)\n        masked = torch.argmax(output, dim=1)\n        masked = masked.cpu().squeeze(0)\n    return masked, score, output.permute(0, 2, 3,1)","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:50.110556Z","iopub.execute_input":"2025-09-18T06:29:50.110820Z","iopub.status.idle":"2025-09-18T06:29:50.124826Z","shell.execute_reply.started":"2025-09-18T06:29:50.110801Z","shell.execute_reply":"2025-09-18T06:29:50.124106Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_image_mask_pixel(model, image, mask, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]):\n    model.eval()\n    model.to(device); image=image.to(device)\n    mask = mask.to(device)\n    with torch.no_grad():\n        \n        output = model(image)\n        acc = pixel_accuracy(output, mask)\n        masked = torch.argmax(output, dim=1)\n        masked = masked.cpu().squeeze(0)\n    return masked, acc","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:50.125707Z","iopub.execute_input":"2025-09-18T06:29:50.125981Z","iopub.status.idle":"2025-09-18T06:29:50.140498Z","shell.execute_reply.started":"2025-09-18T06:29:50.125945Z","shell.execute_reply":"2025-09-18T06:29:50.139862Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def mask_to_onehot(mask, palette):\n    \"\"\"\n    Converts a segmentation mask (H, W, C) to (H, W, K) where the last dim is a one\n    hot encoding vector, C is usually 1 or 3, and K is the number of class.\n    \"\"\"\n    semantic_map = []\n    for colour in palette:\n        equality = np.equal(mask, colour)\n        class_map = np.all(equality, axis=-1)\n        semantic_map.append(class_map)\n    semantic_map = np.stack(semantic_map, axis=-1).astype(np.float32)\n    return torch.from_numpy(semantic_map)","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:50.141274Z","iopub.execute_input":"2025-09-18T06:29:50.141471Z","iopub.status.idle":"2025-09-18T06:29:50.154361Z","shell.execute_reply.started":"2025-09-18T06:29:50.141448Z","shell.execute_reply":"2025-09-18T06:29:50.153569Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image, mask = next(iter(val_dataloader))\npred_mask, score,output = predict_image_mask_miou(model, image, mask)\nsemantic_map = mask_to_onehot(torch.unsqueeze(mask,-1).numpy(),palette)","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:50.155194Z","iopub.execute_input":"2025-09-18T06:29:50.155428Z","iopub.status.idle":"2025-09-18T06:29:50.507380Z","shell.execute_reply.started":"2025-09-18T06:29:50.155405Z","shell.execute_reply":"2025-09-18T06:29:50.506579Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize(image, semantic_map, pred_batch=output)\n\n# yellow is TP, red is FP, green is FN","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:50.508420Z","iopub.execute_input":"2025-09-18T06:29:50.508685Z","iopub.status.idle":"2025-09-18T06:29:52.022406Z","shell.execute_reply.started":"2025-09-18T06:29:50.508662Z","shell.execute_reply":"2025-09-18T06:29:52.021696Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Resultados","metadata":{"cell_id":"cfa9619d07b8419d8da0d7ccc71ac5e2","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"code","source":"# Gráficas","metadata":{"cell_id":"53755f9304a5422292947f088e47d32c","deepnote_cell_type":"code","deepnote_to_be_reexecuted":true,"execution":{"iopub.status.busy":"2025-09-18T06:29:52.023368Z","iopub.execute_input":"2025-09-18T06:29:52.023950Z","iopub.status.idle":"2025-09-18T06:29:52.027423Z","shell.execute_reply.started":"2025-09-18T06:29:52.023917Z","shell.execute_reply":"2025-09-18T06:29:52.026800Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def miou_score(model, test_set):\n    score_iou = []\n    for i, data in enumerate(tqdm(test_set)):\n        img, mask = data\n        pred_mask, score,output = predict_image_mask_miou(model, img, mask)\n        score_iou.append(score)\n    return score_iou","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:52.028239Z","iopub.execute_input":"2025-09-18T06:29:52.028493Z","iopub.status.idle":"2025-09-18T06:29:52.044084Z","shell.execute_reply.started":"2025-09-18T06:29:52.028477Z","shell.execute_reply":"2025-09-18T06:29:52.043525Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mob_miou = miou_score(model, val_dataloader)\nmob_miou","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:52.044887Z","iopub.execute_input":"2025-09-18T06:29:52.045291Z","iopub.status.idle":"2025-09-18T06:29:52.418312Z","shell.execute_reply.started":"2025-09-18T06:29:52.045273Z","shell.execute_reply":"2025-09-18T06:29:52.417456Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"del train_images\ndel train_masks","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:52.419515Z","iopub.execute_input":"2025-09-18T06:29:52.420088Z","iopub.status.idle":"2025-09-18T06:29:52.423664Z","shell.execute_reply.started":"2025-09-18T06:29:52.420064Z","shell.execute_reply":"2025-09-18T06:29:52.422909Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def test_predict(model, image, mean=[0.485], std=[0.229]):\n    t = T.Compose([T.ToTensor(), T.Normalize(mean, std)])\n    image = t(image)\n    model.eval()\n    \n    model.to(device); image=image.to(device)\n    \n    with torch.no_grad():\n        output = model(torch.unsqueeze(image,1))\n        output = nn.Softmax(dim=1)(output)\n    return output.permute(0, 2, 3,1).cpu().numpy()","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:52.424412Z","iopub.execute_input":"2025-09-18T06:29:52.424602Z","iopub.status.idle":"2025-09-18T06:29:52.437435Z","shell.execute_reply.started":"2025-09-18T06:29:52.424586Z","shell.execute_reply":"2025-09-18T06:29:52.436617Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_batch = np.stack([val_augs(image=img)['image'] for img in test_images_medseg], axis=0)\nprint(torch.from_numpy(image_batch).shape)\n#output = test_predict(model, torch.from_numpy(image_batch).permute(0, 3, 1,2))\noutput = np.zeros((10,256,256,4))\nfor i in range(10):   \n    output[i] = test_predict(model, image_batch[i])\nprint(output.shape)\ntest_masks_prediction = output > 0.5\nvisualize(image_batch, test_masks_prediction, num_samples=len(test_images_medseg))","metadata":{"execution":{"iopub.status.busy":"2025-09-18T06:29:52.438412Z","iopub.execute_input":"2025-09-18T06:29:52.438614Z","iopub.status.idle":"2025-09-18T06:29:55.029009Z","shell.execute_reply.started":"2025-09-18T06:29:52.438598Z","shell.execute_reply":"2025-09-18T06:29:55.028179Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import scipy\ntest_masks_prediction_original_size = scipy.ndimage.zoom(test_masks_prediction[..., :-2], (1, 2, 2, 1), order=0)\ntest_masks_prediction_original_size.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T06:42:24.135199Z","iopub.execute_input":"2025-09-18T06:42:24.135552Z","iopub.status.idle":"2025-09-18T06:42:24.219875Z","shell.execute_reply.started":"2025-09-18T06:42:24.135530Z","shell.execute_reply":"2025-09-18T06:42:24.219166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\npd.DataFrame(\n             data=np.stack((np.arange(len(test_masks_prediction_original_size.ravel())), \n                            test_masks_prediction_original_size.ravel().astype(int)),\n                            axis=-1), \n             columns=['Id', 'Predicted'])\\\n.set_index('Id').to_csv('sub.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T06:42:26.140035Z","iopub.execute_input":"2025-09-18T06:42:26.140375Z","iopub.status.idle":"2025-09-18T06:42:29.509445Z","shell.execute_reply.started":"2025-09-18T06:42:26.140353Z","shell.execute_reply":"2025-09-18T06:42:29.508603Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Conclusiones","metadata":{"cell_id":"20c157a68e4b4effb8b9823db7d625ca","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"markdown","source":"### Fortalezas del Modelo\n\nEl modelo U-Net con encoder EfficientNet-B4 demostró ser efectivo para la segmentación de COVID-19, alcanzando un **mIoU de validación de 0.656** con solo 100 imágenes de entrenamiento. Las principales fortalezas incluyen:\n\n- **Arquitectura robusta**: La combinación U-Net + EfficientNet-B4 preservó información espacial crítica mediante conexiones skip\n- **Manejo efectivo de datos limitados**: Las técnicas de aumento de datos (rotación 360°, recorte aleatorio, reflejo horizontal) mejoraron significativamente la generalización\n- **Preprocesamiento optimizado**: La normalización por percentiles y el scheduler OneCycleLR permitieron una convergencia estable\n\n### Limitaciones Identificadas\n\n- **Dataset reducido**: Solo 100 imágenes limitan la capacidad de generalización\n- **Ausencia de manejo de desbalance de clases**: No se implementaron técnicas específicas para clases minoritarias\n- **Interpretabilidad limitada**: El modelo funciona como \"caja negra\", dificultando la interpretación clínica\n\n### Desafíos Superados\n\n**Aprendizaje de arquitectura U-Net**: Gracias al material de apoyo de clase y el notebook PyTorch Baseline for Semantic Segmentation.\n**Manejo de hiperparametros**: Con la documentación de SMP vimos que hiperparametros nos podían servir para mejorar el modelo.\n\n### Recomendaciones Futuras\n\n1. **Implementación de loss functions ponderadas** (Focal Loss) para manejar desbalance\n2. **Exploración de arquitecturas transformer-based** (Swin-UNet, TransUNet)\n\n### Impacto\n\nEste proyecto demuestra el potencial del deep learning para asistir en diagnóstico médico durante emergencias sanitarias. A pesar del dataset limitado, los resultados (mIoU: 0.656) proporcionan una base sólida para futuras investigaciones en segmentación automática.","metadata":{"cell_id":"d90f1d7d269e4630b7b273a7e9a30e67","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"# Referencias","metadata":{"cell_id":"25e1bd4d6c444f2a91038c819e144f0f","deepnote_cell_type":"text-cell-h1","formattedRanges":[]}},{"cell_type":"markdown","source":"Ronneberger, O., Fischer, P., & Brox, T. (2015, Octubre). U-Net: Convolutional networks for biomedical image segmentation. arXiv. https://arxiv.org/abs/1505.04597","metadata":{"cell_id":"fded6d382b8844bd9f6aa12281301522","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Maedemaftouni. (2021, 8 mayo). PyTorch Baseline for Semantic Segmentation. Kaggle. https://www.kaggle.com/code/maedemaftouni/pytorch-baseline-for-semantic-segmentation","metadata":{"cell_id":"a430f1b2005040ef8231585c78a4ef68","deepnote_cell_type":"text-cell-p","formattedRanges":[]}},{"cell_type":"markdown","source":"Segmentation models (s. f.)— Segmentation Models documentation. https://smp.readthedocs.io/en/latest/models.html","metadata":{}}]}