{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.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":71549,"databundleVersionId":8561470,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# RSNA Lumbar Spine Challenge\nAuthors: Abhinav Suri MPH, Andrew Wentland MD PhD, Hari Trivedi MD \n– Radiology Artificial Intelligence Data Standards Committee","metadata":{}},{"cell_type":"markdown","source":"### Purpose of this notebook:\n\nThis notebook aims to give a brief overview of what data is available in the challenge and how to get started with visualizing some of the conditions in our dataset. ","metadata":{}},{"cell_type":"markdown","source":"### Overview of the clinical problem\n\nDegenerative spine conditions adversely affect people’s quality of life. Detecting these conditions is crucial for determining therapeutic plans for patients. Therefore, it is essential to develop methods for detecting and assessing the severity of degenerative spine conditions on imaging.\n \nThis challenge primarily focuses on identifying three types of conditions in the lumbar region of the spine (refer below for the anatomical overview). The three conditions we aim to assess are:\n \n1. Foraminal narrowing (on either the left or right foramen at a specified level).\n2. Subarticular stenosis (on either the left or right side at a specified level).\n3. Canal stenosis (only at a specified level).\n \nEach of these conditions can manifest at various levels within the spine itself, specifically at each vertebral disc (e.g., L4/5 corresponds to the vertebral disc between the L4 and L5 vertebral bodies).\n\n\nFor each of the conditions, you'll need to predict whether the degree of compression is normal/mild, moderate, or severe. You can refer to the example test submission `sample_submission.csv` to get a better idea for what we're looking for in terms of output. For each case, you'll have to output a score from 0 to 1 representing the probability of the patient having a specific grade (`normal_mild`, `moderate`, `severe`), at the spinal level (`l1_l2`, `l2_l3`, `l3_l4`, `l4_l5`, `l5_s1`), for that condition (`spinal_canal_stenosis`, `left_neural_foraminal_narrowing`, `right_neural_foraminal_narrowing`, `left_subarticular_stenosis`, `right_subarticular_stenosis`):\n\n\nLet's talk a bit about the anatomy to get a sense for what we're asking you all to detect.\n\n### Anatomical Overview\n\nThe spine is divided into four regions: the cervical region (with 7 vertebral bodies), the thoracic region (with 12 vertebral bodies), the lumbar region (with 5 vertebral bodies), and the sacral region (with 3-5 fused vertebral bodies). \n\n<img src=\"https://prod-images-static.radiopaedia.org/images/53655832/Gray-square.001_big_gallery.jpeg\" width=400/>\n\n*From [Radiopedia](https://prod-images-static.radiopaedia.org/images/53655832/Gray-square.001_big_gallery.jpeg)*\n\nBetween each vertebral body in all of the regions (except the sacrum) is a vertebral disc. Furthermore, along the posterior aspect of each vertebral body lies the spinal cord. At each vertebral body, spinal nerves leave the spinal cord through openings between vertebral bodies called foramina.\n\n<img src=\"https://files.miamineurosciencecenter.com/media/filer_public_thumbnails/filer_public/78/1e/781e78be-8980-466f-8a82-83a5c8350770/herniated_disc_larger.jpg__720.0x600.0_q85_subject_location-360%2C300_subsampling-2.jpg\" width=400/>  \n\n*From [Miami Neuroscience Center](https://miamineurosciencecenter.com/en/conditions/herniated-disc/)*\n\nCompression of the spinal cord or any of the nerves can cause pain to patients. Things that can cause compression of these nerves/the spinal cord include a bulging vertebral disc, degenerative changes in the bones itself (leading them to grow protrusions/become compressed), trauma, or thickening of the ligaments surrounding the spinal cord.\n\n### Foraminal Narrowing Overview\n\nThe spinal cord has spinal nerves that exit the spinal canal through openings called foramina. The foramina are best viewed in the sagittal plane. Sometimes these openings can become compressed, resulting in foraminal narrowing. This compression results in pain for patients along the nerve distribution that is downstream of the compression. \n\nOn the left, the image shows a sagittal MR slice where the foramina are visible. Crosshairs show where the foramina exit the spinal canal. On the right, the image shows our grading criteria for designating the degree of compression (note for this challenge, Normal/Mild is one label).\n<p float=\"middle\">\n<img src=\"https://i.imgur.com/6c7erNM.png\" width=300/>\n<img src=\"https://i.imgur.com/b1VGiN5.png\" width=300/>\n</p>\n\n### Subarticular Stenosis Overview\n\nSubarticular stenosis is due to compression of the spinal cord in the subarticular zone (this compression can be best visualized in the axial plane).\n\nOn the left is a schematic showing the relevant anatomical zone. On the right is our grading criteria for designating the degree of subarticular stenosis (normal/mild is collapsed into one label for this challenge). \n<p float=\"middle\">\n<img src=\"https://files.miamineurosciencecenter.com/media/filer_public_thumbnails/filer_public/d5/08/d508ae6a-a4f2-4796-be9f-455f8df45fe1/herniation_zones.jpg__1700.0x1308.0_q85_subject_location-850%2C656_subsampling-2.jpg\" width=300/>\n<img src=\"https://i.imgur.com/Usuxgge.png\" width=300/>\n</p>\n\n*Left image from [Miami Neuroscience Center](https://miamineurosciencecenter.com/en/conditions/herniated-disc/)*\n\n\n### Canal Stenosis Overview\n\nCanal stenosis is impingement on the spinal canal (where the spinal cord travels). Impingement can be due to a bulging vertebral disc, trauma, bony osteophytes (outgrowths of the vertebral bodies due to degenerative changes), or ligamental thickening (of the ligaments that run along the length of the spinal canal). The degree of compression is best assessed in the axial plane.\n\nOn the left, we show canal stenosis visible in the sagittal plane (to give an overview of what it looks like). On the right, we show our canal stenosis grading criteria (normal/mild are collapsed into one label). \n\n<p float=\"middle\">\n<img src=\"https://prod-images-static.radiopaedia.org/images/940993/f7a8adca63efae788f621869cc21e8_big_gallery.jpg\" width=300/>\n<img src=\"https://i.imgur.com/opjnAwl.png\" width=300/>\n</p>\n\n*From [Radiopedia](https://prod-images-static.radiopaedia.org/images/940993/f7a8adca63efae788f621869cc21e8_big_gallery.jpg)*\n\n### Imaging Overview\n\nMRI imaging of the spine can be taken in three planes: the axial plane, the sagittal plane, and the coronal plane. The two main image types you'll need for this challenge are the axial and sagittal planes. The axial plane takes images horizonal slices (perpendicular to the spine) across the body from top to bottom. The sagittal plane takes vertical slices (parallel to the spine) going from left to right. \n\nMRI images come in multiple variants. They can generally be classified as either being T1 weighted or T2 weighted. T1 weighted images show fat as being brighter. The inner part of bones would appear brighter on T1 images. T2 images show water as brighter. The spinal canal would appear as brighter on T2 images. MRI images are not standardized with regards to the pixel values that are output from it (unlike CT images). So you'll need to figure out how to standardize these images (or maybe you wont need to at all, we'll leave it up to you). ","metadata":{"jp-MarkdownHeadingCollapsed":true}},{"cell_type":"markdown","source":"## Expected Directory Structure\nWe expect the following to be in your working directory to run this notebook.\n\n```\n.\n├── ExploreData.ipynb **This notebook\n└── /kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/\n    ├── test_images/\n    │   ├── 1005139/\n    │   │   └── 609308237/\n    │   │       ├── 1.dcm\n    │   │       └── ...\n    │   └── ...\n    ├── test_series_descriptions.csv\n    ├── train_images/\n    │   ├── 4003253/\n    │   │   └── 702807833/\n    │   │       ├── 1.dcm\n    │   │       └── ...\n    │   └── ...\n    ├── train_label_coordinates.csv\n    ├── train_series_descriptions.csv\n    └── train.csv\n```","metadata":{}},{"cell_type":"markdown","source":"## Loading Diagnosis Information","metadata":{}},{"cell_type":"markdown","source":"In this part of the notebook, we'll load in information about the diagnoses present in our dataset to give an overview of the distribution of cases.","metadata":{}},{"cell_type":"markdown","source":"Importar librerias necesarias","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport cv2\nimport pydicom\nimport numpy as np\nimport os\nimport glob\nfrom tqdm import tqdm\nimport warnings","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:31.135044Z","iopub.execute_input":"2024-10-10T11:10:31.135522Z","iopub.status.idle":"2024-10-10T11:10:32.843460Z","shell.execute_reply.started":"2024-10-10T11:10:31.135468Z","shell.execute_reply":"2024-10-10T11:10:32.842169Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Leer los datos de entrenamiento","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv')","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:32.845430Z","iopub.execute_input":"2024-10-10T11:10:32.845988Z","iopub.status.idle":"2024-10-10T11:10:32.880367Z","shell.execute_reply.started":"2024-10-10T11:10:32.845948Z","shell.execute_reply":"2024-10-10T11:10:32.879108Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Número de registros que tiene el conjunto de entrenamiento","metadata":{}},{"cell_type":"code","source":"print(\"Total Cases: \", len(train))","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:32.881847Z","iopub.execute_input":"2024-10-10T11:10:32.882197Z","iopub.status.idle":"2024-10-10T11:10:32.889605Z","shell.execute_reply.started":"2024-10-10T11:10:32.882168Z","shell.execute_reply":"2024-10-10T11:10:32.888079Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Columnas del conjunto de entrenamiento","metadata":{}},{"cell_type":"code","source":"train.columns","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:32.892579Z","iopub.execute_input":"2024-10-10T11:10:32.892963Z","iopub.status.idle":"2024-10-10T11:10:32.903591Z","shell.execute_reply.started":"2024-10-10T11:10:32.892934Z","shell.execute_reply":"2024-10-10T11:10:32.902317Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Creación de tres gráficas para ver la cantidad de pacientes que sufren cada diagnosis, y su grado de gravedad","metadata":{}},{"cell_type":"code","source":"figure, axis = plt.subplots(1,3, figsize=(20,5)) \nfor idx, d in enumerate(['foraminal', 'subarticular', 'canal']):\n    diagnosis = list(filter(lambda x: x.find(d) > -1, train.columns))\n    dff = train[diagnosis]\n    with warnings.catch_warnings():\n        warnings.simplefilter(action='ignore', category=FutureWarning)\n        value_counts = dff.apply(pd.value_counts).fillna(0).T\n    value_counts.plot(kind='bar', stacked=True, ax=axis[idx])\n    axis[idx].set_title(f'{d} distribution')","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:32.905053Z","iopub.execute_input":"2024-10-10T11:10:32.905431Z","iopub.status.idle":"2024-10-10T11:10:34.248313Z","shell.execute_reply.started":"2024-10-10T11:10:32.905396Z","shell.execute_reply":"2024-10-10T11:10:34.247153Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As expected many of our patients have normal/mild grades for each of the diagnoses categories. You'll also see that for some of the diagnoses (particularly subarticular stenosis category), there are missing data. This is due to the fact that some of the images do not have those regions visualized (more specifically the most superior vertebral bodies were less likely to make it into the imaging field).","metadata":{}},{"cell_type":"markdown","source":"## Loading in images","metadata":{}},{"cell_type":"markdown","source":"Now that we can see the rough distribution of diagnoses, let's go ahead and load in an example set of scans for one patient. We'll start off by grabbing the scan for one patient as an example. \n\nIn terms of background. Each patient has a Study (called a StudyInstanceUID). That study has multiple series called a (SeriesInstanceUID). Within a series you have multiple images with a unique SOPInstanceUID. \n\nAll of this data is tied together via the csv from earlier that listed diagnoses types, but also a separate dicom metadata file that contains information about the particular series descriptions (containing useful series descriptions such as whether the image is sagital or axial and t1 vs t2). Though the names for the series descriptions are not standardized.","metadata":{}},{"cell_type":"markdown","source":"### Grab metadata for each scan.\nFor each scan let's create an object with the following structure:\n\n```\nmeta_obj = {\n    StudyInstanceUID: {\n        'folder_path': ... # path to the folder,\n        'SeriesInstanceUIDs': [ Array of the SeriesInstanceUIDs ],\n        'SeriesDescriptions' [ Array of the Series Descriptions ]\n    }, ...\n}\n```","metadata":{}},{"cell_type":"markdown","source":"Primero devuelve una lista de los archivos que hay en la ruta '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images' y luego filtra esos archivos para que no contengan 'DS' en su nombre.","metadata":{}},{"cell_type":"code","source":"# List out all of the Studies we have on patients.\npart_1 = os.listdir('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images')\npart_1 = list(filter(lambda x: x.find('.DS') == -1, part_1))","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:34.249588Z","iopub.execute_input":"2024-10-10T11:10:34.249916Z","iopub.status.idle":"2024-10-10T11:10:34.477303Z","shell.execute_reply.started":"2024-10-10T11:10:34.249885Z","shell.execute_reply":"2024-10-10T11:10:34.475892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Lee el archivo 'train_series_descriptions'","metadata":{}},{"cell_type":"code","source":"df_meta_f = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_series_descriptions.csv')","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:34.479282Z","iopub.execute_input":"2024-10-10T11:10:34.479618Z","iopub.status.idle":"2024-10-10T11:10:34.495719Z","shell.execute_reply.started":"2024-10-10T11:10:34.479588Z","shell.execute_reply":"2024-10-10T11:10:34.494444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Crea un un diccionario, donde cada paciente tiene el nombre del archivo, la ruta completa hacia la carpeta donde se encuentra ese archivo y una lista vacía 'SeriesInstanceUIDs'","metadata":{}},{"cell_type":"code","source":"p1 = [(x, f\"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/{x}\") for x in part_1]\nmeta_obj = { p[0]: { 'folder_path': p[1], \n                    'SeriesInstanceUIDs': [] \n                   } \n            for p in p1 }","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:34.497181Z","iopub.execute_input":"2024-10-10T11:10:34.497620Z","iopub.status.idle":"2024-10-10T11:10:34.509158Z","shell.execute_reply.started":"2024-10-10T11:10:34.497579Z","shell.execute_reply":"2024-10-10T11:10:34.507806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Añade a la lista 'SeriesInstanceUIDs' de cada paciente imágenes que estan en la carpeta de cada uno, excluyendo a los archivos .DS","metadata":{}},{"cell_type":"code","source":"for m in meta_obj:\n    meta_obj[m]['SeriesInstanceUIDs'] = list(\n        filter(lambda x: x.find('.DS') == -1, \n               os.listdir(meta_obj[m]['folder_path'])\n              )\n    )","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:34.510942Z","iopub.execute_input":"2024-10-10T11:10:34.511413Z","iopub.status.idle":"2024-10-10T11:10:39.732354Z","shell.execute_reply.started":"2024-10-10T11:10:34.511372Z","shell.execute_reply":"2024-10-10T11:10:39.731188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Añade a cada 'SeriesInstanceUIDs' de cada paciente una descripción, pero si se produce algún error, lo imprime por pantalla. De esta manera, asociará a cada serie del paciente con una descripción del estudio, esto facilitará el análisis","metadata":{}},{"cell_type":"code","source":"# grabs the correspoding series descriptions\nfor k in tqdm(meta_obj):\n    for s in meta_obj[k]['SeriesInstanceUIDs']:\n        if 'SeriesDescriptions' not in meta_obj[k]:\n            meta_obj[k]['SeriesDescriptions'] = []\n        try:\n            meta_obj[k]['SeriesDescriptions'].append(\n                df_meta_f[(df_meta_f['study_id'] == int(k)) & \n                (df_meta_f['series_id'] == int(s))]['series_description'].iloc[0])\n        except:\n            print(\"Failed on\", s, k)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:39.736973Z","iopub.execute_input":"2024-10-10T11:10:39.737461Z","iopub.status.idle":"2024-10-10T11:10:43.308699Z","shell.execute_reply.started":"2024-10-10T11:10:39.737422Z","shell.execute_reply":"2024-10-10T11:10:43.307580Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Selecciona el segundo elemento del objeto meta_obj y devuelve toda la información: ruta completa de la carpeta del paciente, las identificaciones de las series y las descripciones de cada serie","metadata":{}},{"cell_type":"code","source":"meta_obj[list(meta_obj.keys())[1]]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.310354Z","iopub.execute_input":"2024-10-10T11:10:43.310778Z","iopub.status.idle":"2024-10-10T11:10:43.318905Z","shell.execute_reply.started":"2024-10-10T11:10:43.310741Z","shell.execute_reply":"2024-10-10T11:10:43.317845Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Pull up images for one patient","metadata":{}},{"cell_type":"markdown","source":"Now that we've made our meta obj, let's pull up all the images for one patient.","metadata":{}},{"cell_type":"markdown","source":"Selecciona la segunda fila del conjunto de entrenamiento y se la asigna a la variable patient","metadata":{}},{"cell_type":"code","source":"patient = train.iloc[1]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.320098Z","iopub.execute_input":"2024-10-10T11:10:43.320435Z","iopub.status.idle":"2024-10-10T11:10:43.338460Z","shell.execute_reply.started":"2024-10-10T11:10:43.320408Z","shell.execute_reply":"2024-10-10T11:10:43.337270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Accede a los datos del estudio que se encuentran en 'meta_obj' mediante el id de estudio que tiene ese paciente, que se encuentra en la columna 'study_id'","metadata":{}},{"cell_type":"code","source":"ptobj = meta_obj[str(patient['study_id'])]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.339842Z","iopub.execute_input":"2024-10-10T11:10:43.340221Z","iopub.status.idle":"2024-10-10T11:10:43.351150Z","shell.execute_reply.started":"2024-10-10T11:10:43.340184Z","shell.execute_reply":"2024-10-10T11:10:43.350083Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Imprime por pantalla la información de 'ptobj': la ruta completa de la carpeta del paciente, los identificadores de las series y sus descripciones","metadata":{}},{"cell_type":"code","source":"print(ptobj)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.352585Z","iopub.execute_input":"2024-10-10T11:10:43.353020Z","iopub.status.idle":"2024-10-10T11:10:43.363743Z","shell.execute_reply.started":"2024-10-10T11:10:43.352981Z","shell.execute_reply":"2024-10-10T11:10:43.362515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Le da formato a los datos. \n- Primero crea el diccionario 'im_list_dcm' vacío, que es donde almacenará la información formateada.\n- Luego recorre las series del paciente, de tal forma que usa 'idx' para saber el índice de cada serie, e 'i' para saber el valor de la serie\n- Añade en la posición 'i' un nuevo diccionario que contiene una lista vacía de imágenes 'images' y una descripción, que coincide con la descripción de la serie que teníamos antes en la posición 'idx'\n- Luego utiliza 'glob' para buscar en la ruta de la carpeta completa a la serie actual y extrae todos los archivos con extensión '.dcm' (archivos DICOM)\n- Finalmente, hace un nuevo bucle de las imágenes ordenadas en función del número que aparece en el nombre de cada archivo '.dcm', quitándo la extensión\n- Dentro de dicho bucle lo que se hace es añadir al diccionario creado, en la lista 'images', cada una de las imágenes que hay en la ruta. Esto es, extrae el identificador único de la imagen a partir del nombre y lo almacena en 'SOPInstanceUID', y lee los archivos DICOM y los guarda en 'dicom'","metadata":{}},{"cell_type":"code","source":"# Get data into the format\n\"\"\"\nim_list_dcm = {\n    '{SeriesInstanceUID}': {\n        'images': [\n            {'SOPInstanceUID': ...,\n             'dicom': PyDicom object\n            },\n            ...,\n        ],\n        'description': # SeriesDescription\n    },\n    ...\n}\n\"\"\"\nim_list_dcm = {}\nfor idx, i in enumerate(ptobj['SeriesInstanceUIDs']):\n    im_list_dcm[i] = {'images': [], 'description': ptobj['SeriesDescriptions'][idx]}\n    images = glob.glob(f\"{ptobj['folder_path']}/{ptobj['SeriesInstanceUIDs'][idx]}/*.dcm\")\n    for j in sorted(images, key=lambda x: int(x.split('/')[-1].replace('.dcm', ''))):\n        im_list_dcm[i]['images'].append({\n            'SOPInstanceUID': j.split('/')[-1].replace('.dcm', ''), \n            'dicom': pydicom.dcmread(j) })","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.364995Z","iopub.execute_input":"2024-10-10T11:10:43.365427Z","iopub.status.idle":"2024-10-10T11:10:43.938010Z","shell.execute_reply.started":"2024-10-10T11:10:43.365400Z","shell.execute_reply":"2024-10-10T11:10:43.936694Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"La siguiente función muestra las imágenes que se le pasan como argumento. \nPrimero calcula la cantidad de imágenes que se le pasan, y así calcula el número de filas necesarias para el gáfico.\nLuego, crea el gráfico con 'subplots' para crear un gráfico para cada imagen y con el número de filas calcualdo anteriormente.\nFinalmente, dibuja cada una de las imágenes, en escala de grises y oculta los ejes. Por último, oculta los gráficos que no se han usado","metadata":{}},{"cell_type":"code","source":"# Function to display images\ndef display_images(images, title, max_images_per_row=4):\n    # Calculate the number of rows needed\n    num_images = len(images)\n    num_rows = (num_images + max_images_per_row - 1) // max_images_per_row  # Ceiling division\n\n    # Create a subplot grid\n    fig, axes = plt.subplots(num_rows, max_images_per_row, figsize=(5, 1.5 * num_rows))\n    \n    # Flatten axes array for easier looping if there are multiple rows\n    if num_rows > 1:\n        axes = axes.flatten()\n    else:\n        axes = [axes]  # Make it iterable for consistency\n\n    # Plot each image\n    for idx, image in enumerate(images):\n        ax = axes[idx]\n        ax.imshow(image, cmap='gray')  # Assuming grayscale for simplicity, change cmap as needed\n        ax.axis('off')  # Hide axes\n\n    # Turn off unused subplots\n    for idx in range(num_images, len(axes)):\n        axes[idx].axis('off')\n    fig.suptitle(title, fontsize=16)\n\n    plt.tight_layout()","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.939500Z","iopub.execute_input":"2024-10-10T11:10:43.939865Z","iopub.status.idle":"2024-10-10T11:10:43.955775Z","shell.execute_reply.started":"2024-10-10T11:10:43.939835Z","shell.execute_reply":"2024-10-10T11:10:43.954419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Recorre el diccionario 'im_list_dcm' y para cada elemento, llama a la función creada en la celda anterior, para dibujar las imágenes junto a la descripción","metadata":{}},{"cell_type":"code","source":"for i in im_list_dcm:\n    display_images([x['dicom'].pixel_array for x in im_list_dcm[i]['images']], \n                   im_list_dcm[i]['description'])","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:43.957019Z","iopub.execute_input":"2024-10-10T11:10:43.957723Z","iopub.status.idle":"2024-10-10T11:10:50.088420Z","shell.execute_reply.started":"2024-10-10T11:10:43.957678Z","shell.execute_reply":"2024-10-10T11:10:50.087024Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Look at the coordinates of pathologies\n\nWe can also display the coordinates of the pathologies that are annotated for each of the patients.","metadata":{}},{"cell_type":"markdown","source":"Lee el archivo 'train_label_coordinates' para mostrar las coordenadas de las patologías de cada paciente","metadata":{}},{"cell_type":"code","source":"df_coor = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_label_coordinates.csv')","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:50.089880Z","iopub.execute_input":"2024-10-10T11:10:50.090329Z","iopub.status.idle":"2024-10-10T11:10:50.222038Z","shell.execute_reply.started":"2024-10-10T11:10:50.090294Z","shell.execute_reply":"2024-10-10T11:10:50.220817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Muestra los primeros registros ","metadata":{}},{"cell_type":"code","source":"df_coor.head()","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:50.223306Z","iopub.execute_input":"2024-10-10T11:10:50.223603Z","iopub.status.idle":"2024-10-10T11:10:50.242671Z","shell.execute_reply.started":"2024-10-10T11:10:50.223578Z","shell.execute_reply":"2024-10-10T11:10:50.241275Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Crea una función para mostrar las coordenadas en las imágenes.\n\nPrimero crea variables como el centro de las coordenadas, el radio, el color y el grosor del círculo.\n\nLuego se extrae el array de píxeles de la imagen i['dicom'] y se normaliza con 'cv2.normalize'. Esto transforma los valores de píxeles a un rango de 0 a 255 para que todos tengan la misma escala.\n\nCon 'cv2.circle' se crea un círculo con la imagen normalizada, el centro de coordenadas, el radio, el color y el grosor.\n\nCon 'cv2.cvtColor' se convierte la imagen de BGR a RGB.\n\nFinalmente, se muestra la imagen","metadata":{}},{"cell_type":"code","source":"def display_coor_on_img(c, i, title):\n    center_coordinates = (int(c['x']), int(c['y']))\n    radius = 10\n    color = (255, 0, 0)  # Red color in BGR\n    thickness = 2\n    IMG = i['dicom'].pixel_array\n    IMG_normalized = cv2.normalize(IMG, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)\n    \n    IMG_with_circle = cv2.circle(IMG_normalized.copy(), center_coordinates, radius, color, thickness)\n    \n    # Convert the image from BGR to RGB for correct color display in matplotlib\n    IMG_with_circle = cv2.cvtColor(IMG_with_circle, cv2.COLOR_BGR2RGB)\n    \n    # Display the image\n    plt.imshow(IMG_with_circle)\n    plt.axis('off')  # Turn off axis numbers and ticks\n    plt.title(title)\n    plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:50.243882Z","iopub.execute_input":"2024-10-10T11:10:50.244227Z","iopub.status.idle":"2024-10-10T11:10:50.251327Z","shell.execute_reply.started":"2024-10-10T11:10:50.244199Z","shell.execute_reply":"2024-10-10T11:10:50.250220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Filtra las coordenadas para el 'study_id' del paciente actual","metadata":{}},{"cell_type":"code","source":"coor_entries = df_coor[df_coor['study_id'] == int(patient['study_id'])]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:50.252572Z","iopub.execute_input":"2024-10-10T11:10:50.252916Z","iopub.status.idle":"2024-10-10T11:10:50.264239Z","shell.execute_reply.started":"2024-10-10T11:10:50.252885Z","shell.execute_reply":"2024-10-10T11:10:50.263014Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Hace un bucle para iterar sobre las coordenadas anteriores y para recorrer cada una de las imágenes dela serie. De esta forma, si el paciente tiene una condición de gravedad, dibuja la imagen con las coordenadas de de la zona afectada","metadata":{}},{"cell_type":"code","source":"print(\"Only showing severe cases for this patient\")\nfor idc, c in coor_entries.iterrows():\n    for i in im_list_dcm[str(c['series_id'])]['images']:\n        if int(i['SOPInstanceUID']) == int(c['instance_number']):\n            try:\n                patient_severity = patient[\n                    f\"{c['condition'].lower().replace(' ', '_')}_{c['level'].lower().replace('/', '_')}\"\n                ]\n            except Exception as e:\n                patient_severity = \"unknown severity\"\n            title = f\"{i['SOPInstanceUID']} \\n{c['level']}, {c['condition']}: {patient_severity} \\n{c['x']}, {c['y']}\"\n            if patient_severity == 'Severe':\n                display_coor_on_img(c, i, title)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T11:10:50.265461Z","iopub.execute_input":"2024-10-10T11:10:50.265948Z","iopub.status.idle":"2024-10-10T11:10:50.748921Z","shell.execute_reply.started":"2024-10-10T11:10:50.265920Z","shell.execute_reply":"2024-10-10T11:10:50.747733Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Wrap up","metadata":{}},{"cell_type":"markdown","source":"In this notebook we covered the basic distribution of cases in our popuation, how to look at which scans and diagnoses correspond to each patient, and how to visualize the patient DICOMs + locations of annotated pathologies. Have fun with the challenge!","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}