{"metadata":{"colab":{"provenance":[]},"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":37333,"databundleVersionId":3949526,"sourceType":"competition"},{"sourceId":145998,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":123817,"modelId":146879},{"sourceId":146617,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":124360,"modelId":147407}],"dockerImageVersionId":30786,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **Classification of Blood Clot Origins in Ischemic Strokes 🩸**","metadata":{"id":"ziQaJdvqh88F"}},{"cell_type":"markdown","source":"This notebook explores the task of classifying the etiology of blood clots in whole-slide digital pathology images, specifically identifying whether they are of Cardioembolic (CE) or Large Artery Atherosclerosis (LAA) origin. Through an extensive exploratory data analysis (EDA), we describe the dataset, analyze missing and duplicate values, examine the distribution of image sizes, classify variables, and review the label distribution for the training set, along with plenty of other analysis. This EDA provides a comprehensive understanding of the data and helps identify potential preprocessing steps for optimal model performance.\n\nFollowing the EDA, we preprocess the images to standardize them for model input. The preprocessing involves resizing, converting images to grayscale, normalizing pixel values, and applying Gaussian blur to reduce noise. These steps ensure that the images are suitable for a Convolutional Neural Network (CNN) by preparing them with a consistent size, format, and reduced noise, enabling more efficient training and improved classification accuracy.","metadata":{"id":"2kK9OsnqiC9h"}},{"cell_type":"markdown","source":"**Authors:**\n- [Daniel Valdez](https://github.com/Danval-003)\n- [Emilio Solano](https://github.com/emiliosolanoo21)\n- [Adrian Flores](https://github.com/adrianRFlores)\n- [Andrea Ramírez](https://github.com/Andrea-gt)","metadata":{"id":"CXBxh8WPiG7b"}},{"cell_type":"markdown","source":"***","metadata":{"id":"sQGNhxwEiJKw"}},{"cell_type":"markdown","source":"## **(1) Import Libraries** ⬇️","metadata":{"id":"63DGMm3MiMEh"}},{"cell_type":"code","source":"# Data manipulation and visualization\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nimport subprocess\nfrom PIL import Image\nimport tifffile as tifi\nimport cv2\n\n# Set the maximum allowable pixels to a higher number.\nImage.MAX_IMAGE_PIXELS = None\n\n# Standard libraries\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# ===== ===== Reproducibility Seed ===== =====\n# Set a fixed seed for the random number generator for reproducibility\nrandom_state = 42\n\n# Set matplotlib inline\n%matplotlib inline\n\n# Set default figure size\nplt.rcParams['figure.figsize'] = (6, 4)\n\n# Define custom color palette\npalette = sns.color_palette(\"viridis\", 12)\n\n# Set the style of seaborn\nsns.set(style=\"whitegrid\")","metadata":{"id":"AP02dNX3cF31","execution":{"iopub.status.busy":"2024-10-25T05:37:46.476965Z","iopub.execute_input":"2024-10-25T05:37:46.477607Z","iopub.status.idle":"2024-10-25T05:37:48.3828Z","shell.execute_reply.started":"2024-10-25T05:37:46.477559Z","shell.execute_reply":"2024-10-25T05:37:48.381395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **(2) Data Upload** 📄","metadata":{"id":"fsI-gEUliSPC"}},{"cell_type":"code","source":"# Note:\n# -----\n# For this specific task, we are only using the training data (`train.csv`) throughout most of our analysis.\n# The test dataset (`test.csv`) will not be used until the very end when making final predictions.\n# We will train, validate, and tune your model using the training data only.\n#\n# The reason the test data is only used at the very end is to prevent any bias during model training and evaluation.\n# We want the test data to remain completely unseen until after we've finalized our model so that it can serve\n# as a true evaluation of our model's performance.\n# ---------------------------------------------\n\ndf = pd.read_csv('../input/mayo-clinic-strip-ai/train.csv')  # Load the training data\ndf.head()  # Display the first 5 rows of the DataFrame for a quick inspection of the data","metadata":{"id":"WIj3lNgYmBA7","outputId":"689b0ef1-4fc0-4ed5-f5a1-84629e718226","execution":{"iopub.status.busy":"2024-10-25T05:37:51.350206Z","iopub.execute_input":"2024-10-25T05:37:51.350759Z","iopub.status.idle":"2024-10-25T05:37:51.392066Z","shell.execute_reply.started":"2024-10-25T05:37:51.350719Z","shell.execute_reply":"2024-10-25T05:37:51.390497Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **(3) Exploratory Analysis** 🔎","metadata":{}},{"cell_type":"markdown","source":"### **(1) Descripción de los Datos**","metadata":{}},{"cell_type":"code","source":"# Print the number of records in the DataFrame\nprint(\"The given dataset has\", df.shape[0], \"registers and\", df.shape[1], \"columns.\")","metadata":{"id":"VcHCcVnC4YmV","execution":{"iopub.status.busy":"2024-10-25T05:37:56.302964Z","iopub.execute_input":"2024-10-25T05:37:56.303411Z","iopub.status.idle":"2024-10-25T05:37:56.309762Z","shell.execute_reply.started":"2024-10-25T05:37:56.30337Z","shell.execute_reply":"2024-10-25T05:37:56.308487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡 -->**\n> - El conjunto de datos contiene más de mil imágenes de patología digital de alta resolución de diapositivas completas. Cada diapositiva representa un coágulo de sangre de un paciente que sufrió de un accidente cerebrovascular isquémico agudo. \n\n> - En el conjunto `train.csv`, con el que se trabajará por el momento, se cuenta con 754 registros y 5 columnas, lo que indica que tiene una dimensión relativamente pequeña. Cada uno de los 754 registros representa una anotación única con relación a una de las imágenes dentro del directorio `train/`.\n\n**Fuente:** [Página oficial de Kaggle](https://www.kaggle.com/competitions/mayo-clinic-strip-ai/data)","metadata":{}},{"cell_type":"code","source":"# Basic information about the dataset\ndf.info()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:38:00.678894Z","iopub.execute_input":"2024-10-25T05:38:00.679369Z","iopub.status.idle":"2024-10-25T05:38:00.707497Z","shell.execute_reply.started":"2024-10-25T05:38:00.679325Z","shell.execute_reply":"2024-10-25T05:38:00.7064Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- `image_id`: Un identificador único para la instancia, con el formato {patient_id}_{image_num}. Corresponde a la imagen {image_id}.tif.\n- `center_id`: Identifica el centro médico donde se obtuvo la diapositiva.\n- `patient_id`: Identifica al paciente del que se obtuvo la diapositiva.\n- `image_num`: Enumera las imágenes de coágulos obtenidas del mismo paciente.\n- `label`: La etiología del coágulo, que puede ser CE (embolia cardioembólica) o LAA (ataque isquémico). Este campo es el objetivo de clasificación.","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-block alert-info\">\n<b>Nota importante:</b> Las diapositivas que conforman los conjuntos de entrenamiento y prueba representan coágulos con una etiología (es decir, origen) que se conoce como CE (cardioembólica) o LAA (aterosclerosis de grandes arterias).\n</div>\n\n**Fuente:** [Página oficial de Kaggle](https://www.kaggle.com/competitions/mayo-clinic-strip-ai/data)","metadata":{}},{"cell_type":"markdown","source":"### **(2) Clasificación de Variables**","metadata":{}},{"cell_type":"markdown","source":"| Nombre      | Descripción                                                       | Tipo                          |\n|-------------|-------------------------------------------------------------------|-------------------------------|\n| image_id    | Identificador único de la imagen.                                 | Cualitativa (Nominal)         |\n| center_id   | Identificador del centro médico donde se tomó la diapositiva.     | Cualitativa (Nominal)         |\n| patient_id  | Identificación del paciente de la diapositiva.                    | Cualitativa (Nominal)         |\n| image_num   | Número que indica la secuencia de imágenes de un mismo paciente.  | Cuantitativa (Discreta)       |\n| label       | Clasificación del coágulo: CE (cardioembólica) o LAA (aterosclerosis). | Cualitativa (Nominal)    |\n","metadata":{}},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - En nuestro conjunto de datos, la mayoría de las variables son de tipo **cualitativo nominal**.\n\n> - Por otro lado, solo una variable es de tipo **cuantitativo discreto**, que corresponde al número de imagen.","metadata":{}},{"cell_type":"markdown","source":"### **(3) Exploración y Limpieza Inicial de los Datos**","metadata":{}},{"cell_type":"markdown","source":"#### **(1) Análisis de Data Faltante**","metadata":{}},{"cell_type":"code","source":"df.isnull().sum()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:38:05.88437Z","iopub.execute_input":"2024-10-25T05:38:05.884802Z","iopub.status.idle":"2024-10-25T05:38:05.89423Z","shell.execute_reply.started":"2024-10-25T05:38:05.884761Z","shell.execute_reply":"2024-10-25T05:38:05.892906Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - A partir de este breve análisis de los datos faltantes, podemos observar que no hay columnas con valores nulos. Esto significa que no es necesario realizar ningún tipo de imputación en el conjunto de datos. Todas las variables están completamente pobladas, lo que garantiza la integridad de los datos para su análisis posterior.","metadata":{}},{"cell_type":"markdown","source":"#### **(2) Previsualización de Imágenes**","metadata":{}},{"cell_type":"code","source":"# Assuming your DataFrame is named df\nbase_path = \"../input/mayo-clinic-strip-ai/train/\"\n\n# Add the full path to the df\ndf['image_path'] = base_path + df['image_id'] + '.tif'\n\n# Preview the DataFrame to ensure the new column is added correctly\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:38:10.510792Z","iopub.execute_input":"2024-10-25T05:38:10.511211Z","iopub.status.idle":"2024-10-25T05:38:10.526216Z","shell.execute_reply.started":"2024-10-25T05:38:10.511172Z","shell.execute_reply":"2024-10-25T05:38:10.524967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - Una de las principales técnicas de preprocesamiento será **incluir el path o la dirección de cada imagen** dentro del DataFrame. Esto permitirá acceder a las imágenes de manera más sencilla, ya que, como se ha mencionado anteriormente, todas las imágenes se encuentran almacenadas en el directorio `train`.","metadata":{}},{"cell_type":"code","source":"def plot_images(df, num_images=5):\n    # Select first n images\n    sample_df = df.head(num_images)\n    \n    # Create subplots\n    fig, axes = plt.subplots(1, num_images, figsize=(15, 5))\n    \n    # Flatten axes to make it easier to iterate\n    axes = axes.flatten()\n    \n    for i, (img_path, label) in enumerate(zip(sample_df['image_path'], sample_df['label'])):\n        img = Image.open(img_path)\n        img.thumbnail((300,300), Image.Resampling.LANCZOS)\n        axes[i].imshow(img)\n        axes[i].set_title(label)\n        axes[i].axis('off')  # Hide axes\n    \n    plt.tight_layout()\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:38:16.729465Z","iopub.execute_input":"2024-10-25T05:38:16.729897Z","iopub.status.idle":"2024-10-25T05:38:16.737502Z","shell.execute_reply.started":"2024-10-25T05:38:16.729856Z","shell.execute_reply":"2024-10-25T05:38:16.736087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Show 5 images\nplot_images(df)","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:38:20.568897Z","iopub.execute_input":"2024-10-25T05:38:20.569335Z","iopub.status.idle":"2024-10-25T05:39:34.731464Z","shell.execute_reply.started":"2024-10-25T05:38:20.569287Z","shell.execute_reply":"2024-10-25T05:39:34.729971Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - Una de las primeras observaciones clave es la gran variabilidad en los tamaños de las imágenes presentes en el conjunto de datos. Estas imágenes muestran diferentes valores de relación de aspecto, lo cual puede presentar desafíos al momento de alimentar estos datos en un modelo predictivo. \n\n> - También hemos detectado una variación considerable en las características cromáticas de las imágenes. Por ejemplo, algunas de ellas presentan fondos blancos, mientras que otras tienen fondos grises. Esto puede introducir ruido en el modelo si no se trata adecuadamente, ya que los modelos de visión por computadora son sensibles a las variaciones en las condiciones de iluminación y color de las imágenes.\n\n> - Adicionalmente, las imágenes presentan una mezcla de elementos que pueden afectar la precisión del modelo. Algunos coágulos están claramente definidos, mientras que otros están parcialmente oscurecidos o presentan sombras y brillos que pueden confundir al modelo.\n\nTodas extas observaciones reflejan los primeros desafíos que enfrentaremos en el preprocesamiento de imágenes, un paso que se realizará más adelante.","metadata":{}},{"cell_type":"markdown","source":"#### **(3) Distribución de Labels**","metadata":{}},{"cell_type":"code","source":"def label_distribution(df, name='Training Dataframe'):\n  label_counts = df['label'].value_counts().sort_index()\n  total_counts = label_counts.sum()\n    \n  # Calculate percentages\n  label_percentages = (label_counts / total_counts) * 100\n    \n  # Plot counts\n  plt.plot(1, 2, 1)\n  label_counts.plot(kind='bar', color=[palette[0], palette[6]])\n  plt.title(f'{name}: Label Counts')\n  plt.xlabel('Label')\n  plt.ylabel('Count')\n  plt.tight_layout()\n  plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:40:32.050338Z","iopub.execute_input":"2024-10-25T05:40:32.051237Z","iopub.status.idle":"2024-10-25T05:40:32.05803Z","shell.execute_reply.started":"2024-10-25T05:40:32.051188Z","shell.execute_reply":"2024-10-25T05:40:32.056878Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_distribution(df)","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:40:35.404155Z","iopub.execute_input":"2024-10-25T05:40:35.404579Z","iopub.status.idle":"2024-10-25T05:40:35.739271Z","shell.execute_reply.started":"2024-10-25T05:40:35.404539Z","shell.execute_reply":"2024-10-25T05:40:35.738045Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - Nuestro conjunto de datos presenta un **fuerte desbalance**, ya que se cuenta con aproximadamente 550 imágenes de coágulos de **etiología cardioembólica (CE)**, lo que representa más del 70.0% del total de muestras.\n  \n> - En contraste, solo se disponen de 200 imágenes de coágulos de **etiología aterosclerótica de grandes arterias (LAA)**, lo que corresponde a **menos del 30.0%** del conjunto de datos.\n\n> - Este desequilibrio entre las clases puede tener un impacto negativo en el rendimiento del modelo predictivo, ya que probablemente este se incline a **predecir mayormente la clase CE** debido a la falta de representatividad de la clase LAA. Un modelo sesgado de esta manera podría no capturar adecuadamente las características distintivas de los coágulos LAA, reduciendo su capacidad para hacer predicciones precisas.\n\n> - Para mitigar este problema, se tendrá que explorar técnicas como **submuestreo de la clase mayoritaria**, o el uso de algoritmos tales como **data augmentation**. Aunque esto forma parte de los siguientes pasos a tomar.","metadata":{}},{"cell_type":"markdown","source":"#### **(4) Análisis de Tamaños de Imagen**","metadata":{}},{"cell_type":"code","source":"def get_image_size(image_path):\n    try:\n        with Image.open(image_path) as img:\n            return img.size  # Returns (width, height)\n    except Exception as e:\n        print(f\"Error loading {image_path}: {e}\")\n        return None\n\n# Applying the function to the DataFrame\ndf['image_size'] = df['image_path'].apply(get_image_size)\n\n# Separate width and height if needed\ndf['width'], df['height'] = zip(*df['image_size'].dropna())","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:40:40.707138Z","iopub.execute_input":"2024-10-25T05:40:40.707565Z","iopub.status.idle":"2024-10-25T05:40:59.647461Z","shell.execute_reply.started":"2024-10-25T05:40:40.707526Z","shell.execute_reply":"2024-10-25T05:40:59.646125Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - Como parte del proceso de preprocesamiento de datos, se extraerán las dimensiones (ancho y alto) de cada imagen del DataFrame. Estas dimensiones se almacenarán en dos columnas separadas (width y height) para facilitar el acceso y análisis de esta información. Este paso es importante ya que nos permitirá analizar la distribución de los tamaños de las imágenes, lo cual puede ser útil para determinar de qué manera se deberá redimensionar estas en los próximos pasos.","metadata":{}},{"cell_type":"code","source":"# Set up the plot size and style\nplt.figure(figsize=(16, 5))\n\n# Plot the distribution of the width\nplt.subplot(1, 3, 1)\nsns.histplot(df['width'], kde=True, color=palette[0])\nplt.title('Distribution of Image Widths')\nplt.xlabel('Width (pixels)')\nplt.ylabel('Frequency')\n\n# Plot the distribution of the height\nplt.subplot(1, 3, 2)\nsns.histplot(df['height'], kde=True, color= palette[4])\nplt.title('Distribution of Image Heights')\nplt.xlabel('Height (pixels)')\nplt.ylabel('Frequency')\n\n# Show the plots\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:41:00.641545Z","iopub.execute_input":"2024-10-25T05:41:00.642077Z","iopub.status.idle":"2024-10-25T05:41:01.629498Z","shell.execute_reply.started":"2024-10-25T05:41:00.642005Z","shell.execute_reply":"2024-10-25T05:41:01.628316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.scatterplot(data=df, x='width', y='height', hue='label', palette=[palette[0], palette[5]])\nplt.title('Image Dimensions by Label')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-28T01:26:28.484245Z","iopub.execute_input":"2024-09-28T01:26:28.484651Z","iopub.status.idle":"2024-09-28T01:26:28.909871Z","shell.execute_reply.started":"2024-09-28T01:26:28.484606Z","shell.execute_reply":"2024-09-28T01:26:28.909002Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - En estos nuevos gráficos se confirma nuevamente la presencia de múltiples valores tanto en altura como en ancho de las imágenes. La mayoría de las imágenes presentan un ancho concentrado alrededor de los 20,000 píxeles, mientras que la altura se encuentra predominantemente alrededor de los 40,000 píxeles, como lo muestra claramente el histograma.\n\n> - Además, al continuar con este análisis, podemos observar en el diagrama de dispersión que existe una relación predominante entre el tamaño de las imágenes y el tipo de etiología del coágulo del paciente.\n\n> - Se han identificado **datos atípicos**, específicamente en relación con las dimensiones de las imágenes. Sin embargo, estos valores no son de gran relevancia, ya que se normalizarán al redimensionar las imágenes para su inclusión en el modelo.\n\nA continuación veremos cuál es la imagen que posee las dimensiones más grandes y sus detalles.","metadata":{}},{"cell_type":"code","source":"# Find the largest image without creating a new column for area\nlargest_image_index = (df['width'] * df['height']).idxmax()\nlargest_image = df.loc[largest_image_index]\n\n# Print the details neatly\nprint(\"Largest Image Details:\")\nprint(\"-\" * 30)\nfor column in df.columns:\n    print(f\"{column}: {largest_image[column]}\")\n    \n# Open and display the image using Matplotlib\nimage_path = largest_image['image_path']\nimg = Image.open(image_path)\nimg.thumbnail((400,400), Image.Resampling.LANCZOS)\n\nplt.imshow(img)\nplt.axis('off')  # Hide axes\nplt.title(f\"Largest Image: {largest_image['image_id']}\")\nplt.show()\n\ndel img","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:41:13.150185Z","iopub.execute_input":"2024-10-25T05:41:13.150621Z","iopub.status.idle":"2024-10-25T05:42:44.06316Z","shell.execute_reply.started":"2024-10-25T05:41:13.15058Z","shell.execute_reply":"2024-10-25T05:42:44.061518Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### **(5) Cross-Tab Analysis**","metadata":{}},{"cell_type":"code","source":"# Create a cross-tabulation of 'center_id' and 'label'\ncross_tab = pd.crosstab(df['center_id'], df['label'])\n\n# Display the cross-tabulation table\ncross_tab.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:42:59.673749Z","iopub.execute_input":"2024-10-25T05:42:59.674388Z","iopub.status.idle":"2024-10-25T05:42:59.713695Z","shell.execute_reply.started":"2024-10-25T05:42:59.67432Z","shell.execute_reply":"2024-10-25T05:42:59.71256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a stacked bar plot for better visualization\ncross_tab.plot(kind='bar', stacked=True, color=[palette[0], palette[8]])\n\n# Adding titles and labels\nplt.title('Cross-Tabulation of CE and LAA by Center ID')\nplt.xlabel('Center ID')\nplt.ylabel('Count of Labels')\nplt.legend(title='Label', loc='upper right')\n\n# Show plot\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-25T05:43:03.774969Z","iopub.execute_input":"2024-10-25T05:43:03.775816Z","iopub.status.idle":"2024-10-25T05:43:04.306513Z","shell.execute_reply.started":"2024-10-25T05:43:03.775768Z","shell.execute_reply":"2024-10-25T05:43:04.305332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - El Centro 11 tiene la mayor cantidad de imágenes, predominantemente etiquetadas como CE (179) en comparación con LAA (7). Este desequilibrio significativo sugiere un posible enfoque o especialización en casos cardioembólicos en este centro.\n\n> - El Centro 4 también muestra una fuerte prevalencia de CE con 88 casos frente a 26 casos de LAA.\n\n> - Los Centros 3 y 7 demuestran una distribución más equilibrada, con el Centro 3 teniendo 22 CE y 27 LAA, y el Centro 7 reportando 70 CE frente a 29 LAA. \n\n> - Otros centros, como el Centro 2 y el Centro 5, presentan un menor número de casos en general, con el Centro 2 mostrando 26 CE y 3 LAA, lo que indica una posible subrepresentación de casos de aterosclerosis de grandes arterias.\n\nVemos que en general, los datos sugieren que los centros están más especializados en etiologías de accidente cerebrovascular, particularmente en casos **cardioembólicos**.","metadata":{}},{"cell_type":"markdown","source":"#### **(6) Tablas de Frecuencia**","metadata":{}},{"cell_type":"code","source":"# Define a function for formatted printing\ndef print_frequent_values(df):\n    # Iterate over each column in the DataFrame\n    for column in df.columns:\n        frequency_values = df[column].value_counts().head(10)\n        print(f\"\\n{'='*50}\\nTop 10 most frequent values for column '{column}':\")\n        print(f\"{'Index':<5} {'Value':<30} {'Frequency':<10}\")\n        print('-' * 50)  # Separator line\n\n        for index, (value, frequency) in enumerate(frequency_values.items(), start=1):\n            print(f\"{index:<5} {str(value)[:30]:<30} {frequency:<10}\")\n\n        print(f\"{'='*50}\")","metadata":{"execution":{"iopub.status.busy":"2024-09-28T01:27:23.784669Z","iopub.execute_input":"2024-09-28T01:27:23.784979Z","iopub.status.idle":"2024-09-28T01:27:23.790953Z","shell.execute_reply.started":"2024-09-28T01:27:23.784946Z","shell.execute_reply":"2024-09-28T01:27:23.790011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Call the function\nprint_frequent_values(df)","metadata":{"execution":{"iopub.status.busy":"2024-09-28T01:27:23.792049Z","iopub.execute_input":"2024-09-28T01:27:23.792356Z","iopub.status.idle":"2024-09-28T01:27:23.81032Z","shell.execute_reply.started":"2024-09-28T01:27:23.792323Z","shell.execute_reply":"2024-09-28T01:27:23.809244Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n\n> - Tal como se espera, el valor más frecuente en la columna `center_id` es 11, que aparece 257 veces, lo que indica que es el centro médico principal involucrado en la mayoría de los casos. Otros centros notables incluyen el 4, con 114 ocurrencias, y el 7, con 99.\n\n> - En la columna `patient_id`, los IDs más frecuentes (por ejemplo, 91b9d3, 3d10be y 09644e) aparecen cada uno 5 veces, lo que indica que estos pacientes tienen múltiples imágenes tomadas para análisis. Esta repetición sugiere un posible enfoque en pacientes específicos con imágenes recurrentes, posiblemente debido a un seguimiento continuo de condiciones particulares. \n\n> - Finalmente, la columna `image_num` muestra que 0 es el valor más común, con 632 ocurrencias, lo que sugiere que el conjunto de datos consiste principalmente en las primeras imágenes tomadas de varios pacientes. Los valores posteriores, con 1 apareciendo 89 veces y 2 solo 21 veces, indican que hay menos imágenes subsiguientes por paciente. Este patrón podría sugerir que la mayoría de los casos implican evaluaciones iniciales en lugar de imágenes de seguimiento.","metadata":{}},{"cell_type":"markdown","source":"#### **(7) Análisis de Pacientes**","metadata":{}},{"cell_type":"code","source":"# Count unique patients\nunique_patient_count = df['patient_id'].nunique()\n\n# Print the result neatly\nprint(f\"Unique Patients Count: {unique_patient_count}\")","metadata":{"execution":{"iopub.status.busy":"2024-09-28T01:27:23.811607Z","iopub.execute_input":"2024-09-28T01:27:23.811987Z","iopub.status.idle":"2024-09-28T01:27:23.818275Z","shell.execute_reply.started":"2024-09-28T01:27:23.811945Z","shell.execute_reply":"2024-09-28T01:27:23.817244Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - Como se ha mencionado anteriormente, solamente algunos pacientes tienen más de una imagen asociada a su persona, por lo que en nuestro conjunto de datos hay imágenes de 630 pacientes **únicos**.","metadata":{}},{"cell_type":"markdown","source":"#### **(8) Análisis de Valores Duplicados**","metadata":{}},{"cell_type":"code","source":"# Check duplicate rows in dataset\ndf = df.drop_duplicates()\n# Print the number of records in the DataFrame\nprint(\"The given dataset has\", df.shape[0], \"registers and\", df.shape[1], \"columns.\")","metadata":{"execution":{"iopub.status.busy":"2024-09-28T01:27:23.819872Z","iopub.execute_input":"2024-09-28T01:27:23.820215Z","iopub.status.idle":"2024-09-28T01:27:23.830374Z","shell.execute_reply.started":"2024-09-28T01:27:23.82018Z","shell.execute_reply":"2024-09-28T01:27:23.829309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - Vemos que no existen valores duplicados en el conjunto de datos.","metadata":{}},{"cell_type":"markdown","source":"#### **(9) Análisis de Datos Atípicos**","metadata":{}},{"cell_type":"code","source":"# Calculate quartiles\n# Calculate the first quartile (Q1)\nQ1 = df[\"width\"].quantile(0.25)\n# Calculate the third quartile (Q3)\nQ3 = df[\"width\"].quantile(0.75)\n# Calculate the interquartile range (IQR)\nIQR = Q3 - Q1\n\n# Create the box and whisker plot with quartiles\nsns.boxplot(x=\"width\", data=df, palette=palette)\nplt.title(\"Diagrama de Caja y Bigotes para Ancho de Imagen\")\nplt.xlabel(\"Ancho de Imagen\")\n\n# Add quartile information as text annotations\nplt.text(0.95, 0.9, f\"Q1: {Q1:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.8, f\"IQR: {IQR:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.85, f\"Q3: {Q3:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\n\n# Adding grid with custom style\nplt.grid(True, linestyle='--', linewidth=0.5)  # Adding grid with dashed lines and custom line width\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-29T20:20:19.587032Z","iopub.execute_input":"2024-09-29T20:20:19.587658Z","iopub.status.idle":"2024-09-29T20:20:19.820712Z","shell.execute_reply.started":"2024-09-29T20:20:19.587618Z","shell.execute_reply":"2024-09-29T20:20:19.819818Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - Se observa en las estadísticas del conjunto de datos para el ancho de las imágenes que la mediana (50%) es de 18,700 píxeles, lo que indica que la mitad de las imágenes tienen un ancho igual o menor a este valor. \n\n> - El primer cuartil (25%) se encuentra en 13,215 píxeles y el tercer cuartil (75%) en 26,376.75 píxeles, lo que sugiere que la mayoría de las imágenes tienen un tamaño moderado, con un rango intercuartílico (IQR) de 13,161.5 píxeles.\n\n> - El valor máximo de 99,699 píxeles, así como la desviación estándar de 15,653.64 píxeles indica sugieren que existe una variabilidad notable entre los tamaños de las imágenes y revelan la existencia de imágenes significativamente más anchas en el conjunto","metadata":{}},{"cell_type":"code","source":"# Calculate quartiles\n# Calculate the first quartile (Q1)\nQ1 = df[\"height\"].quantile(0.25)\n# Calculate the third quartile (Q3)\nQ3 = df[\"height\"].quantile(0.75)\n# Calculate the interquartile range (IQR)\nIQR = Q3 - Q1\n\n# Create the box and whisker plot with quartiles\nsns.boxplot(x=\"height\", data=df, palette=palette)\nplt.title(\"Diagrama de Caja y Bigotes para Alto de Imagen\")\nplt.xlabel(\"Alto de Imagen\")\n\n# Add quartile information as text annotations\nplt.text(0.95, 0.9, f\"Q1: {Q1:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.8, f\"IQR: {IQR:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.85, f\"Q3: {Q3:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\n\n# Adding grid with custom style\nplt.grid(True, linestyle='--', linewidth=0.5)  # Adding grid with dashed lines and custom line width\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-29T20:21:07.808177Z","iopub.execute_input":"2024-09-29T20:21:07.809085Z","iopub.status.idle":"2024-09-29T20:21:08.114497Z","shell.execute_reply.started":"2024-09-29T20:21:07.809041Z","shell.execute_reply":"2024-09-29T20:21:08.113552Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - Se observa que la mediana (50%) es de 34,981.5 píxeles, lo que indica que la mitad de las imágenes tiene una altura igual o menor a este valor.\n\n> - El primer cuartil (25%) se sitúa en 25,402.5 píxeles y el tercer cuartil (75%) en 48,919.75 píxeles, sugiriendo que la mayoría de las imágenes poseen una altura moderada, con un rango intercuartílico (IQR) de 23,517.25 píxeles.\n\n> - La media de 37,622.20 píxeles refleja la altura promedio de las imágenes, mientras que la desviación estándar de 18,058.75 píxeles indica una considerable dispersión en las alturas.","metadata":{}},{"cell_type":"code","source":"# Calculate quartiles\n# Calculate the first quartile (Q1)\nQ1 = df[\"image_num\"].quantile(0.25)\n# Calculate the third quartile (Q3)\nQ3 = df[\"image_num\"].quantile(0.75)\n# Calculate the interquartile range (IQR)\nIQR = Q3 - Q1\n\n# Create the box and whisker plot with quartiles\nsns.boxplot(x=\"image_num\", data=df, palette=palette)\nplt.title(\"Diagrama de Caja y Bigotes para Cantidad de Imagenes\")\nplt.xlabel(\"Cantidad de Imagenes\")\n\n# Add quartile information as text annotations\nplt.text(0.95, 0.9, f\"Q1: {Q1:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.8, f\"IQR: {IQR:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\nplt.text(0.95, 0.85, f\"Q3: {Q3:.2f}\", transform=plt.gca().transAxes, ha=\"right\")\n\n# Adding grid with custom style\nplt.grid(True, linestyle='--', linewidth=0.5)  # Adding grid with dashed lines and custom line width\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-29T20:22:02.396732Z","iopub.execute_input":"2024-09-29T20:22:02.39771Z","iopub.status.idle":"2024-09-29T20:22:02.725371Z","shell.execute_reply.started":"2024-09-29T20:22:02.397666Z","shell.execute_reply":"2024-09-29T20:22:02.72435Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define the limits to determine outliers\nlower_limit = Q1 - 1.5 * IQR\nupper_limit = Q3 + 1.5 * IQR\n\n# Count the number of outliers\noutliers = df[(df[\"image_num\"] < lower_limit) | (df[\"image_num\"] > upper_limit)]\nnum_outliers = len(outliers)\n\n# Calculate the percentage of outliers\npercentage_outliers = (num_outliers / len(df)) * 100\n\nprint(\"Outliers Percentage in 'image_num':\", round(percentage_outliers, 2))","metadata":{"execution":{"iopub.status.busy":"2024-09-29T21:11:05.375674Z","iopub.execute_input":"2024-09-29T21:11:05.376698Z","iopub.status.idle":"2024-09-29T21:11:05.384608Z","shell.execute_reply.started":"2024-09-29T21:11:05.376657Z","shell.execute_reply":"2024-09-29T21:11:05.383642Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"\\nTraining Set: 'image_num' Statistics\")\nprint(df['image_num'].describe())","metadata":{"execution":{"iopub.status.busy":"2024-09-29T20:24:25.038443Z","iopub.execute_input":"2024-09-29T20:24:25.03933Z","iopub.status.idle":"2024-09-29T20:24:25.047935Z","shell.execute_reply.started":"2024-09-29T20:24:25.039292Z","shell.execute_reply":"2024-09-29T20:24:25.04699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create a cross-tabulation of 'center_id' and 'label'\ncross_tab = pd.crosstab(df['image_num'], df['label'])\n\n# Display the cross-tabulation table\ncross_tab.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-29T21:25:04.64689Z","iopub.execute_input":"2024-09-29T21:25:04.647746Z","iopub.status.idle":"2024-09-29T21:25:04.680633Z","shell.execute_reply.started":"2024-09-29T21:25:04.647709Z","shell.execute_reply":"2024-09-29T21:25:04.679705Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a stacked bar plot for better visualization\ncross_tab.plot(kind='bar', stacked=True, color=[palette[0], palette[8]])\n\n# Adding titles and labels\nplt.title('Cross-Tabulation of CE and LAA by Image Number')\nplt.xlabel('Image Number')\nplt.ylabel('Count of Labels')\nplt.legend(title='Label', loc='upper right')\n\n# Show plot\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-29T21:28:38.645515Z","iopub.execute_input":"2024-09-29T21:28:38.646263Z","iopub.status.idle":"2024-09-29T21:28:39.084582Z","shell.execute_reply.started":"2024-09-29T21:28:38.646221Z","shell.execute_reply":"2024-09-29T21:28:39.083421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡**\n> - El valor de todos los cuartiles es de 0, lo que indica que el conjunto de datos consiste principalmente en las primeras imágenes tomadas de varios pacientes. Sin embargo, el valor máximo de 4 indica que hay algunos pacientes que presentan hasta cuatro imágenes de coágulos, lo que introduce cierta variabilidad en el conjunto de datos.\n\n> - Los puntos atípicos representan alrededor del 16.18% de la totalidad de los datos. \n","metadata":{}},{"cell_type":"markdown","source":"## **(4) Image Preprocessing 📷**","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom sklearn.model_selection import StratifiedKFold\nfrom multiprocessing import Pool\nimport openslide\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Definir aumentaciones avanzadas para mayor variabilidad en los datos\nminority_augmentations = A.Compose([\n    A.HorizontalFlip(p=0.8),\n    A.VerticalFlip(p=0.8),\n    A.RandomRotate90(p=0.8),\n    A.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4, hue=0.2, p=0.8),\n    A.Perspective(p=0.5),\n    A.RandomBrightnessContrast(p=0.6),\n    A.CoarseDropout(max_holes=10, max_height=20, max_width=20, min_holes=1, p=0.5),\n    A.Resize(256, 256),\n    ToTensorV2()\n])\n\nmajority_augmentations = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.RandomRotate90(p=0.5),\n    A.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.2, p=0.5),\n    A.Resize(256, 256),\n    ToTensorV2()\n])\n\n# Verificación de fondo para asegurar parches informativos\ndef is_background_patch(img, std_threshold=15, mean_diff_threshold=10):\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    if np.std(gray) < std_threshold:\n        return True\n    mean_r, mean_g, mean_b = img[..., 0].mean(), img[..., 1].mean(), img[..., 2].mean()\n    return max(abs(mean_r - mean_g), abs(mean_r - mean_b), abs(mean_g - mean_b)) < mean_diff_threshold\n\n# Verificar si un parche es válido\ndef is_valid_patch(img, threshold=15):\n    grayscale = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    return np.std(grayscale) >= threshold\n\n# Procesar parches y aplicar aumentaciones balanceadas\ndef preprocess_image(args):\n    image_path, label, idx, num_patches = args\n    print(f\"Procesando imagen en el índice {idx}\")\n    \n    slide = openslide.OpenSlide(image_path)\n    width, height = slide.dimensions\n    patches = []\n    \n    patch_size = 512\n    step_size = patch_size // 2\n    max_attempts = num_patches * 30\n    \n    attempts = 0\n    for y in range(0, height - patch_size + 1, step_size):\n        for x in range(0, width - patch_size + 1, step_size):\n            if len(patches) >= num_patches:\n                break\n            \n            img = slide.read_region((x, y), 0, (patch_size, patch_size)).convert(\"RGB\")\n            img = np.array(img)\n            \n            if is_background_patch(img):\n                continue\n            \n            if is_valid_patch(img):\n                if label == 'LAA':  # Clase minoritaria\n                    augmented = minority_augmentations(image=img)\n                    patches.extend([augmented['image']] * 2)  # Añadir duplicados\n                else:  # Clase mayoritaria\n                    augmented = majority_augmentations(image=img)\n                    patches.append(augmented['image'])\n                \n                attempts = 0\n            else:\n                attempts += 1\n            \n            if attempts >= max_attempts:\n                print(f\"Excedido el máximo de intentos en imagen {image_path}\")\n                break\n    \n    if len(patches) < num_patches:\n        print(f\"No se encontraron suficientes parches válidos para la imagen en {image_path}\")\n        return None, None\n    \n    patches_tensor = np.stack(patches[:num_patches], axis=0)  # Equilibrar cantidad final de parches\n    return patches_tensor, label\n\n# Procesar y guardar conjunto de datos en fragmentos\ndef create_and_save_dataset_in_chunks(df, dataset_type, num_patches_per_image=15, chunk_size=5000):\n    images_dir = '/kaggle/input/mayo-clinic-strip-ai/test' if dataset_type == 'test' else '/kaggle/input/mayo-clinic-strip-ai/train'\n    \n    all_images = []\n    all_labels = []\n    total_samples = 0\n    chunk_index = 0\n    \n    args_list = []\n    for idx, row in df.iterrows():\n        image_path = os.path.join(images_dir, f\"{row['image_id']}.tif\") if dataset_type == 'test' else row['image_path']\n        label = None if dataset_type == 'test' else row['label']\n        args_list.append((image_path, label, idx, num_patches_per_image))\n    \n    with Pool(processes=os.cpu_count()) as pool:\n        for patches, label in pool.imap(preprocess_image, args_list):\n            if patches is not None:\n                all_images.append(patches)\n                if dataset_type != 'test':\n                    all_labels.append(label)\n                \n                total_samples += 1\n                \n                if total_samples >= chunk_size:\n                    if dataset_type != 'test':\n                        save_chunk(np.array(all_images), np.array(all_labels), dataset_type, chunk_index)\n                    else:\n                        save_test_chunk(np.array(all_images), chunk_index)\n                    chunk_index += 1\n                    all_images = []\n                    all_labels = []\n                    total_samples = 0\n    \n    if total_samples > 0:\n        if dataset_type != 'test':\n            save_chunk(np.array(all_images), np.array(all_labels), dataset_type, chunk_index)\n        else:\n            save_test_chunk(np.array(all_images), chunk_index)\n\n# Guardar fragmentos de entrenamiento y validación\ndef save_chunk(images, labels, dataset_type, chunk_index):\n    np.save(f'X_{dataset_type}_chunk_{chunk_index}.npy', images)\n    np.save(f'y_{dataset_type}_chunk_{chunk_index}.npy', labels)\n    print(f'Guardado {len(images)} muestras en X_{dataset_type}_chunk_{chunk_index}.npy y y_{dataset_type}_chunk_{chunk_index}.npy')\n\n# Guardar fragmentos del conjunto de prueba\ndef save_test_chunk(images, chunk_index):\n    np.save(f'X_test_chunk_{chunk_index}.npy', images)\n    print(f'Guardado {len(images)} muestras en X_test_chunk_{chunk_index}.npy')\n\n# Cargar el CSV de entrenamiento\ntrain_csv_path = '/kaggle/input/mayo-clinic-strip-ai/train.csv'\ndf_train = pd.read_csv(train_csv_path)\ntrain_images_dir = '/kaggle/input/mayo-clinic-strip-ai/train'\ndf_train['image_path'] = df_train['image_id'].apply(lambda x: os.path.join(train_images_dir, f\"{x}.tif\"))\ndf_train.rename(columns={'target': 'label'}, inplace=True)\n\n# Crear K-Folds estratificados en lugar de dividir una vez\nkf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nfor fold, (train_idx, val_idx) in enumerate(kf.split(df_train, df_train['label'])):\n    print(f'Procesando fold {fold + 1}')\n    df_train_fold = df_train.iloc[train_idx]\n    df_val_fold = df_train.iloc[val_idx]\n    \n    create_and_save_dataset_in_chunks(df_train_fold, dataset_type=f'train_fold{fold}', num_patches_per_image=15, chunk_size=5000)\n    create_and_save_dataset_in_chunks(df_val_fold, dataset_type=f'val_fold{fold}', num_patches_per_image=15, chunk_size=5000)\n\n# Procesar el conjunto de prueba\ntest_csv_path = '/kaggle/input/mayo-clinic-strip-ai/test.csv'\ndf_test = pd.read_csv(test_csv_path)\ncreate_and_save_dataset_in_chunks(df_test, dataset_type='test', num_patches_per_image=15, chunk_size=5000)\n","metadata":{"execution":{"iopub.status.busy":"2024-10-25T07:00:13.957035Z","iopub.execute_input":"2024-10-25T07:00:13.958475Z","iopub.status.idle":"2024-10-25T07:00:32.344378Z","shell.execute_reply.started":"2024-10-25T07:00:13.958412Z","shell.execute_reply":"2024-10-25T07:00:32.343032Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Observaciones 💡 -->**\n\n1. **Conversión a Escala de Grises**\n   - **Código**: `img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)`\n   - **Descripción**: Convierte la imagen de color (BGR) a una imagen en escala de grises.\n   - **Propósito**: Reducir la complejidad del modelo ya que se espera una entrada de un solo canal (grayscale). Permitirá que el entrenamiento sea más rápido.\n\n2. **Normalización de Valores de Pixel**\n   - **Código**: `img = img / 255.0`\n   - **Descripción**: Normaliza los valores de los píxeles a un rango de [0, 1].\n   - **Propósito**: Normalizar los valores de los píxeles asegura que estén en un rango consistente, lo que ayuda a mantener una iluminación y contraste uniformes en todas las imágenes. Esto facilita la comparación de patrones y características, haciendo que las imágenes sean más adecuadas para el análisis. Además, permite que los modelos de aprendizaje automático aprendan patrones de manera más efectiva, sin que las diferencias en las condiciones de iluminación afecten el rendimiento.\n\n3. **Aplicación de Desenfoque Gaussiano**\n   - **Código**: `img = cv2.GaussianBlur(img, (5, 5), 0)`\n   - **Descripción**: Aplica un filtro de desenfoque gaussiano para reducir el ruido en la imagen.\n   - **Propósito**: El desenfoque gaussiano reduce el detalle y el ruido en la imagen aplicando una función gaussiana a cada píxel y sus píxeles circundantes. Esto suaviza los bordes y elimina pequeños detalles que pueden no ser útiles para el aprendizaje, ayudando a la red neuronal a centrarse en las características más relevantes. La reducción de ruido mejora la capacidad de generalización del modelo y puede facilitar tareas como la detección de bordes o la segmentación al reducir las variaciones no deseadas en la imagen.\n\n4. **Expansión de Dimensiones**\n   - **Código**: `img = np.expand_dims(img, axis=-1)`\n   - **Descripción**: Expande las dimensiones de la imagen para agregar un canal adicional.\n   - **Propósito**: Asegura que la imagen tenga un formato consistente para la entrada de la red neuronal, especialmente si la red espera una entrada con un número específico de canales (por ejemplo, [alto, ancho, canales]).\n   \n5. Redimensionamiento de Imagen\n   - **Código**: img = cv2.resize(img, (0,0), fx=0.05, fy=0.05)\n   - **Descripción**: Redimensiona la imagen aplicando un factor de escala del 5% en los ejes horizontal (fx) y vertical (fy).\n   - **Propósito**: Reduce el tamaño de la imagen para disminuir la cantidad de información que se procesa en la red neuronal convolucional (CNN), lo que puede ser útil para ahorrar recursos computacionales o ajustar la entrada a las dimensiones esperadas por el modelo.\n\n**Referencia**\n> - https://medium.com/@maahip1304/the-complete-guide-to-image-preprocessing-techniques-in-python-dca30804550c","metadata":{}}]}