{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Advanced Exploratory Data Analysis\n## 🧠 RSNA-MICCAI Brain Tumor Radiogenomic Classification 🧠\n\n\n**Advanced Exploratory Data Analysis** and **Data Cleaning** for [RSNA-MICCAI Brain Tumor Radiogenomic Classification](https://www.kaggle.com/c/rsna-miccai-brain-tumor-radiogenomic-classification) challenge with **useful insights**, **visualizations** and **extra resources** on MRI scans.\n\nNotebook based on the following fantastic notebooks: \n1. [\n🧠Brain Tumor🧠 - EDA with Animations and Modeling](https://www.kaggle.com/ihelon/brain-tumor-eda-with-animations-and-modeling)\n2. [Converting DICOM Metadata to CSV](https://www.kaggle.com/carlolepelaars/converting-dicom-metadata-to-csv-rsna-ihd-2019)\n3. [DICOM Metadata EDA](https://www.kaggle.com/anarthal/dicom-metadata-eda)\n4. [Pulmonary Dicom Preprocessing](https://www.kaggle.com/allunia/pulmonary-dicom-preprocessing#Prepare-to-start-) and \n5. [Insightful EDA on Meta Data & Dicom Files](https://www.kaggle.com/jagdmir/insightful-eda-on-meta-data-dicom-files).\n6. [BTRC EDA (Final)](https://www.kaggle.com/josecarmona/btrc-eda-final)\n7. [(Part-1) RSNA-MICCAI BTRC: Understanding The Data](https://www.kaggle.com/arnabs007/part-1-rsna-miccai-btrc-understanding-the-data)\n8. [🧠 DICOM to 2D Resized Axial PNGs 256x256 [x36] 🧠](https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36)","metadata":{}},{"cell_type":"markdown","source":"![](https://storage.googleapis.com/kaggle-competitions/kaggle/29653/logos/header.png)","metadata":{}},{"cell_type":"markdown","source":"<a id=\"top\"></a>\n\n<div class=\"list-group\" id=\"list-tab\" role=\"tablist\">\n<h3 class=\"list-group-item list-group-item-action active\" data-toggle=\"list\" style='color:white; background:darkviolet; border:0' role=\"tab\" aria-controls=\"home\"><center>Quick Navigation</center></h3>\n\n* [1. Overview](#1)\n* [2. Quick EDA and Data Visualization](#2)\n* [3. EDA DICOM Images](#5)\n* [4. EDA DICOM Metadata](#10)\n* [5. EDA Pixel Data](#15)\n* [6. 3D Visualizations](#20)\n* [7. Conclusions](#30)\n    ","metadata":{}},{"cell_type":"markdown","source":"<a id=\"1\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>1. Overview<center><h2>","metadata":{}},{"cell_type":"markdown","source":"The work uses some ideas from next great works:\n- [EDA with Animation](https://www.kaggle.com/avloss/eda-with-animation) - animation technique","metadata":{}},{"cell_type":"code","source":"import os\nimport ast\nimport json\nimport glob\nimport random\nimport collections\nfrom tqdm import tqdm\nimport gc\n\n# Visualization\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\nimport numpy as np\nimport pandas as pd\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Seed for reproducability\nseed = 1234\nnp.random.seed(seed)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:25.710921Z","iopub.execute_input":"2021-08-26T11:44:25.711327Z","iopub.status.idle":"2021-08-26T11:44:27.102668Z","shell.execute_reply.started":"2021-08-26T11:44:25.711292Z","shell.execute_reply":"2021-08-26T11:44:27.101501Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Files\n**train/** - folder containing the training files, with each top-level folder representing a subject  \n**train_labels.csv** - file containing the target MGMT_value for each subject in the training data (e.g. the presence of MGMT promoter methylation)   \n**test/** - the test files, which use the same structure as train/; your task is to predict the MGMT_value for each subject in the test data. NOTE: the total size of the rerun test set (Public and Private) is ~5x the size of the Public test set   \n**sample_submission.csv** - a sample submission file in the correct format","metadata":{}},{"cell_type":"markdown","source":"The exact mpMRI scans included are:\n\n- Fluid Attenuated Inversion Recovery (FLAIR)\n- T1-weighted pre-contrast (T1w)\n- T1-weighted post-contrast (T1Gd)\n- T2-weighted (T2)","metadata":{}},{"cell_type":"markdown","source":"Exact folder structure:\n\n\n```\nTraining/Validation/Testing\n│\n└─── 00000\n│   │\n│   └─── FLAIR\n│   │   │ Image-1.dcm\n│   │   │ Image-2.dcm\n│   │   │ ...\n│   │   \n│   └─── T1w\n│   │   │ Image-1.dcm\n│   │   │ Image-2.dcm\n│   │   │ ...\n│   │   \n│   └─── T1wCE\n│   │   │ Image-1.dcm\n│   │   │ Image-2.dcm\n│   │   │ ...\n│   │   \n│   └─── T2w\n│   │   │ Image-1.dcm\n│   │   │ Image-2.dcm\n│   │   │ .....\n│   \n└─── 00001\n│   │ ...\n│   \n│ ...   \n│   \n└─── 00002\n│   │ ...\n```","metadata":{}},{"cell_type":"markdown","source":"DICOM® — [Digital Imaging and Communications in Medicine](https://www.dicomstandard.org/about-home) — is the international standard for medical images and related information. It defines the formats for medical images that can be exchanged with the data and quality necessary for clinical use. With hundreds of thousands of medical imaging devices in use, DICOM® is one of the most widely deployed healthcare messaging Standards in the world.","metadata":{}},{"cell_type":"markdown","source":"## MRI Scan Basics\n\n**1. [MRI NHS Explanation](https://www.nhs.uk/conditions/mri-scan/)**\n\nShort bursts of radio waves are then sent to certain areas of the body, knocking the protons out of alignment.\n- When the radio waves are turned off, the protons realign. This sends out radio signals, which are picked up by receivers.\n- **These signals provide information about the exact location of the protons in the body.**\n- **They also help to distinguish between the various types of tissue in the body, because the protons in different types of tissue realign at different speeds and produce distinct signals.**\n- **In the same way that millions of pixels on a computer screen can create complex pictures, the signals from the millions of protons in the body are combined to create a detailed image of the inside of the body.**\n- **Fourier Transform is used to get the final image from k-space.**\n\n**2. [Wikipedia](https://en.wikipedia.org/wiki/Magnetic_resonance_imaging)**\n\n **Definitions**\n- **T1-weighted**: \nThe dominant signal intensities of different tissues are:\n    - Lower signal for more water content, as in edema, tumor, infarction, inflammation, infection, hyperacute or chronic hemorrhage.\n    - High signal for fat\n    - High signal for paramagnetic substances, such as MRI contrast agents\n_Standard foundation and comparison for other sequences_\n- **Constrast**:\n    - T1 - signal increase. Pathological tissues (tumours, areas of inflammation / infection) will demonstrate accumulation of contrast (mostly due to leaky blood vessels) and therefore appear as brighter than surrounding tissue. \n    - Often post contrast T1 sequences are also fat suppressed (see below) to make this easier to appreciate. Fat suppression (or attenuation or saturation) is a tweak performed on many T1 weighted sequences, to suppress the bright signal from fat.\n- **T2-weighted**: \n    - Higher signal for more water content\n    - Low signal for fat\n    - Low signal for paramagnetic substances\n_Standard foundation and comparison for other sequences_\n- **FLAIR**:\n    - Fluid suppression by setting an inversion time that nulls fluids\n\n**3. For Intuitive MRI-scan examples see: [The Basics of MRI Interpretation](https://geekymedics.com/the-basics-of-mri-interpretation/)**\n\n**4. For a good explanation of T1w, T1wCE, T2w, FLAIR see: [Radiopaedia: MRI sequences](https://radiopaedia.org/articles/mri-sequences-overview?lang=gb)**\n\n**5. For a detailed list of DICOM metadata attributes and explanations see: [DICOM Attributes](https://dicom.innolitics.com/ciods/mr-image/mr-image/00181100).**","metadata":{}},{"cell_type":"markdown","source":"# Main Competition Workflow","metadata":{}},{"cell_type":"markdown","source":"![RadiogenomicsCompetitionFlowchart.png](attachment:286369f1-2662-4008-a1f8-4b106096757b.png)","metadata":{},"attachments":{"286369f1-2662-4008-a1f8-4b106096757b.png":{"image/png":"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IhARGCwEIlkZLGRHULwnnHCCnXLKKXbxxReHVJ966qnhl5lEuC9+8YthiOiCCy4I1/6HGUI4yM4HPvCBYKjrz+JvRCAiEBGICEQE0oVAJCvpQjJD43HtR2rysrKyui6xPWFWzx/+8IcwxRnbE+xbcF/5ylesrq4uGN1eccUV9tGPfjTc9ziZIVRbWxtsYG666SbrPnwUPMc/EYGIQEQgIhAR2E4E4tTl7QRwpAb3qcuTJ0+2pUuXGlOXsWlhDZXujjVbsGvJzX39sjxMk162bJmxVgvTl6OLCEQEIgIRgYhAuhF4vfRJ9xtifCMCARaM681NmjSpt0dhgTm0K9FFBCICEYGIQERgsBCIZGWwkM3weNGgsEAci79FFxGICEQEIgIRgUxGIA4DZXLpxLRFBCICEYGIQEQgImDRwDZWgohARCAiEBGICEQEMhqBSFYyunhi4iICEYGIQEQgIhARiGQl1oGIQEQgIhARiAhEBDIagUhWMrp4YuIiAhGBiEBEICIQEci42UANDQ32yiuvGMvAx71oYgXdURFgYb3CwkIbNWqUMW3cF9rbUfMb8xURiAhEBLYHgWEnK62trTZv3jybO3euPfnkk4GksFAZG+zFBnx7ijaGzWQEqNubN2+2DRs22Lp162y33Xazgw46yGbPnh0W2MvktMe0RQQiAhGBoUZg2KYuozn57W9/azfffHNoqI8++mjbf//9raamZqgxiO+LCAwrAmgQn3rqqbDH0pw5c8L38KEPfch8F+xhTVx8eUQgIhARyAAEhoWs3H///fajH/3IDj74YDv99NODGjwDsIhJiAgMOwKJRML+/ve/24033mgHHHCAnX/++WEbhGFPWExARCAiEBEYRgSGlKx0dnbaZZddZo8//rh96Utfsj333HMYsx5fHRHIXASam5vtuuuus3/961/27W9/2xgajS4iEBGICOysCAypzcpXv/pVo+f485//PBgX7qygx3xHBLaFAMa35513XthAcsqUKbZq1aq4NcK2QIvPIwIRgR0WgSGbuvyNb3zDXnrpJfvmN78ZicoOW51ixtKNwLvf/W67++677aMf/agtWbIk3dHH+CICEYGIwIhAYEg0KxjSolFBrR1n+IyIehETmUEIMEOI7wfN5NVXXx3JfgaVTUxKRCAiMDQIDLpmZeHChfaHP/zBPvWpT0WiMjRlGt+y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hwACCCCAgBGsBPRDkErzU2IlbNu2LfNYYsXiOAQQQACBTAGClUyK4LxIxfkpseoxjyVWKY5DAAEEEPAECFY8iYD8TuX5KbESMo8lVimOQwABBBCQAMFKQD4Hec1P0fvvv/++zZgxw/78809X6iVLlpiGi/KbPvzwQ3fqN998Y2PHjrUvv/zStm3bFnN2Kscff/xhOj/exDyWeMU4HgEEEAivAMFKANo+r/kpq1evttNOO80tYz9z5kyrV6+erVu3ztQL869//SvfNVCAsnv3bmvevLlt2LDBpkyZYps2bYopPy1M99FHH9mqVasKVIbIeSwTJ06M6dochAACCCAQLgEeZFjI7a35Kffdd59dc8011rp162xLowcE6n3d+qukhxbqPC9t3LjRhg0bZv/5z39Mz+i544477IMPPrBRo0ZZ5cqV7c4773Q9JoMHD3bBie7IOemkk+yQQw6xxx57zO3TM32qVKlixYoVs/nz57vn/Gj75ptvdvsi87/yyitNgYWOGzp0qFucbufOnfbiiy+6cl122WXWpEkTGzFihB133HEuqLrwwgvtggsu8Iqc5bc3j0WLyH3//ffWq1cvK1GiRJZj/NhQGWfNmpUlK9X77LPPzrIvekM9UH/961+jd7tepRUrVtjxxx+/z3vsQAABBBDwT4CeFf8s48pJX5yxzk/57LPPrGXLlpn5d+7cOcv2K6+8YkWLFjUFI3o2z7x58+z666+3W265xY455hibOnWqe09fqldddZWNHj3a5TVmzBi76KKLrGrVqu73pEmTXI/N+eefb+3atXNBiAKO6PyXL1/ugiIdo3VT3nnnHVcXDVPpurfffrsLOpSfVq9VcHTddddllj+7F948lj179rigTMNLficNpSlYmTZtmimg0mtvKCy3a6kHKrtU0F6l7PJkHwIIIIDAvgL0rOxrkpQ9CiA0pKPn++S1bL6W14/88tYQkDdvRYVt1aqVDRkyxLp27ermsGzevNnU+/G3v/3NzjzzTOvbt68dfvjhrnfm6KOPthtuuCGzjgceeKDrxdBvJQUiRx55pOsZUe+I0sqVK7Pkry/9cuXKuZ4Yd0DGP6+++qrdfffdpl6S9u3bmwIVJZVJw1bly5c3BSIKqnJKmsfSr18/l5eGvRYtWpTTofnarzI8+OCDzlKr6uq10uOPP26///67yblFixaut+nX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"}}},{"cell_type":"markdown","source":"<a id=\"2\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>2. Quick EDA and Data Visualization<center><h2>","metadata":{"execution":{"iopub.status.busy":"2021-07-14T06:41:32.077425Z","iopub.execute_input":"2021-07-14T06:41:32.077767Z","iopub.status.idle":"2021-07-14T06:41:32.0845Z","shell.execute_reply.started":"2021-07-14T06:41:32.077737Z","shell.execute_reply":"2021-07-14T06:41:32.082683Z"}}},{"cell_type":"code","source":"# Paths \nKAGGLE_DIR = '../input/rsna-miccai-brain-tumor-radiogenomic-classification/'\nIMG_PATH_TRAIN = KAGGLE_DIR + 'train/'\nIMG_PATH_TEST = KAGGLE_DIR + 'test/'\nTRAIN_CSV_PATH = KAGGLE_DIR + 'train_labels.csv'\nTEST_CSV_PATH = KAGGLE_DIR + 'sample_submission.csv'","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.106599Z","iopub.execute_input":"2021-08-26T11:44:27.107001Z","iopub.status.idle":"2021-08-26T11:44:27.112557Z","shell.execute_reply.started":"2021-08-26T11:44:27.106935Z","shell.execute_reply":"2021-08-26T11:44:27.111730Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### **train\\** Dataset","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(TRAIN_CSV_PATH)\ndisplay(train_df.head(5))\nprint('MGMT counts:')\ntrain_df.MGMT_value.value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.114014Z","iopub.execute_input":"2021-08-26T11:44:27.114436Z","iopub.status.idle":"2021-08-26T11:44:27.161047Z","shell.execute_reply.started":"2021-08-26T11:44:27.114395Z","shell.execute_reply":"2021-08-26T11:44:27.159800Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(5, 5))\nplt.title('Train csv')\nsns.countplot(data=train_df, x=\"MGMT_value\");","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.163320Z","iopub.execute_input":"2021-08-26T11:44:27.163772Z","iopub.status.idle":"2021-08-26T11:44:27.352145Z","shell.execute_reply.started":"2021-08-26T11:44:27.163726Z","shell.execute_reply":"2021-08-26T11:44:27.350816Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### **test\\** Dataset","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(TEST_CSV_PATH)\ndisplay(test_df.head(5))\nprint('MGMT counts:')\ntest_df.MGMT_value.value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.353782Z","iopub.execute_input":"2021-08-26T11:44:27.354248Z","iopub.status.idle":"2021-08-26T11:44:27.381187Z","shell.execute_reply.started":"2021-08-26T11:44:27.354191Z","shell.execute_reply":"2021-08-26T11:44:27.380197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(5, 5))\nplt.title('Test csv')\nsns.countplot(data=test_df, x=\"MGMT_value\");","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.382380Z","iopub.execute_input":"2021-08-26T11:44:27.382667Z","iopub.status.idle":"2021-08-26T11:44:27.516613Z","shell.execute_reply.started":"2021-08-26T11:44:27.382639Z","shell.execute_reply":"2021-08-26T11:44:27.515778Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# All filenames for train and test images\ntrain_images = os.listdir(IMG_PATH_TRAIN)\ntest_images = os.listdir(IMG_PATH_TEST)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.517933Z","iopub.execute_input":"2021-08-26T11:44:27.518587Z","iopub.status.idle":"2021-08-26T11:44:27.570215Z","shell.execute_reply.started":"2021-08-26T11:44:27.518540Z","shell.execute_reply":"2021-08-26T11:44:27.569341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### For more details in pixel arrays see : [Working with Pixel Data](https://pydicom.github.io/pydicom/stable/old/working_with_pixel_data.html)","metadata":{}},{"cell_type":"code","source":"def load_dicom(path):\n    # read file\n    dicom = pydicom.read_file(path)\n    # get pixel data into a useful format. \n    data = dicom.pixel_array\n    # transform data into black and white scale / grayscale\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    return data\n\n\ndef visualize_sample(\n    brats21id, \n    slice_i,\n    mgmt_value,\n    types=(\"FLAIR\", \"T1w\", \"T1wCE\", \"T2w\")\n):\n    plt.figure(figsize=(16, 5))\n    patient_path = os.path.join(\n        IMG_PATH_TRAIN, \n        str(brats21id).zfill(5),\n    )\n    for i, t in enumerate(types, 1):\n        t_paths = sorted(\n            glob.glob(os.path.join(patient_path, t, \"*\")), \n            key=lambda x: int(x[:-4].split(\"-\")[-1]),\n        )\n        data = load_dicom(t_paths[int(len(t_paths) * slice_i)])\n        plt.subplot(1, 4, i)\n        plt.imshow(data, cmap=\"gray\")\n        plt.title(f\"{t}\", fontsize=16)\n        plt.axis(\"off\")\n\n    plt.suptitle(f\"MGMT_value: {mgmt_value}\", fontsize=16)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.571652Z","iopub.execute_input":"2021-08-26T11:44:27.572306Z","iopub.status.idle":"2021-08-26T11:44:27.585136Z","shell.execute_reply.started":"2021-08-26T11:44:27.572259Z","shell.execute_reply":"2021-08-26T11:44:27.583736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Visualize 10 random observations and show the 4 datasets (FLAIR, T1w, T1wCE, T2w) for each.\n\n* Each dataset is a 3D scan / dataset and thus for simplicity visualize a specific slice for each dataset chosen by the variable $slice_i$","metadata":{}},{"cell_type":"code","source":"for i in random.sample(range(train_df.shape[0]), 10): # get 10 random indexes from the train ds\n    _brats21id = train_df.iloc[i][\"BraTS21ID\"] # for these indexes get the associated brats ID\n    _mgmt_value = train_df.iloc[i][\"MGMT_value\"] # and tumor class\n    visualize_sample(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.5) # visualize samples","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:27.588765Z","iopub.execute_input":"2021-08-26T11:44:27.589166Z","iopub.status.idle":"2021-08-26T11:44:32.530945Z","shell.execute_reply.started":"2021-08-26T11:44:27.589130Z","shell.execute_reply":"2021-08-26T11:44:32.529636Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from matplotlib import animation, rc\nrc('animation', html='jshtml')\n\n\ndef create_animation(ims):\n    fig = plt.figure(figsize=(6, 6))\n    plt.axis('off')\n    im = plt.imshow(ims[0], cmap=\"gray\")\n\n    def animate_func(i):\n        im.set_array(ims[i])\n        return [im]\n\n    return animation.FuncAnimation(fig, animate_func, frames = len(ims), interval = 1000//24)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:32.533424Z","iopub.execute_input":"2021-08-26T11:44:32.533779Z","iopub.status.idle":"2021-08-26T11:44:32.540411Z","shell.execute_reply.started":"2021-08-26T11:44:32.533746Z","shell.execute_reply":"2021-08-26T11:44:32.539317Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_dicom_line(path):\n    t_paths = sorted(\n        glob.glob(os.path.join(path, \"*\")), \n        key=lambda x: int(x[:-4].split(\"-\")[-1]),\n    )\n    images = []\n    for filename in t_paths:\n        data = load_dicom(filename)\n        if data.max() == 0:\n            continue\n        images.append(data)\n        \n    return images","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:32.541923Z","iopub.execute_input":"2021-08-26T11:44:32.542257Z","iopub.status.idle":"2021-08-26T11:44:32.554527Z","shell.execute_reply.started":"2021-08-26T11:44:32.542227Z","shell.execute_reply":"2021-08-26T11:44:32.553123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"5\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>3. EDA DICOM Images<center><h2>","metadata":{}},{"cell_type":"code","source":"IMG_PATH_TRAIN = \"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/\"\nIMG_PATH_TEST = \"../input/rsna-miccai-brain-tumor-radiogenomic-classification/test/\"","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:32.556072Z","iopub.execute_input":"2021-08-26T11:44:32.556467Z","iopub.status.idle":"2021-08-26T11:44:32.569443Z","shell.execute_reply.started":"2021-08-26T11:44:32.556434Z","shell.execute_reply":"2021-08-26T11:44:32.568419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# review training directory\ns_sizes = [] # list of no. of scans present for each patient\np_sizes = [] # list of no. of dcm files present for each patient\npatient_id = [] # patient id\nfile_paths = [] # file_paths\n\nfor d in os.listdir(IMG_PATH_TRAIN):\n#     print(\"Patient '{}' has {} scans and a total of {} DICOM images\".format(d, len(os.listdir(TRAIN_DIR + d)), len(glob.glob(TRAIN_DIR+ d + \"/*/*.dcm\"))))\n    s_sizes.append(len(os.listdir(IMG_PATH_TRAIN + d)))\n    p_sizes.append(len(glob.glob(IMG_PATH_TRAIN + d + \"/*/*.dcm\")))\n    patient_id.append(d)\n\npatient_files_df = pd.DataFrame(\n    {'patient_id': patient_id,\n     'file_count': p_sizes,\n    })\n    \nprint('----')\nprint('Total patients {} Total DCM files {}'.format(len(os.listdir(IMG_PATH_TRAIN)), \n                                                      len(glob.glob(IMG_PATH_TRAIN+ \"/*/*/*.dcm\"))))\n\nprint('----')\nprint('TRAIN Dataframe with File Count per Patient ')\ndisplay(patient_files_df.head(5))\n\nprint('----')\nprint('Verify total File Count for all Patients ')\nprint('Total number of patients:', patient_files_df.shape[0])\n\nprint('Total file count:', patient_files_df.file_count.sum())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:44:32.570768Z","iopub.execute_input":"2021-08-26T11:44:32.571121Z","iopub.status.idle":"2021-08-26T11:45:33.488391Z","shell.execute_reply.started":"2021-08-26T11:44:32.571073Z","shell.execute_reply":"2021-08-26T11:45:33.487231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"f = []\nfor (dirpath, dirnames, filenames) in os.walk(IMG_PATH_TRAIN):\n    f.extend(os.path.join(dirpath, x) for x in filenames)\n    \ntrain_file_paths_df = pd.DataFrame({'file_paths': f})\ntrain_file_paths_df['train_dir'] = IMG_PATH_TRAIN\ntrain_file_paths_df['patient_id'] = train_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[4]\ntrain_file_paths_df['scan_type'] = train_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[5]\ntrain_file_paths_df['file'] = train_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[6]\ndisplay(train_file_paths_df.head(2))\ntrain_file_paths_df.shape[0]","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:33.489650Z","iopub.execute_input":"2021-08-26T11:45:33.489926Z","iopub.status.idle":"2021-08-26T11:45:41.866709Z","shell.execute_reply.started":"2021-08-26T11:45:33.489898Z","shell.execute_reply":"2021-08-26T11:45:41.865639Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**train/ dataset:** \n\nWe have a total of 585 directories, one for each patient, with 4 subdirectories corresponding to 4 scans per patient and total of 348641 dicom images","metadata":{}},{"cell_type":"code","source":"print('Possible Number of scans for all patients:', set(s_sizes))","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:41.868102Z","iopub.execute_input":"2021-08-26T11:45:41.868421Z","iopub.status.idle":"2021-08-26T11:45:41.874013Z","shell.execute_reply.started":"2021-08-26T11:45:41.868389Z","shell.execute_reply":"2021-08-26T11:45:41.873055Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So all patients in the train/ ds had all 4 types of scans: T1w, T1wCE, T2w, FLAIR.","metadata":{}},{"cell_type":"code","source":"# lets visualize trainig data\np = sns.color_palette()\nplt.hist(p_sizes, color=p[2])\nplt.ylabel('Number of patients')\nplt.xlabel('Count of DICOM files')\nplt.title('Histogram of DICOM count per patient - Training Data');","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:41.875321Z","iopub.execute_input":"2021-08-26T11:45:41.875623Z","iopub.status.idle":"2021-08-26T11:45:42.120983Z","shell.execute_reply.started":"2021-08-26T11:45:41.875593Z","shell.execute_reply":"2021-08-26T11:45:42.119787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# review test directory\ns_sizes = [] # list of no. of scans present for each patient\np_sizes = [] # list of no. of dcm files present for each patient\npatient_id = [] # patient id\n\nfor d in os.listdir(IMG_PATH_TEST):\n#     print(\"Patient '{}' has {} scans and a total of {} DICOM images\".format(d, \n#                     len(os.listdir(IMG_PATH_TEST + d)), len(glob.glob(IMG_PATH_TEST+ d + \"/*/*.dcm\"))))\n    s_sizes.append(len(os.listdir(IMG_PATH_TEST + d)))\n    p_sizes.append(len(glob.glob(IMG_PATH_TEST + d + \"/*/*.dcm\")))\n    patient_id.append(d)\n\npatient_files_df = pd.DataFrame(\n    {'patient_id': patient_id,\n     'file_count': p_sizes,\n    })\n    \nprint('----')\nprint('Total patients {} Total DCM files {}'.format(len(os.listdir(IMG_PATH_TEST)), \n                                                      len(glob.glob(IMG_PATH_TEST+ \"/*/*/*.dcm\"))))\nprint('----')\nprint('TRAIN Dataframe with File Count per Patient ')\ndisplay(patient_files_df.head(5))\n\nprint('----')\nprint('Verify total File Count for all Patients ')\nprint('Total number of patients:', patient_files_df.shape[0])\n\nprint('Total file count:', patient_files_df.file_count.sum())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:42.124146Z","iopub.execute_input":"2021-08-26T11:45:42.124651Z","iopub.status.idle":"2021-08-26T11:45:46.065369Z","shell.execute_reply.started":"2021-08-26T11:45:42.124612Z","shell.execute_reply":"2021-08-26T11:45:46.064258Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"f = []\nfor (dirpath, dirnames, filenames) in os.walk(IMG_PATH_TEST):\n    f.extend(os.path.join(dirpath, x) for x in filenames)\n    \ntest_file_paths_df = pd.DataFrame({'file_paths': f})\ntest_file_paths_df['train_dir'] = IMG_PATH_TEST\ntest_file_paths_df['patient_id'] = test_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[4]\ntest_file_paths_df['scan_type'] = test_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[5]\ntest_file_paths_df['file'] = test_file_paths_df['file_paths'].str.split(\"/\", n = 7, expand = True)[6]\ndisplay(test_file_paths_df.head(2))\ntest_file_paths_df.shape[0]","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:46.066660Z","iopub.execute_input":"2021-08-26T11:45:46.066947Z","iopub.status.idle":"2021-08-26T11:45:47.234335Z","shell.execute_reply.started":"2021-08-26T11:45:46.066917Z","shell.execute_reply":"2021-08-26T11:45:47.233563Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**test/ dataset:** \n\nWe have a total of 87 directories, one for each patient, with 4 subdirectories corresponding to 4 scans per patient and total of 51473 dicom images, i.e. **about 13% and 87% test / train split**. ","metadata":{}},{"cell_type":"code","source":"print('Possible Number of scans for all patients:', set(s_sizes))","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.236212Z","iopub.execute_input":"2021-08-26T11:45:47.236810Z","iopub.status.idle":"2021-08-26T11:45:47.241396Z","shell.execute_reply.started":"2021-08-26T11:45:47.236774Z","shell.execute_reply":"2021-08-26T11:45:47.240593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So all patients in the test/ ds had all 4 types of scans: T1w, T1wCE, T2w, FLAIR.","metadata":{}},{"cell_type":"code","source":"# lets visualize test data\np = sns.color_palette()\nplt.hist(p_sizes, color=p[2])\nplt.ylabel('Number of patients')\nplt.xlabel('Count of DICOM files')\nplt.title('Histogram of DICOM count per patient - Training Data');","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.242659Z","iopub.execute_input":"2021-08-26T11:45:47.243158Z","iopub.status.idle":"2021-08-26T11:45:47.458384Z","shell.execute_reply.started":"2021-08-26T11:45:47.243125Z","shell.execute_reply":"2021-08-26T11:45:47.457603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Let's check the size of the dicom images","metadata":{}},{"cell_type":"code","source":"# sizes = [os.path.getsize(dcm)/1000000 for dcm in glob.glob(TRAIN_DIR+ \"/*/*/*.dcm\")]\n# print('DCM file sizes: min {:.3}MB max {:.3}MB avg {:.3}MB std {:.3}MB'.format(np.min(sizes), \n#                                                        np.max(sizes), np.mean(sizes), np.std(sizes)))","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.459415Z","iopub.execute_input":"2021-08-26T11:45:47.459810Z","iopub.status.idle":"2021-08-26T11:45:47.463437Z","shell.execute_reply.started":"2021-08-26T11:45:47.459782Z","shell.execute_reply":"2021-08-26T11:45:47.462280Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The DICOM files in the **\\train** dataset have sizes between 0.1MB and 2.1MB (average 0.34MB).\n\nResult: DCM file sizes: min 0.0991MB max 2.08MB avg 0.341MB std 0.202MB","metadata":{}},{"cell_type":"markdown","source":"# Peek at one example dicom-file ","metadata":{}},{"cell_type":"markdown","source":"The [pydicom](https://pydicom.github.io/pydicom/stable/getting_started.html) library allows us to conveniently read in DICOM files and access different values from the file. The actual image can be found in \"pixel_array\".","metadata":{}},{"cell_type":"code","source":"print('Example of all data in a single DICOM file:\\n')\nexample_dicom = pydicom.dcmread(train_file_paths_df['file_paths'][0])\nprint(example_dicom)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.464522Z","iopub.execute_input":"2021-08-26T11:45:47.464801Z","iopub.status.idle":"2021-08-26T11:45:47.489789Z","shell.execute_reply.started":"2021-08-26T11:45:47.464774Z","shell.execute_reply":"2021-08-26T11:45:47.488736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"10\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>4. EDA DICOM Metadata<center><h2>","metadata":{}},{"cell_type":"code","source":"# All columns for which we want to collect information\nmeta_cols = ['SpecificCharacterSet','ImageType','SOPClassUID',\n             'SOPInstanceUID','AccessionNumber','Modality', 'SeriesDescription', \n             'PatientID', 'MRAcquisitionType', 'SliceThickness', \n             'EchoTime', 'NumberOfAverages', 'ImagingFrequency', 'ImagedNucleus', \n             'MagneticFieldStrength', 'SpacingBetweenSlices', \n             'EchoTrainLength', 'PercentSampling', 'PercentPhaseFieldOfView',\n             'PixelBandwidth', 'TriggerWindow', 'ReconstructionDiameter', 'AcquisitionMatrix',\n             'FlipAngle', 'SAR', 'PatientPosition',\n             'StudyInstanceUID', 'SeriesInstanceUID', 'SeriesNumber', 'InstanceNumber',\n             'ImagePositionPatient', 'ImageOrientationPatient', 'Laterality',\n             'PositionReferenceIndicator', 'SliceLocation', 'InStackPositionNumber',\n             'SamplesPerPixel', 'PhotometricInterpretation', 'Rows', 'Columns', 'PixelSpacing',\n             'BitsAllocated', 'BitsStored', 'HighBit', 'PixelRepresentation', 'WindowCenter',\n             'WindowWidth', 'RescaleIntercept', 'RescaleSlope', 'RescaleType']","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.490892Z","iopub.execute_input":"2021-08-26T11:45:47.491233Z","iopub.status.idle":"2021-08-26T11:45:47.497425Z","shell.execute_reply.started":"2021-08-26T11:45:47.491200Z","shell.execute_reply":"2021-08-26T11:45:47.496428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize dictionaries to collect the metadata\ncol_dict_train = {col: [] for col in meta_cols}\ncol_dict_test = {col: [] for col in meta_cols}","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.498729Z","iopub.execute_input":"2021-08-26T11:45:47.499248Z","iopub.status.idle":"2021-08-26T11:45:47.510859Z","shell.execute_reply.started":"2021-08-26T11:45:47.499212Z","shell.execute_reply":"2021-08-26T11:45:47.509843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here we extract all features for the training and testing set.","metadata":{}},{"cell_type":"markdown","source":"❗❗❗ **If you want to create the collactive metadata dataframes for test and train datasets from the DICOM images within the directory tree, you can uncomment the following three code snipets / cells.** ❗❗❗\n\n🏎   To speed things up I skip this step here, as it takes O(hours) and I have already done this once and loaded the train and test metadata *.csv files as input.","metadata":{}},{"cell_type":"code","source":"'''Uncomment code snipet to obtain metadata for all train DICOM images'''\n# # Get values for training images\n# for img in tqdm(train_file_paths_df.file_paths): \n#     dicom_object = pydicom.dcmread(img)\n#     for col in meta_cols: \n# #         print(str(getattr(dicom_object, col)))\n#         try:\n#             col_dict_train[col].append(str(getattr(dicom_object, col)))\n#         except AttributeError:\n#             col_dict_train[col].append(\"NaN\")\n\n# # Store all information in a DataFrame\n# meta_df_train = pd.DataFrame(col_dict_train)\n# del col_dict_train\n# # Garbage Collector for memory cleaning\n# gc.collect()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.512637Z","iopub.execute_input":"2021-08-26T11:45:47.513096Z","iopub.status.idle":"2021-08-26T11:45:47.525547Z","shell.execute_reply.started":"2021-08-26T11:45:47.513047Z","shell.execute_reply":"2021-08-26T11:45:47.524450Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"'''Uncomment code snipet to obtain metadata for all test DICOM images'''\n# # Get values for training images\n# for img in tqdm(test_file_paths_df.file_paths): \n#     dicom_object = pydicom.dcmread(img)\n#     for col in meta_cols: \n# #         print(str(getattr(dicom_object, col)))\n#         try:\n#             col_dict_test[col].append(str(getattr(dicom_object, col)))\n#         except AttributeError:\n#             col_dict_test[col].append(\"NaN\")\n\n# # Store all information in a DataFrame\n# meta_df_test = pd.DataFrame(col_dict_test)\n# del col_dict_test\n# # Garbage Collector for memory cleaning\n# gc.collect()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.527069Z","iopub.execute_input":"2021-08-26T11:45:47.527397Z","iopub.status.idle":"2021-08-26T11:45:47.544269Z","shell.execute_reply.started":"2021-08-26T11:45:47.527363Z","shell.execute_reply":"2021-08-26T11:45:47.543159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" Save metadata dataframes to *.csv files so that we don't have to redo this long computation.","metadata":{}},{"cell_type":"code","source":"'''Uncomment code snipet to store metadata dataframes for test and train dfs'''\n# # Save to CSV\n# meta_df_train.to_csv('stage_0_train_with_metadata.csv', index=False)\n# meta_df_test.to_csv('stage_0_test_with_metadata.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.549932Z","iopub.execute_input":"2021-08-26T11:45:47.550357Z","iopub.status.idle":"2021-08-26T11:45:47.557207Z","shell.execute_reply.started":"2021-08-26T11:45:47.550321Z","shell.execute_reply":"2021-08-26T11:45:47.556120Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load Metadata for train/ and test/ and do EDA for insights","metadata":{}},{"cell_type":"code","source":"test_meta_df = pd.read_csv(\"../input/stage0-metadata-rsna/stage_0_test_with_metadata.csv\")\ntrain_meta_df = pd.read_csv(\"../input/stage0-metadata-rsna/stage_0_train_with_metadata.csv\")","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:47.560385Z","iopub.execute_input":"2021-08-26T11:45:47.560706Z","iopub.status.idle":"2021-08-26T11:45:54.301117Z","shell.execute_reply.started":"2021-08-26T11:45:47.560674Z","shell.execute_reply":"2021-08-26T11:45:54.299865Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Insights on Metadata","metadata":{}},{"cell_type":"markdown","source":"Calculate unique number of values per metadata attribute.","metadata":{}},{"cell_type":"markdown","source":"### train/ dataset","metadata":{}},{"cell_type":"code","source":"meta_attr = []\nnum_unique = []\n\nfor col in train_meta_df:\n#     print(\"* For attribute  '{}' , there are [ {} ] unique values.\".format(col,\n#                     len(train_meta_df[col].unique())))\n    meta_attr.append(col)\n    num_unique.append(len(train_meta_df[col].unique()))\n    \ntrain_meta_values_df = pd.DataFrame(\n    {'attribute': meta_attr,\n     'value_count': num_unique,\n     'nan_count': train_meta_df.isna().sum()\n    })\n\ntrain_meta_values_df = train_meta_values_df.sort_values(by=['value_count'], ascending=False).reset_index(drop=True)\ntrain_meta_values_df","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:54.302282Z","iopub.execute_input":"2021-08-26T11:45:54.302574Z","iopub.status.idle":"2021-08-26T11:45:56.045788Z","shell.execute_reply.started":"2021-08-26T11:45:54.302544Z","shell.execute_reply":"2021-08-26T11:45:56.044721Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are 50 attributes present in the DICOM images of the **train/  - dataset** and 585 unique patients.","metadata":{}},{"cell_type":"markdown","source":"### test/ dataset","metadata":{}},{"cell_type":"code","source":"meta_attr = []\nnum_unique = []\n\nfor col in test_meta_df:\n#     print(\"* For attribute  '{}' , there are [ {} ] unique values.\".format(col,\n#                     len(train_meta_df[col].unique())))\n    meta_attr.append(col)\n    num_unique.append(len(test_meta_df[col].unique()))\n    \ntest_meta_values_df = pd.DataFrame(\n    {'attribute': meta_attr,\n     'value_count': num_unique,\n     'nan_count': test_meta_df.isna().sum()\n    })\n\ntest_meta_values_df = test_meta_values_df.sort_values(by=['value_count'], ascending=False).reset_index(drop=True)\ntest_meta_values_df","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:56.046922Z","iopub.execute_input":"2021-08-26T11:45:56.047234Z","iopub.status.idle":"2021-08-26T11:45:56.315722Z","shell.execute_reply.started":"2021-08-26T11:45:56.047204Z","shell.execute_reply":"2021-08-26T11:45:56.314683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are 50 attributes present in the DICOM images of the **test/  - dataset** and 87 unique patients.","metadata":{"execution":{"iopub.status.busy":"2021-07-22T10:06:36.168395Z","iopub.execute_input":"2021-07-22T10:06:36.168792Z","iopub.status.idle":"2021-07-22T10:06:36.176009Z","shell.execute_reply.started":"2021-07-22T10:06:36.168739Z","shell.execute_reply":"2021-07-22T10:06:36.174086Z"}}},{"cell_type":"markdown","source":"### train/ - dataset","metadata":{}},{"cell_type":"code","source":"def color_code_by_vcount(df):\n    if df['value_count'] == 1.0:\n        return 'k' # Single unique value, color-code black\n    elif df['value_count'] <= 1000.0:\n        return 'b' # Unique value count between one and 1000, color-code blue\n    else:\n        return 'r' # Unique value count more than > 1000, color-code red\n\ntrain_mv_df = train_meta_values_df.copy().set_index(\"attribute\")\ntrain_mv_df['color'] = train_mv_df.apply(color_code_by_vcount, axis=1)\n\nax = train_mv_df['value_count'].plot(kind='bar',\n                                    figsize=(14,8),  color=train_mv_df['color'],\n                                    title=\"Number of Unique Values per Attribute [LOG SCALE]\")\nax.set_xlabel(\"Metadata Attribute\")\nax.set_ylabel(\"Unique Number of Values [LOG SCALE]\")\nax.set_yscale('log');","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:56.316887Z","iopub.execute_input":"2021-08-26T11:45:56.317191Z","iopub.status.idle":"2021-08-26T11:45:57.960024Z","shell.execute_reply.started":"2021-08-26T11:45:56.317156Z","shell.execute_reply":"2021-08-26T11:45:57.959009Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We are **probably** interested in the **blue-range** attributes mainly since, highly-unique and single value attributes, will not help learn new information that will help our model discriminate between different cases during training.","metadata":{}},{"cell_type":"markdown","source":"### 4.1 NAN count","metadata":{}},{"cell_type":"code","source":"f, axes = plt.subplots(1, 2, figsize=(15,15))\n\nnans = train_meta_df.isna().sum().sort_values(ascending=False)\nsns.barplot(y=nans.index, x=nans, orient='h', ax = axes[0])\naxes[0].set_title(\"Train NaN Count\")\n\nnans_test = test_meta_df.isna().sum().sort_values(ascending=False)\nsns.barplot(y=nans_test.index, x=nans_test, orient='h', ax = axes[1])\naxes[1].yaxis.set_ticks_position(\"right\")\naxes[1].set_title(\"Test NaN Count\");","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:45:57.961295Z","iopub.execute_input":"2021-08-26T11:45:57.961601Z","iopub.status.idle":"2021-08-26T11:46:00.855343Z","shell.execute_reply.started":"2021-08-26T11:45:57.961569Z","shell.execute_reply":"2021-08-26T11:46:00.854136Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.2 Let's try merging train/ and test/ to gain some more insights","metadata":{}},{"cell_type":"code","source":"merged_meta_attrs = pd.merge(train_meta_values_df, test_meta_values_df, on=\"attribute\", \n                             suffixes=(\"_train\",\"_test\"))\nmerged_meta_attrs","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:00.857055Z","iopub.execute_input":"2021-08-26T11:46:00.857510Z","iopub.status.idle":"2021-08-26T11:46:00.888169Z","shell.execute_reply.started":"2021-08-26T11:46:00.857451Z","shell.execute_reply":"2021-08-26T11:46:00.887116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* AccessionNumber, PatientID and StudyInstanceUID seem to be unique per patient as there are 585 and 87 unique values for **train/** and **test/** datasets, i.e. as many patients there are in each ds respectively. So we can either drop them all and only keep PatientID as the patient identifier.","metadata":{}},{"cell_type":"markdown","source":"* There are some descrepancy between test and train value counts.","metadata":{}},{"cell_type":"code","source":"'''Print attributes unique values for low value count and descrepancy between test and train.'''\nprint(train_meta_df[\"SpecificCharacterSet\"].unique())\nprint(test_meta_df[\"PositionReferenceIndicator\"].unique())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:00.889481Z","iopub.execute_input":"2021-08-26T11:46:00.889848Z","iopub.status.idle":"2021-08-26T11:46:00.934496Z","shell.execute_reply.started":"2021-08-26T11:46:00.889804Z","shell.execute_reply":"2021-08-26T11:46:00.933442Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are not any other obvious descrepancies between **train\\** and **test\\** from the value count, so we will continue looking into the train data for more insights below.","metadata":{}},{"cell_type":"markdown","source":"### In conclusion so far we can drop the following metadata attributes, since they probably won't help our model:\n* SOPInstanceUID\n* ImagePositionPatient\n* SliceLocation\n* WindowWidth\n* WindowCenter\n* SeriesInstanceUID\n* SAR\n* ImageOrientationPatient\n* ImagingFrequency\n* AccessionNumber\n* StudyInstanceUID\n* HighBit\n* RescaleIntercept\n* BitsStored\n* BitsAllocated\n* RescaleSlope\n* PatientPosition\n* PhotometricInterpretation\n* SamplesPerPixel\n* PositionReferenceIndicator\n* Laterality\n* ImageType\n* SpacingBetweenSlices\n* Modality\n* SOPClassUID\n* RescaleType","metadata":{}},{"cell_type":"markdown","source":"So we are left with the following columns","metadata":{}},{"cell_type":"code","source":"list_drop_attrs = [\"SOPInstanceUID\",\"ImagePositionPatient\",\"SliceLocation\",\"WindowWidth\",\n                   \"WindowCenter\",\"SeriesInstanceUID\",\"SAR\",\"ImageOrientationPatient\",\n                   \"ImagingFrequency\",\"AccessionNumber\",\"StudyInstanceUID\",\"HighBit\",\n                   \"RescaleIntercept\",\"BitsStored\",\"BitsAllocated\",\"RescaleSlope\",\n                   \"PatientPosition\",\"PhotometricInterpretation\",\"SamplesPerPixel\",\n                   \"PositionReferenceIndicator\",\"Laterality\",'ImageType',\"SpacingBetweenSlices\",\n                   \"Modality\",\"SOPClassUID\",\"RescaleType\",]","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:00.936357Z","iopub.execute_input":"2021-08-26T11:46:00.936719Z","iopub.status.idle":"2021-08-26T11:46:00.943041Z","shell.execute_reply.started":"2021-08-26T11:46:00.936684Z","shell.execute_reply":"2021-08-26T11:46:00.941533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Columns before : \", len(train_meta_df.columns))\ntrain_meta_df_useful = train_meta_df.drop(list_drop_attrs, axis=1)\nprint(\"Columns after : \", len(train_meta_df_useful.columns))","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:00.945019Z","iopub.execute_input":"2021-08-26T11:46:00.945513Z","iopub.status.idle":"2021-08-26T11:46:01.000792Z","shell.execute_reply.started":"2021-08-26T11:46:00.945462Z","shell.execute_reply":"2021-08-26T11:46:00.999276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.3 Image sizes","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Rows](https://dicom.innolitics.com/ciods/us-image/image-pixel/00280010): Number of rows in the image.Shall be an exact multiple of the vertical downsampling factor if any of the samples (planes) are encoded downsampled in the vertical direction for pixel data encoded in a Native (uncompressed) format. E.g., required to be an even value for a Photometric Interpretation (0028,0004) of YBR_FULL_422.\n\n* [Columns](https://dicom.innolitics.com/ciods/us-image/image-pixel/00280011): Number of columns in the image. Shall be an exact multiple of the horizontal downsampling factor if any of the samples (planes) are encoded downsampled in the horizontal direction for pixel data encoded in a Native (uncompressed) format. E.g., required to be an even value for a Photometric Interpretation (0028,0004) of YBR_FULL_422.","metadata":{}},{"cell_type":"code","source":"sizes = train_meta_df.apply(lambda x: f'{x.Rows}x{x.Columns}', axis=1)\nplt.figure(figsize=(15, 8))\nplt.xticks(rotation=45)\nsns.countplot(sizes);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:01.002313Z","iopub.execute_input":"2021-08-26T11:46:01.002714Z","iopub.status.idle":"2021-08-26T11:46:12.668433Z","shell.execute_reply.started":"2021-08-26T11:46:01.002677Z","shell.execute_reply":"2021-08-26T11:46:12.667152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.4 Series Description","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Series Description](https://dicom.innolitics.com/ciods/segmentation/general-series/0008103e): Description of Series. Long String (LO). \n\nIn our case: T1w, T1wCE, T2w, FLAIR","metadata":{}},{"cell_type":"code","source":"sns.countplot(train_meta_df.SeriesDescription);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:12.670126Z","iopub.execute_input":"2021-08-26T11:46:12.670549Z","iopub.status.idle":"2021-08-26T11:46:13.208430Z","shell.execute_reply.started":"2021-08-26T11:46:12.670504Z","shell.execute_reply":"2021-08-26T11:46:13.207367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.5 Slice Thickness","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Slice Thickness](https://dicom.innolitics.com/ciods/ct-image/image-plane/00180050): Nominal slice thickness, in mm. \n* [Spacing Between Slices](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180088): Spacing between slices, in mm. The spacing is measured from the center-to-center of each slice.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nplt.xticks(rotation=45)\nsns.countplot(train_meta_df.SliceThickness);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:13.209713Z","iopub.execute_input":"2021-08-26T11:46:13.210134Z","iopub.status.idle":"2021-08-26T11:46:13.603393Z","shell.execute_reply.started":"2021-08-26T11:46:13.210093Z","shell.execute_reply":"2021-08-26T11:46:13.602202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.jointplot(data=train_meta_df, x='SliceThickness', y='SpacingBetweenSlices');","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:13.604603Z","iopub.execute_input":"2021-08-26T11:46:13.604886Z","iopub.status.idle":"2021-08-26T11:46:17.887148Z","shell.execute_reply.started":"2021-08-26T11:46:13.604858Z","shell.execute_reply":"2021-08-26T11:46:17.885080Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.6 MR Acquisition Type","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [MR Acquisition Type\n](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180023): Identification of data encoding scheme.Enumerated Values\n    * 2D: frequency x phase\n    * 3D: frequency x phase x phase","metadata":{}},{"cell_type":"code","source":"sns.countplot(train_meta_df.MRAcquisitionType);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:17.888776Z","iopub.execute_input":"2021-08-26T11:46:17.889231Z","iopub.status.idle":"2021-08-26T11:46:18.452823Z","shell.execute_reply.started":"2021-08-26T11:46:17.889184Z","shell.execute_reply":"2021-08-26T11:46:18.451569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.7 Number Of Averages","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Number Of Averages](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180083): Number of times a given pulse sequence is repeated before any parameter is changed.","metadata":{}},{"cell_type":"code","source":"sns.countplot(train_meta_df.NumberOfAverages);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:18.454183Z","iopub.execute_input":"2021-08-26T11:46:18.454501Z","iopub.status.idle":"2021-08-26T11:46:18.698239Z","shell.execute_reply.started":"2021-08-26T11:46:18.454471Z","shell.execute_reply":"2021-08-26T11:46:18.696901Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.8 MagneticFieldStrength","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Magnetic Field](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180087): Nominal field strength of MR magnet, in Tesla.\nExample Values:\n    * 15000 😱😱😱\n    * 1.5\n    * 3\n","metadata":{}},{"cell_type":"code","source":"sns.countplot(train_meta_df.MagneticFieldStrength);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:18.699774Z","iopub.execute_input":"2021-08-26T11:46:18.700113Z","iopub.status.idle":"2021-08-26T11:46:18.921356Z","shell.execute_reply.started":"2021-08-26T11:46:18.700081Z","shell.execute_reply":"2021-08-26T11:46:18.919985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.9 Reconstruction Diameter","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Reconstruction Diameter](https://dicom.innolitics.com/ciods/nm-image/nm-reconstruction/00181100): Diameter, in mm, of the region from within which the data was used in creating the reconstruction of the image. Data may exist outside this region and portions of the patient may exist outside this region. The diameter defines a circular region that is entirely contained within the encoded Pixel Data (7FE0,0010), unless the encoded image has been cropped after reconstruction.\n\n**_Note_**\nIf not cropped or padded, for square images with square pixels, both values of Pixel Spacing (0028,0030) will be equal and equal to Reconstruction Diameter (0018,1100) / Rows (0028,0010) and Reconstruction Diameter (0018,1100) / Columns (0028,0011). ","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nplt.xticks(rotation=45)\nsns.countplot(train_meta_df.ReconstructionDiameter);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:18.922853Z","iopub.execute_input":"2021-08-26T11:46:18.923294Z","iopub.status.idle":"2021-08-26T11:46:19.277496Z","shell.execute_reply.started":"2021-08-26T11:46:18.923257Z","shell.execute_reply":"2021-08-26T11:46:19.275099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.10 Pixel Representation","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Pixel Representation](https://dicom.innolitics.com/ciods/segmentation/image-pixel/00280103): Data representation of the pixel samples. Each sample shall have the same pixel representation.\nEnumerated Values:\n    * 0000H: unsigned integer.\n    * 0001H: 2's complement. ","metadata":{}},{"cell_type":"code","source":"len(train_meta_df.PixelRepresentation.unique())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:19.280058Z","iopub.execute_input":"2021-08-26T11:46:19.280508Z","iopub.status.idle":"2021-08-26T11:46:19.292525Z","shell.execute_reply.started":"2021-08-26T11:46:19.280460Z","shell.execute_reply":"2021-08-26T11:46:19.291202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.countplot(train_meta_df.PixelRepresentation);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:19.294456Z","iopub.execute_input":"2021-08-26T11:46:19.295170Z","iopub.status.idle":"2021-08-26T11:46:19.458085Z","shell.execute_reply.started":"2021-08-26T11:46:19.295117Z","shell.execute_reply":"2021-08-26T11:46:19.457147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4.11 Pixel Representation","metadata":{}},{"cell_type":"markdown","source":"**Definitions from [DICOM Standard Website](https://dicom.innolitics.com/ciods)**\n* [Image Orientation](https://dicom.innolitics.com/ciods/mr-image/image-plane/00200037) (0020,0037) specifies the direction cosines of the first row and the first column with respect to the patient. These Attributes shall be provide as a pair. Row value for the x, y, and z axes respectively followed by the Column value for the x, y, and z axes respectively.\n_The direction of the axes is defined fully by the patient's orientation._","metadata":{}},{"cell_type":"markdown","source":"Get Orientation of each Image from coordinates [x1,y1,x2,y2] to coronal, axial or sagittal.","metadata":{}},{"cell_type":"code","source":"def get_image_plane(data):\n    '''\n    Returns the MRI's plane from the dicom data.\n    \n    '''\n    x1,y1,_,x2,y2,_ = [round(j) for j in ast.literal_eval(data.ImageOrientationPatient)]\n    cords = [x1,y1,x2,y2]\n\n    if cords == [1,0,0,0]:\n        return 'coronal'\n    if cords == [1,0,0,1]:\n        return 'axial'\n    if cords == [0,1,0,0]:\n        return 'sagittal'\n    \ntrain_meta_df['Orientation'] = train_meta_df.apply(get_image_plane, axis=1)\n\ntest_meta_df['Orientation'] = test_meta_df.apply(get_image_plane, axis=1)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:19.459467Z","iopub.execute_input":"2021-08-26T11:46:19.459862Z","iopub.status.idle":"2021-08-26T11:46:38.196709Z","shell.execute_reply.started":"2021-08-26T11:46:19.459827Z","shell.execute_reply":"2021-08-26T11:46:38.195550Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Overall Orientation Distribution","metadata":{}},{"cell_type":"markdown","source":"**/train/ dataset**","metadata":{}},{"cell_type":"code","source":"sns.countplot(train_meta_df.Orientation);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:38.198344Z","iopub.execute_input":"2021-08-26T11:46:38.198774Z","iopub.status.idle":"2021-08-26T11:46:38.753970Z","shell.execute_reply.started":"2021-08-26T11:46:38.198729Z","shell.execute_reply":"2021-08-26T11:46:38.753226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"count: \"axial\" > \"sagittal\" > \"coronal\"","metadata":{}},{"cell_type":"markdown","source":"**/test - dataset**","metadata":{}},{"cell_type":"code","source":"sns.countplot(test_meta_df.Orientation);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:38.754967Z","iopub.execute_input":"2021-08-26T11:46:38.755368Z","iopub.status.idle":"2021-08-26T11:46:38.966872Z","shell.execute_reply.started":"2021-08-26T11:46:38.755338Z","shell.execute_reply":"2021-08-26T11:46:38.965851Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"❗❗❗ SAME AS FOR TRAIN ❗❗❗(Confusing Colors)\n\ncount: \"axial\" > \"sagittal\" > \"coronal\"","metadata":{}},{"cell_type":"markdown","source":"Orientation Distribution per Scan Type","metadata":{}},{"cell_type":"code","source":"sns.histplot(data=train_meta_df, x=\"SeriesDescription\", hue=\"Orientation\", \n             multiple=\"dodge\", shrink=.8);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:38.968206Z","iopub.execute_input":"2021-08-26T11:46:38.968495Z","iopub.status.idle":"2021-08-26T11:46:40.243208Z","shell.execute_reply.started":"2021-08-26T11:46:38.968466Z","shell.execute_reply":"2021-08-26T11:46:40.242059Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.histplot(data=test_meta_df, x=\"SeriesDescription\", hue=\"Orientation\", \n             multiple=\"dodge\", shrink=.8);","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:40.244426Z","iopub.execute_input":"2021-08-26T11:46:40.244750Z","iopub.status.idle":"2021-08-26T11:46:40.680788Z","shell.execute_reply.started":"2021-08-26T11:46:40.244718Z","shell.execute_reply":"2021-08-26T11:46:40.679689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* Similar Distributions for both Train and Test.\n* T2w has more sagittal images.\n* FLAIR more coronal\n* T1w and T1wCE many more axial.","metadata":{}},{"cell_type":"markdown","source":"**Let's see if all images per patient and per modality have the same orientation**","metadata":{}},{"cell_type":"code","source":"df2 = train_meta_df.groupby(['PatientID', 'Orientation', 'SeriesDescription']).size().reset_index(name='count') \ndf2","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:40.682370Z","iopub.execute_input":"2021-08-26T11:46:40.682875Z","iopub.status.idle":"2021-08-26T11:46:40.774368Z","shell.execute_reply.started":"2021-08-26T11:46:40.682763Z","shell.execute_reply":"2021-08-26T11:46:40.773089Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(df2.PatientID.unique()) * 4","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:40.775720Z","iopub.execute_input":"2021-08-26T11:46:40.776167Z","iopub.status.idle":"2021-08-26T11:46:40.784128Z","shell.execute_reply.started":"2021-08-26T11:46:40.776120Z","shell.execute_reply":"2021-08-26T11:46:40.782922Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Indeed for each patient the orientations of images within each modality do not change.","metadata":{}},{"cell_type":"code","source":"dft = test_meta_df.groupby(['PatientID', 'Orientation', 'SeriesDescription']).size().reset_index(name='count') \ndft","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:40.785785Z","iopub.execute_input":"2021-08-26T11:46:40.786285Z","iopub.status.idle":"2021-08-26T11:46:40.822136Z","shell.execute_reply.started":"2021-08-26T11:46:40.786238Z","shell.execute_reply":"2021-08-26T11:46:40.821051Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(dft.PatientID.unique()) * 4","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:40.823688Z","iopub.execute_input":"2021-08-26T11:46:40.824117Z","iopub.status.idle":"2021-08-26T11:46:40.831648Z","shell.execute_reply.started":"2021-08-26T11:46:40.824070Z","shell.execute_reply":"2021-08-26T11:46:40.830587Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Same goes for the test dataset.","metadata":{}},{"cell_type":"markdown","source":"### 4.12 Heatmap","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(10,10))   \nsns.heatmap(train_meta_df.corr(), ax =ax)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:48:13.344887Z","iopub.execute_input":"2021-08-26T11:48:13.345300Z","iopub.status.idle":"2021-08-26T11:48:15.310131Z","shell.execute_reply.started":"2021-08-26T11:48:13.345265Z","shell.execute_reply":"2021-08-26T11:48:15.308831Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Not very interesting patterns in term of metadata feature correlation.","metadata":{}},{"cell_type":"markdown","source":"<a id=\"15\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>5. EDA Pixel Data<center><h2>","metadata":{}},{"cell_type":"markdown","source":"Plot Histogram of Pixel Values per Image","metadata":{}},{"cell_type":"markdown","source":"## Histogram of Raw Image Data","metadata":{}},{"cell_type":"code","source":"def image_stats(image):\n    nonzero_pixels = image[np.nonzero(image)]\n    if nonzero_pixels.shape == (0,):\n        mean = 0\n        std = 0\n    else:\n        mean = np.mean(nonzero_pixels)\n        std = np.std(nonzero_pixels)\n    return (mean,std)\n\ndef plot_image_hist(image, threshold = 1.5, normalize = False):\n    pixels = image.ravel()\n    nonzero_pixels = pixels[np.nonzero(pixels)]\n    (mean,std) = image_stats(nonzero_pixels)\n    if normalize:\n        nonzero_pixels = (nonzero_pixels - mean) / std\n        (mean,std) = image_stats(nonzero_pixels)\n    over_threshold = np.count_nonzero(nonzero_pixels > mean + threshold * std)\n\n    fig, (axi, axh) = plt.subplots(1, 2, figsize = (20,3), \n                                   gridspec_kw={'width_ratios': [1, 4]})\n    fig.suptitle(f'Pixels over threshold # ({over_threshold})')\n\n    axh.hist(nonzero_pixels, 200)\n\n    ax_limits = axh.get_ylim()\n    axh.vlines(mean, ymin=ax_limits[0], \n               ymax=ax_limits[1], colors='b')\n    axh.vlines(mean+std, ymin=ax_limits[0], \n               ymax=ax_limits[1], colors='b', linestyles='dotted')\n    axh.vlines(mean + threshold * std, ymin=ax_limits[0], \n               ymax=ax_limits[1], colors='b', linestyles='dashed')\n    axi.imshow(image, cmap = plt.cm.gray)\n    axi.grid(False)\n    axi.axis('off')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:42.330864Z","iopub.execute_input":"2021-08-26T11:46:42.331216Z","iopub.status.idle":"2021-08-26T11:46:42.344181Z","shell.execute_reply.started":"2021-08-26T11:46:42.331176Z","shell.execute_reply":"2021-08-26T11:46:42.342756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Plot DICOM image and pixel value distributions next to each other.","metadata":{}},{"cell_type":"code","source":"img = load_dicom(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000/T2w/Image-200.dcm\")\nplot_image_hist(img, threshold = 2, normalize = True)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:42.345675Z","iopub.execute_input":"2021-08-26T11:46:42.346165Z","iopub.status.idle":"2021-08-26T11:46:43.113467Z","shell.execute_reply.started":"2021-08-26T11:46:42.346129Z","shell.execute_reply":"2021-08-26T11:46:43.112099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_image_hist(img, threshold = 2, normalize = False)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:43.114623Z","iopub.execute_input":"2021-08-26T11:46:43.114964Z","iopub.status.idle":"2021-08-26T11:46:43.786546Z","shell.execute_reply.started":"2021-08-26T11:46:43.114929Z","shell.execute_reply":"2021-08-26T11:46:43.785493Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Visualize the histogram distribution of all the scans for a random patient ID.","metadata":{}},{"cell_type":"code","source":"def visualize_hist_sample_image(\n    brats21id, \n    slice_i,\n    mgmt_value,\n    types=(\"FLAIR\", \"T1w\", \"T1wCE\", \"T2w\"),\n    threshold = 1.5, normalize = False\n):\n    plt.figure(figsize=(16, 5))\n    patient_path = os.path.join(\n        IMG_PATH_TRAIN, \n        str(brats21id).zfill(5),\n    )\n    for i, t in enumerate(types, 1):\n        t_paths = sorted(\n            glob.glob(os.path.join(patient_path, t, \"*\")), \n            key=lambda x: int(x[:-4].split(\"-\")[-1]),\n        )\n        image = load_dicom(t_paths[int(len(t_paths) * slice_i)])\n        pixels = image.ravel()\n        nonzero_pixels = pixels[np.nonzero(pixels)]\n        (mean,std) = image_stats(nonzero_pixels)\n        if normalize:\n            nonzero_pixels = (nonzero_pixels - mean) / std\n            (mean,std) = image_stats(nonzero_pixels)\n        over_threshold = np.count_nonzero(nonzero_pixels > mean + threshold * std)\n\n        fig, (axi, axh) = plt.subplots(1, 2, figsize = (20,3), \n                                       gridspec_kw={'width_ratios': [1, 4]});\n\n        axh.hist(nonzero_pixels, 200)\n\n        ax_limits = axh.get_ylim()\n        axh.vlines(mean, ymin=ax_limits[0], \n                   ymax=ax_limits[1], colors='b')\n        axh.vlines(mean+std, ymin=ax_limits[0], \n                   ymax=ax_limits[1], colors='b', linestyles='dotted')\n        axh.vlines(mean + threshold * std, ymin=ax_limits[0], \n                   ymax=ax_limits[1], colors='b', linestyles='dashed')\n        axi.imshow(image, cmap = plt.cm.gray)\n        axi.grid(False)\n        plt.title(f\"{t}: pixels over threshold # ({over_threshold})\", fontsize=16)\n        axi.axis('off')\n#     plt.suptitle(f\"MGMT_value: {mgmt_value}\", fontsize=16)\n    plt.show()\n    print(f\"MGMT_value: {mgmt_value}\")","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:43.787832Z","iopub.execute_input":"2021-08-26T11:46:43.788129Z","iopub.status.idle":"2021-08-26T11:46:43.801549Z","shell.execute_reply.started":"2021-08-26T11:46:43.788101Z","shell.execute_reply":"2021-08-26T11:46:43.800744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i in random.sample(range(train_df.shape[0]), 2): # get 10 random indexes from the train ds\n    _brats21id = train_df.iloc[i][\"BraTS21ID\"] # for these indexes get the associated brats ID\n    _mgmt_value = train_df.iloc[i][\"MGMT_value\"] # and tumor class\n    visualize_hist_sample_image(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.5,\n                           threshold = 2, normalize = True) # visualize samples","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:43.802493Z","iopub.execute_input":"2021-08-26T11:46:43.802777Z","iopub.status.idle":"2021-08-26T11:46:49.591248Z","shell.execute_reply.started":"2021-08-26T11:46:43.802748Z","shell.execute_reply":"2021-08-26T11:46:49.590098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Different scans have different histogram distributions as they focus and capture different pathological features.","metadata":{}},{"cell_type":"markdown","source":"## Mask and Plot Image Data","metadata":{}},{"cell_type":"code","source":"def visualize_masked_sample(\n    brats21id, \n    slice_i,\n    mgmt_value,\n    types=(\"FLAIR\", \"T1w\", \"T1wCE\", \"T2w\"),\n    threshold = -1\n):\n    plt.figure(figsize=(16, 5))\n    patient_path = os.path.join(\n        IMG_PATH_TRAIN, \n        str(brats21id).zfill(5),\n    )\n    for i, t in enumerate(types, 1):\n        t_paths = sorted(\n            glob.glob(os.path.join(patient_path, t, \"*\")), \n            key=lambda x: int(x[:-4].split(\"-\")[-1]),\n        )\n        data = load_dicom(t_paths[int(len(t_paths) * slice_i)])\n        if threshold > -1:\n            data[data < threshold] = 0\n        plt.subplot(1, 4, i)\n        plt.imshow(data, cmap=\"gray\")\n        plt.title(f\"{t}\", fontsize=16)\n        plt.axis(\"off\")\n\n    plt.suptitle(f\"MGMT_value: {mgmt_value}\", fontsize=16)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:49.593020Z","iopub.execute_input":"2021-08-26T11:46:49.593444Z","iopub.status.idle":"2021-08-26T11:46:49.603496Z","shell.execute_reply.started":"2021-08-26T11:46:49.593398Z","shell.execute_reply":"2021-08-26T11:46:49.602387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Randomly choose a case to visualize and explore whether a threshold can be applied.","metadata":{}},{"cell_type":"code","source":"i = 520\n_brats21id = train_df.iloc[i][\"BraTS21ID\"] # for these indexes get the associated brats ID\n_mgmt_value = train_df.iloc[i][\"MGMT_value\"] # and tumor class\nvisualize_sample(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.5) # visualize samples","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:49.604786Z","iopub.execute_input":"2021-08-26T11:46:49.605102Z","iopub.status.idle":"2021-08-26T11:46:50.048196Z","shell.execute_reply.started":"2021-08-26T11:46:49.605072Z","shell.execute_reply":"2021-08-26T11:46:50.047231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Replace with zeros all pixels with values below a threshold.","metadata":{}},{"cell_type":"code","source":"i = 520\n_brats21id = train_df.iloc[i][\"BraTS21ID\"] # for these indexes get the associated brats ID\n_mgmt_value = train_df.iloc[i][\"MGMT_value\"] # and tumor class\nvisualize_masked_sample(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.4,\n                           threshold = 80) # visualize samples","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:50.049440Z","iopub.execute_input":"2021-08-26T11:46:50.049740Z","iopub.status.idle":"2021-08-26T11:46:50.439154Z","shell.execute_reply.started":"2021-08-26T11:46:50.049709Z","shell.execute_reply":"2021-08-26T11:46:50.438174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Replace with zeros all pixels with values below a larger threshold.","metadata":{}},{"cell_type":"code","source":"i = 520\n_brats21id = train_df.iloc[i][\"BraTS21ID\"] # for these indexes get the associated brats ID\n_mgmt_value = train_df.iloc[i][\"MGMT_value\"] # and tumor class\nvisualize_masked_sample(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.5,\n                           threshold = 120) # visualize samples","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:50.440496Z","iopub.execute_input":"2021-08-26T11:46:50.441005Z","iopub.status.idle":"2021-08-26T11:46:50.811139Z","shell.execute_reply.started":"2021-08-26T11:46:50.440957Z","shell.execute_reply":"2021-08-26T11:46:50.810272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It's difficult to find and apply a lower threshold in each image in order to isolate the tumor and remove non-important features, as there is no single threshold that fits all the kinds of scans. This is because especially T1wCE is bright overall when the majority of features of FLAIR and T2w appear darker. The reason for this is that each of the different scan types capture and give emphasis on different pathological/anatomical structures.","metadata":{}},{"cell_type":"markdown","source":"## Image Pixel Data Stats","metadata":{}},{"cell_type":"markdown","source":"For this part we make use of image statistical properties and [this dataset](https://www.kaggle.com/smoschou55/train-test-filepaths-rsna-full) produced using [this notebook](https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36).","metadata":{}},{"cell_type":"code","source":"train_px_df = pd.read_csv('/kaggle/input/train-test-filepaths-rsna-full/stats_train_file_paths_df.csv')\ntest_px_df = pd.read_csv('/kaggle/input/train-test-filepaths-rsna-full/stats_test_file_paths_df.csv')","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:50.812442Z","iopub.execute_input":"2021-08-26T11:46:50.812967Z","iopub.status.idle":"2021-08-26T11:46:53.178884Z","shell.execute_reply.started":"2021-08-26T11:46:50.812933Z","shell.execute_reply":"2021-08-26T11:46:53.178075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**\\train**","metadata":{}},{"cell_type":"code","source":"stats_cols = []\nnum_unique = []\n\nfor col in train_px_df:\n#     print(\"* For attribute  '{}' , there are [ {} ] unique values.\".format(col,\n#                     len(train_meta_df[col].unique())))\n    stats_cols.append(col)\n    num_unique.append(len(train_px_df[col].unique()))\n    \ntrain_df_stats = pd.DataFrame(\n    {'col_name': stats_cols,\n     'value_count': num_unique,\n     'nan_count': train_px_df.isna().sum()\n    })\n\ntrain_df_stats = train_df_stats.sort_values(by=['value_count'], ascending=False).reset_index(drop=True)\ntrain_df_stats = train_df_stats.set_index('col_name').T\ntrain_df_stats","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:53.180091Z","iopub.execute_input":"2021-08-26T11:46:53.180574Z","iopub.status.idle":"2021-08-26T11:46:53.764325Z","shell.execute_reply.started":"2021-08-26T11:46:53.180541Z","shell.execute_reply":"2021-08-26T11:46:53.763269Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**\\test**","metadata":{}},{"cell_type":"code","source":"stats_cols = []\nnum_unique = []\n\nfor col in test_px_df:\n#     print(\"* For attribute  '{}' , there are [ {} ] unique values.\".format(col,\n#                     len(train_meta_df[col].unique())))\n    stats_cols.append(col)\n    num_unique.append(len(test_px_df[col].unique()))\n    \ntest_df_stats = pd.DataFrame(\n    {'col_name': stats_cols,\n     'value_count': num_unique,\n     'nan_count': test_px_df.isna().sum()\n    })\n\ntest_df_stats = test_df_stats.sort_values(by=['value_count'], ascending=False).reset_index(drop=True)\ntest_df_stats = test_df_stats.set_index('col_name').T\ntest_df_stats","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:53.765622Z","iopub.execute_input":"2021-08-26T11:46:53.765913Z","iopub.status.idle":"2021-08-26T11:46:53.881362Z","shell.execute_reply.started":"2021-08-26T11:46:53.765884Z","shell.execute_reply":"2021-08-26T11:46:53.880339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Min and Max values for all Images in Train and Test**","metadata":{}},{"cell_type":"code","source":"print(train_px_df.min_px.unique(), train_px_df.max_px.unique())\nprint(test_px_df.min_px.unique(), test_px_df.max_px.unique())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:53.882704Z","iopub.execute_input":"2021-08-26T11:46:53.883016Z","iopub.status.idle":"2021-08-26T11:46:53.896101Z","shell.execute_reply.started":"2021-08-26T11:46:53.882970Z","shell.execute_reply":"2021-08-26T11:46:53.894744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- We confirm that the images have been normalized between [0, 255].\n- All images have min value 0 and max value either min=max=0 or max=255.\n\nThe normalization has taken place within the notebook: [🧠 DICOM to 2D Resized Axial PNGs 256x256 [x36] 🧠](https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36) in order to draw useful conclusions and make meaningful comparisons.","metadata":{}},{"cell_type":"code","source":"min(train_px_df.mean_px.unique()), max(train_px_df.mean_px.unique())","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:53.897763Z","iopub.execute_input":"2021-08-26T11:46:53.898219Z","iopub.status.idle":"2021-08-26T11:46:54.059269Z","shell.execute_reply.started":"2021-08-26T11:46:53.898173Z","shell.execute_reply":"2021-08-26T11:46:54.058250Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 2, figsize=(25, 8), sharey=True)\nfig.suptitle('Train - Test Dataset Pixel Distributions Mean + STD')\n\nsns.histplot(ax=axes[0], data = train_px_df[['mean_px', 'std_px']], bins=50, alpha=0.5)\naxes[0].set_yscale('log')\nsns.histplot(ax=axes[1], data = test_px_df[['mean_px', 'std_px']], bins=50, alpha=0.5)\nplt.show();","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:54.060915Z","iopub.execute_input":"2021-08-26T11:46:54.061356Z","iopub.status.idle":"2021-08-26T11:46:57.305825Z","shell.execute_reply.started":"2021-08-26T11:46:54.061315Z","shell.execute_reply":"2021-08-26T11:46:57.304771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- Train and test images follow similar distributions with respect the mean and standard deviation.\n- Test_mean = train_mean - 20 and test_std = train_std - 20.","metadata":{}},{"cell_type":"markdown","source":"### Take a look into distribution per scan type","metadata":{}},{"cell_type":"markdown","source":"**/train**","metadata":{}},{"cell_type":"code","source":"g = sns.FacetGrid(train_px_df, col=\"scan_type\")\ng.map(plt.hist, 'mean_px', bins=50);\ng.set(yscale=\"log\")","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:46:57.307335Z","iopub.execute_input":"2021-08-26T11:46:57.307670Z","iopub.status.idle":"2021-08-26T11:47:00.758805Z","shell.execute_reply.started":"2021-08-26T11:46:57.307636Z","shell.execute_reply":"2021-08-26T11:47:00.757807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"With respect to pixel values we see that:\n- All scans peak at mean = 0, i.e. there are many empty slices.\n- T2w distribution peaks at mean = 20 and almost cuts off at 60, with low values up to 100.\n- T1wCE is similar to T2w with the difference that the peak at value 20 is rounder and wider.\n- T1w is thicker and more uniform with a sharp cutoff at value 100.\n- FLAIR distribution is similar to T2w with a peak at 30 and a thicker tail that cutsoff at 80 with low values up to 100.","metadata":{}},{"cell_type":"markdown","source":"**/test**","metadata":{}},{"cell_type":"code","source":"g = sns.FacetGrid(test_px_df, col=\"scan_type\")\ng.map(plt.hist, 'mean_px', bins=50);\ng.set(yscale=\"log\")","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:00.760115Z","iopub.execute_input":"2021-08-26T11:47:00.760405Z","iopub.status.idle":"2021-08-26T11:47:03.283675Z","shell.execute_reply.started":"2021-08-26T11:47:00.760375Z","shell.execute_reply":"2021-08-26T11:47:03.282512Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- Similar to **/train** with the difference that cutsoff are all value 20 less than train cut off values.\n- Peaks remain in the same places as train peaks.","metadata":{}},{"cell_type":"markdown","source":"### Look into label differences","metadata":{}},{"cell_type":"code","source":"train_lbls = pd.read_csv(TRAIN_CSV_PATH)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:03.285341Z","iopub.execute_input":"2021-08-26T11:47:03.285765Z","iopub.status.idle":"2021-08-26T11:47:03.295391Z","shell.execute_reply.started":"2021-08-26T11:47:03.285718Z","shell.execute_reply":"2021-08-26T11:47:03.294097Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_px_df = train_px_df.merge(train_lbls, left_on='patient_id', right_on='BraTS21ID')\ntrain_px_df.head(2)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:03.296882Z","iopub.execute_input":"2021-08-26T11:47:03.297248Z","iopub.status.idle":"2021-08-26T11:47:03.419833Z","shell.execute_reply.started":"2021-08-26T11:47:03.297215Z","shell.execute_reply":"2021-08-26T11:47:03.418740Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"g = sns.FacetGrid(train_px_df, col=\"scan_type\", hue='MGMT_value')\ng.map(plt.hist, 'mean_px', bins=50, alpha=0.5);\ng.set(yscale=\"log\")\ng.add_legend()","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:03.421160Z","iopub.execute_input":"2021-08-26T11:47:03.421579Z","iopub.status.idle":"2021-08-26T11:47:08.397733Z","shell.execute_reply.started":"2021-08-26T11:47:03.421542Z","shell.execute_reply":"2021-08-26T11:47:08.396816Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Interestingly, there are some noticeable differences between the distributions of pixels between patients with MGMT_value 0 and MGMT_value 1.\n- Images of Patients with MGMT methylation, have larger peaks for T2w, T1wCE and FLAIR. This could be due to some number difference between slices in the two groups.\n- And most importantly, Images of Patients with MGMT methylation have consistently larger mean values for scan types T2w, T1wCE and FLAIR.\n- T1w distributions for both MGMT 0 and 1 groups are almost indiscriminate.","metadata":{}},{"cell_type":"markdown","source":"<a id=\"20\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>6. 3D Visualizations<center><h2>","metadata":{}},{"cell_type":"markdown","source":"## Load 3D DICOM files and create animations with them","metadata":{}},{"cell_type":"markdown","source":"TBD : Work in Progress","metadata":{}},{"cell_type":"code","source":"images = load_dicom_line(IMG_PATH_TRAIN + \"00000/FLAIR\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:08.400066Z","iopub.execute_input":"2021-08-26T11:47:08.400673Z","iopub.status.idle":"2021-08-26T11:47:30.265872Z","shell.execute_reply.started":"2021-08-26T11:47:08.400625Z","shell.execute_reply":"2021-08-26T11:47:30.265176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = load_dicom_line(IMG_PATH_TRAIN + \"00000/T1w\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:30.267008Z","iopub.execute_input":"2021-08-26T11:47:30.267487Z","iopub.status.idle":"2021-08-26T11:47:32.581468Z","shell.execute_reply.started":"2021-08-26T11:47:30.267455Z","shell.execute_reply":"2021-08-26T11:47:32.580293Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = load_dicom_line(IMG_PATH_TRAIN + \"00000/T1wCE\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:32.582670Z","iopub.execute_input":"2021-08-26T11:47:32.582971Z","iopub.status.idle":"2021-08-26T11:47:39.159110Z","shell.execute_reply.started":"2021-08-26T11:47:32.582941Z","shell.execute_reply":"2021-08-26T11:47:39.158064Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = load_dicom_line(IMG_PATH_TRAIN + \"00000/T2w\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2021-08-26T11:47:39.160338Z","iopub.execute_input":"2021-08-26T11:47:39.160658Z","iopub.status.idle":"2021-08-26T11:48:00.368609Z","shell.execute_reply.started":"2021-08-26T11:47:39.160625Z","shell.execute_reply":"2021-08-26T11:48:00.367608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"30\"></a>\n<h2 style='background:darkviolet; border:0; color:white'><center>7. Conclusions<center><h2>","metadata":{}},{"cell_type":"markdown","source":"In general:\n\n**This compeition contains some highly consistent data, with well-balanced distributions between train and test datasets and with very informative matadata that correspond to the actual values, ot the extend investigated in this notebook.**\n\nMinor points to be careful with and that are not expected to affect the data handling and modeling part:\n* There seem to be both 3D and 2D labeled MR Acquisition Types. I don't expect this to have any impact on modeling though, as there still are 2D slices comprising a 3D volume of a brain.\n* There is a magnetic field intensity 15000 suposedly in Tesla, but that is most probably a typo meaning 15000 Gauss which is equal to 1.5 Tesla.\n\nSpecific comments:\n* There is no class imbalance between positive and negative class in the train dataset, i.e. half of the train do show MGMT methylation and half do not.\n* There several images that have very small parts of the brain or nothing at all captured and these are expected to not play a major role in the classification task later on, so could be removed as a first order approach.\n* Spacing resolution seems to be the same for all images, i.e. Spacing Between Slices is 1mm.\n* Most images are of size 512x512, but sizes can vary significantly, so resazing is going to be necessary for most ML algorithms.\n* There are at laeast about 80,000 images per each one of the T1w, T1wCE, T2w, FLAIR modalities\n* Overall there are more axial images than sagittal and way more than coronal (more on [MRI Planes in this notebook section](https://www.kaggle.com/arnabs007/part-1-rsna-miccai-btrc-understanding-the-data/comments?scriptVersionId=70970330&cellId=18))\n* The dominant orientations strongly vary between the different modalities. This needs to be considered in 2D modeling efforts or when combining the different modalities in preprocessing for example. More specifically,\n    * T2w dominant oriantation is saggital\n    * FLAIR dominant orientation is coronal and\n    * T1w and T1wCE dominant orientation is axial.\n* Train and Test datasets follow similar trends in terms of orientation and most features explored herein.\n* For each patient the orientations of images within each modality do not change.\n* Tumors appear bright in FLAIR and T2w, less bright in T1wCE, and not bright at all in T1w. Histograms reflect this as well, with different modalities having their peaks and being skewed towards different directions. This needs to be considered if one wants to apply a low limit on pixel intensity to remove non-informative pixels. There is no single threshold that fits all.\n* Interestingly, there are some noticeable differences between the mean distributions of pixels between patients with MGMT_value 0 and MGMT_value 1.\n    - Images of Patients with MGMT methylation, have larger peaks for T2w, T1wCE and FLAIR. This could be due to some number difference between slices in the two groups.\n    - And most importantly, Images of Patients with MGMT methylation have consistently larger mean values for scan types T2w, T1wCE and FLAIR.\n    - T1w distributions for both MGMT 0 and 1 groups are almost indiscriminate.\n\nRegarding combining data from the different modalities and using external sources, you can read more about it in [this discussion post](https://www.kaggle.com/c/rsna-miccai-brain-tumor-radiogenomic-classification/discussion/253488), but the main points are also summarized below.\n* We can use the Task 1 Tumor Segmentation dataset that is part of the larger BraTS21 challenge hosted in a different platform: https://www.synapse.org/#!Synapse:syn25829067/wiki/610863\n* In order to properly use the extra datasets, we need to keep in mind that the Task 1 data is resampled, co-registered, NIFTI files, whereas our data (Task 2 : Radiogenomics Classification) DICOM files, have various resolutions and are not co-registered.\n* Co-registration is a transformation / alignment into the same coordinate system and this can happen in a number of ways. \n* The proper from the [FSL course Material](https://fsl.fmrib.ox.ac.uk/fslcourse/online_materials.html) seems to be:\n    * the Afine + Non-linear Registrations for cross-subject transformations (multi-patient studies) to align scans of all subjects\n    * and I don't think that we can co-register / align images between different modalities e.g. T2w with FLAIR and T1w, there were originally captured to be in different planes (sagittal, coronal, axial).\n* See also the [🧠 DICOM to 2D Resized Axial PNGs 256x256 [x36] 🧠](https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36) notebook and references therein.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}