{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":"none","dataSources":[{"sourceId":20561,"databundleVersionId":1187889,"sourceType":"competition"},{"sourceId":34206327,"sourceType":"kernelVersion"}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# MatChaIA TEAM","metadata":{}},{"cell_type":"markdown","source":"#### Equipo\n\n- Galo Alejandro del Rio Viggiano - A01710791\n- Ana Karen Toscano Diaz - A01369687\n- Maxime Vilcocq Parra - A01710550\n- José Antonio López Saldaña - A01710367","metadata":{}},{"cell_type":"markdown","source":"# Contexto del problema  \nLa pandemia de COVID-19 supuso un gran reto para la comunidad médica, especialmente en la detección y seguimiento de las complicaciones pulmonares. Las tomografías computarizadas (CT) se convirtieron en una herramienta clave, ya que permiten observar alteraciones típicas de la enfermedad, como las opacidades en vidrio deslustrado (ground glass opacities) y las consolidaciones. Estas lesiones son indicadores de inflamación o acumulación de líquidos en los pulmones y su identificación es fundamental para evaluar la severidad de la infección, monitorear la progresión y planificar tratamientos.  \n\n## El problema a resolver  \nLa segmentación manual de estas regiones en imágenes médicas es un proceso confiable, pero presenta limitaciones importantes: es tardado, costoso y está sujeto a variabilidad entre especialistas. Por esta razón, surge la necesidad de desarrollar métodos automáticos basados en aprendizaje profundo, capaces de segmentar con rapidez y precisión las áreas pulmonares afectadas.\n\n\n## Objetivo de la competencia de Kaggle  \nEl presente documento se desarrolla en el marco de la competencia **[COVID-19 CT Image Segmentation](https://www.kaggle.com/competitions/covid-segmentation)** organizada en Kaggle. El objetivo central del reto es construir modelos robustos de segmentación semántica que puedan:  \n\n- Detectar y delinear las regiones pulmonares afectadas por COVID-19.  \n- Proporcionar información cuantitativa y visual sobre la extensión de la enfermedad.  \n- Complementar las herramientas de diagnóstico médico, apoyando a los profesionales de la salud en la toma de decisiones clínicas.  \n\n \n","metadata":{}},{"cell_type":"markdown","source":"# Análisis de Datos  \n\n## Exploratory Data Analysis (EDA)  \nEl conjunto de datos proporcionado en la competencia **[COVID-19 CT Image Segmentation](https://www.kaggle.com/competitions/covid-segmentation)** incluye **cortes de tomografías computarizadas (CT)** junto con sus **máscaras de segmentación correspondientes**. Estas provienen de dos fuentes principales:  \n\n### MedSeg Dataset  \n- 100 imágenes de CT de más de 10 pacientes.  \n- Cada imagen cuenta con máscaras de segmentación que contienen cuatro canales:  \n  - Opacidades en vidrio deslustrado (Ground Glass Opacities).  \n  - Consolidaciones.  \n  - Tejido pulmonar no afectado (Lungs Other).  \n  - Fondo.  \n\n### Radiopaedia Dataset  \n- 829 cortes de CT.  \n- La estructura de las imágenes y máscaras es similar a la del dataset de MedSeg.  \n\nEn general, cada imagen tiene dimensiones de 520 × 520 píxeles. Las máscaras etiquetan las regiones de interés en los pulmones, diferenciando áreas infectadas (opacidades y consolidaciones) del tejido sano.  \nLa visualización inicial de estas imágenes permite comprender mejor la distribución de los datos y la extensión de la infección en los pulmones, lo cual es esencial antes de entrenar cualquier modelo de segmentación.  \n\n","metadata":{}},{"cell_type":"code","source":"# Importa la librería pathlib para trabajar con rutas de archivos y carpetas \n# de forma orientada a objetos (más fácil de manejar que strings simples).\nfrom pathlib import Path\nimport numpy as np\n\n# 1) Definimos la ruta base donde Kaggle guarda los datasets cargados.\nbase = Path(\"/kaggle/input\")\n\n# 2) Recorremos de manera recursiva (buscando en todas las subcarpetas) \n# todos los archivos que terminen en .npy dentro de /kaggle/input\nfor p in base.rglob(\"*.npy\"):\n    # 3) Imprimimos la ruta completa de cada archivo encontrado.\n    print(p)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:03:50.069327Z","iopub.execute_input":"2025-09-16T21:03:50.069620Z","iopub.status.idle":"2025-09-16T21:03:50.094503Z","shell.execute_reply.started":"2025-09-16T21:03:50.069599Z","shell.execute_reply":"2025-09-16T21:03:50.093254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2) En Kaggle, los datasets no se guardan directamente en /kaggle/input, \n# sino dentro de carpetas con nombres largos y aleatorios, por ejemplo: \n# /kaggle/input/covid-19-ct-segmentation-1354354131554/...\n#\n# Para no preocuparnos por esos nombres de carpeta, usamos base.rglob()\n# que busca en todas las subcarpetas el archivo exacto por nombre.\n\n# Con next() obtenemos el PRIMER archivo que coincida con el nombre indicado.\n# (Esto funciona bien porque normalmente solo hay un archivo con ese nombre).\n\n# Guardamos la ruta del archivo de imágenes de Medseg\nimages_medseg = next(base.rglob(\"images_medseg.npy\"))\n\n# Guardamos la ruta del archivo de imágenes de Radiopedia\nimages_radiopedia = next(base.rglob(\"images_radiopedia.npy\"))\n\n# Guardamos la ruta del archivo de máscaras de Medseg\nmasks_medseg = next(base.rglob(\"masks_medseg.npy\"))\n\n# Guardamos la ruta del archivo de máscaras de Radiopedia\nmasks_radiopedia = next(base.rglob(\"masks_radiopedia.npy\"))\n\n# Guardamos la ruta del archivo de imágenes de prueba de Medseg\ntest_images = next(base.rglob(\"test_images_medseg.npy\"))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T20:50:20.388665Z","iopub.execute_input":"2025-09-16T20:50:20.389565Z","iopub.status.idle":"2025-09-16T20:50:20.511146Z","shell.execute_reply.started":"2025-09-16T20:50:20.389535Z","shell.execute_reply":"2025-09-16T20:50:20.510166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3) Cargar los datos como arreglos de NumPy.\n# Se usa 'mmap_mode=\"r\"' para no cargar TODO el archivo en memoria de golpe,\n# sino acceder a los datos bajo demanda. Esto ahorra RAM ya que los archivos\n# .npy suelen ser muy grandes en datasets médicos.\n\n# Imágenes del dataset Medseg\nX_medseg = np.load(images_medseg, mmap_mode='r')     \n\n# Imágenes del dataset Radiopaedia\nX_radiopedia = np.load(images_radiopedia, mmap_mode='r')\n\n# Máscaras (etiquetas) del dataset Medseg\nY_medseg = np.load(masks_medseg, mmap_mode='r')     \n\n# Máscaras (etiquetas) del dataset Radiopaedia\nY_radiopedia = np.load(masks_radiopedia, mmap_mode='r')\n\n# Imágenes de prueba (sin etiquetas)\nX_test = np.load(test_images, mmap_mode='r')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:05:50.323231Z","iopub.execute_input":"2025-09-16T21:05:50.323630Z","iopub.status.idle":"2025-09-16T21:05:50.348578Z","shell.execute_reply.started":"2025-09-16T21:05:50.323601Z","shell.execute_reply":"2025-09-16T21:05:50.347511Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4) Revisar la forma (shape) y el tipo de datos (dtype) de cada arreglo cargado.\n# Esto es importante para asegurarnos de que las imágenes y las máscaras\n# tienen las dimensiones y formatos esperados antes de entrenar el modelo.\n\nfor name, arr in {\n    \"X_medseg\": X_medseg,        # Imágenes del dataset Medseg\n    \"Y_medseg\": Y_medseg,        # Máscaras (etiquetas) de Medseg\n    \"X_radiopedia\": X_radiopedia, # Imágenes del dataset Radiopaedia\n    \"Y_radiopedia\": Y_radiopedia, # Máscaras (etiquetas) de Radiopaedia\n    \"X_test\": X_test             # Imágenes de prueba (sin etiquetas)\n}.items():\n    # Imprime el nombre del arreglo, su forma (número de ejemplos, altura, ancho, canales)\n    # y su tipo de datos (ej. float32 para imágenes, int8/int16 para máscaras)\n    print(name, arr.shape, arr.dtype)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:06:58.151622Z","iopub.execute_input":"2025-09-16T21:06:58.152413Z","iopub.status.idle":"2025-09-16T21:06:58.158249Z","shell.execute_reply.started":"2025-09-16T21:06:58.152385Z","shell.execute_reply":"2025-09-16T21:06:58.157193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====== SETUP & CARGA ======\nimport os, random, numpy as np, pandas as pd, matplotlib.pyplot as plt\nimport tensorflow as tf\n\n# 1) Fijamos una semilla para reproducibilidad (resultados consistentes).\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\n# 2) Ruta base donde Kaggle guarda los archivos del dataset.\nPREFIX = \"/kaggle/input/covid-segmentation/\"\n\n# 3) Cargamos los archivos .npy y convertimos a tipos adecuados:\n#    - float32 para imágenes (valores continuos).\n#    - int8 para máscaras (clases enteras discretas).\nX_rp = np.load(os.path.join(PREFIX, \"images_radiopedia.npy\")).astype(np.float32)\nY_rp = np.load(os.path.join(PREFIX, \"masks_radiopedia.npy\")).astype(np.int8)\nX_ms = np.load(os.path.join(PREFIX, \"images_medseg.npy\")).astype(np.float32)\nY_ms = np.load(os.path.join(PREFIX, \"masks_medseg.npy\")).astype(np.int8)\nX_test = np.load(os.path.join(PREFIX, \"test_images_medseg.npy\")).astype(np.float32)\n\n# 4) Función auxiliar para imprimir forma de imágenes y máscaras\ndef shape_info(name, x, y=None):\n    print(f\"{name}: imgs {x.shape}  \" + (f\"masks {y.shape}\" if y is not None else \"\"))\n\nshape_info(\"Radiopaedia\", X_rp, Y_rp)\nshape_info(\"MedSeg\", X_ms, Y_ms)\nshape_info(\"Test\", X_test)\n\n\n# ====== COMPROBAR ONE-HOT Y CLASES ======\ndef mask_stats(Y, name):\n    # Dimensiones de la máscara: alto (H), ancho (W), número de clases (C)\n    H, W, C = Y.shape[1], Y.shape[2], Y.shape[3]\n\n    # Convertimos todas las máscaras a una matriz 2D: píxeles x clases\n    flat = Y.reshape(-1, C)\n\n    # Contamos píxeles por clase\n    counts = flat.sum(axis=0)\n    print(f\"[{name}] píxeles por clase:\", dict(enumerate(counts.astype(int))))\n\n    # Calculamos porcentaje de imágenes que contienen cada clase\n    per_image = (Y.reshape(len(Y), -1, C).sum(axis=1) > 0).mean(axis=0)\n    print(f\"[{name}] % de imágenes con clase:\", dict(enumerate(np.round(per_image*100,1))))\n\nmask_stats(Y_rp, \"Radiopaedia\")\nmask_stats(Y_ms, \"MedSeg\")\n\n\n# ====== VISUALIZAR EJEMPLOS ======\ndef show_samples(X, Y=None, n=6, title=\"\"):\n    # Seleccionamos n ejemplos aleatorios\n    idx = np.random.choice(len(X), n, replace=False)\n\n    # Filas: 1 si solo hay imágenes, 2 si también hay máscaras\n    cols = n; rows = 1 + (Y is not None)\n\n    # Creamos subplots\n    fig, ax = plt.subplots(rows, cols, figsize=(2*cols, 2*rows))\n    ax = np.atleast_2d(ax)\n\n    for i, j in enumerate(idx):\n        # Mostrar la imagen en escala de grises\n        ax[0, i].imshow(X[j,:,:,0], cmap=\"gray\")\n        ax[0, i].axis(\"off\")\n\n        # Si hay máscaras, mostramos la superposición por clases\n        if Y is not None:\n            overlay = np.zeros((*Y.shape[1:3], 3), dtype=float)\n            overlay[...,0] = Y[j,:,:,0]  # canal 0: ground-glass → rojo\n            overlay[...,1] = Y[j,:,:,1]  # canal 1: consolidaciones → verde\n            ax[1, i].imshow(overlay, vmin=0, vmax=1)\n            ax[1, i].axis(\"off\")\n\n    fig.suptitle(title)\n    plt.show()\n\n# Visualizamos ejemplos de cada dataset\nshow_samples(X_rp, Y_rp, title=\"Radiopaedia\")\nshow_samples(X_ms, Y_ms, title=\"MedSeg\")\nshow_samples(X_test, None, title=\"Test (MedSeg)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:09:05.293024Z","iopub.execute_input":"2025-09-16T21:09:05.293799Z","iopub.status.idle":"2025-09-16T21:09:32.194652Z","shell.execute_reply.started":"2025-09-16T21:09:05.293765Z","shell.execute_reply":"2025-09-16T21:09:32.193649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_hists(a, b=None, bins=100, title=\"\"):\n    # Crea una figura de 6x3 pulgadas\n    plt.figure(figsize=(6,3))\n\n    # Histograma del primer conjunto (a)\n    # .ravel() aplana el arreglo en 1D (todos los píxeles en un solo vector).\n    # bins=100 → cantidad de intervalos.\n    # density=True → normaliza el histograma (área total = 1).\n    # alpha=0.6 → transparencia.\n    plt.hist(a.ravel(), bins=bins, density=True, alpha=0.6, label=\"A\")\n\n    # Si se pasa un segundo conjunto (b), también se grafica su histograma\n    if b is not None:\n        plt.hist(b.ravel(), bins=bins, density=True, alpha=0.6, label=\"B\")\n\n    # Añadimos leyenda y título\n    plt.legend()\n    plt.title(title)\n    plt.show()\n\n\n# Comparar distribuciones de intensidades (Hounsfield Units) \n# entre Radiopaedia (A) y MedSeg (B)\nplot_hists(X_rp, X_ms, bins=100, title=\"HU Radiopaedia (A) vs MedSeg (B)\")\n\n# Comparar distribuciones de intensidades entre Test (MedSeg) (A) y MedSeg (B)\nplot_hists(X_test, X_ms, bins=100, title=\"HU Test MedSeg (A) vs MedSeg (B)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:10:41.510229Z","iopub.execute_input":"2025-09-16T21:10:41.510759Z","iopub.status.idle":"2025-09-16T21:10:46.951868Z","shell.execute_reply.started":"2025-09-16T21:10:41.510721Z","shell.execute_reply":"2025-09-16T21:10:46.951006Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Preprocesamiento de Datos  \nAntes del entrenamiento, se aplican varias técnicas de preprocesamiento para garantizar consistencia y mejorar la capacidad de generalización del modelo:  \n\n- **Redimensionamiento:** todas las imágenes se ajustan a 520 × 520 píxeles, manteniendo un tamaño uniforme en los datos de entrada.  \n- **Normalización:** los valores de los píxeles se escalan a un rango estándar, lo que facilita la convergencia durante el entrenamiento.  \n- **Aumento de datos:** se aplican técnicas como rotaciones aleatorias, flips horizontales/verticales e intensidades variables. Esto incrementa la variabilidad del dataset y ayuda al modelo a generalizar mejor frente a datos no vistos.  \n\nEstas transformaciones permiten que el modelo sea más robusto ante variaciones en las imágenes de entrada y mejoren su desempeño en la segmentación automática de lesiones pulmonares.  \n","metadata":{}},{"cell_type":"code","source":"# ====== PREPROCESADO HU, NORMALIZACIÓN ======\ndef clip_and_norm(x, clip_min=-1200, clip_max=600, mean=None, std=None, robust_perc=(5,95)):\n    x = x.copy()\n    x[x<clip_min] = clip_min; x[x>clip_max] = clip_max\n    if mean is None or std is None:\n        lo, hi = np.percentile(x, robust_perc)\n        sel = (x>lo) & (x<hi)\n        mean = float(x[sel].mean()); std = float(x[sel].std() + 1e-6)\n    x = (x - mean) / std\n    return x, (mean, std)\n\n# 1) normaliza Radiopaedia y usa sus stats para el resto (misma lógica que el original, pero parametrizado)\nX_rp_n, (mu, sigma) = clip_and_norm(X_rp)\nX_ms_n, _ = clip_and_norm(X_ms, mean=mu, std=sigma)\nX_test_n, _ = clip_and_norm(X_test, mean=mu, std=sigma)\nprint(\"mean/std usados:\", mu, sigma)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:32:31.256553Z","iopub.execute_input":"2025-09-16T21:32:31.257707Z","iopub.status.idle":"2025-09-16T21:32:39.712378Z","shell.execute_reply.started":"2025-09-16T21:32:31.257675Z","shell.execute_reply":"2025-09-16T21:32:39.711427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install segmentation_models_pytorch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T21:34:17.436384Z","iopub.execute_input":"2025-09-16T21:34:17.436723Z","iopub.status.idle":"2025-09-16T21:35:30.830954Z","shell.execute_reply.started":"2025-09-16T21:34:17.436697Z","shell.execute_reply":"2025-09-16T21:35:30.829886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}