{"cells":[{"metadata":{},"cell_type":"markdown","source":"#  The VinBigData Chest X-ray Classification Competition\n\nIn this competition, we are localizing critical findings and classifying common thoracic lung diseases. Results can be submitted as in the **'sample_submission.scv'** file; the id of the test images with a string containing the class id, the confidence value, and the bounding box. For the normal test images, i.e. no findings, the submission should be the image_id and the prediction string **`14 1 0 0 1 1`** *(the class ID , 14 for 'no finding', a one-pixel bounding box, and a confidence of 1.0)*\n  \n### (This is an ongoing notebook!)\n"},{"metadata":{},"cell_type":"markdown","source":"# The Data \n"},{"metadata":{},"cell_type":"markdown","source":"The sample data contains **18000** images; **15000** manually labeled images for training (in the **train** directory) and **3000** for (testing in the **test** directory). There are **14** Classes of the findings in the training data, they have the following (class_id and class_name):\n\n> **`0`** - Aortic enlargement <br>\n**`1`** - Atelectasis <br>\n**`2`** - Calcification <br>\n**`3`** - Cardiomegaly <br>\n**`4`** - Consolidation <br>\n**`5`** - ILD <br>\n**`6`** - Infiltration <br>\n**`7`** - Lung Opacity <br>\n**`8`** - Nodule/Mass <br>\n**`9`** - Other lesion <br>\n**`10`** - Pleural effusion <br>\n**`11`** - Pleural thickening <br>\n**`12`** - Pneumothorax <br>\n**`13`** - Pulmonary fibrosis <br>\n**`14`** - \"No finding\"\n\nThe last class, 14, means that the x-ray image is normal, i.e. has no findings.\n\nThe **'train.cvs'** file contains the labels for each training image with the following columns:\n\n> **`image_id`** - unique image identifier<br>\n**`class_name`** - one of the 14th class names<br>\n**`class_id`** - the ID of the class of detected object<br>\n**`rad_id`** - the ID of the radiologist that made the observation<br>\n**`x_min`** - minimum X coordinate of the object's bounding box<br>\n**`y_min`** - minimum Y coordinate of the object's bounding box<br>\n**`x_max`** - maximum X coordinate of the object's bounding box<br>\n**`y_max`** - maximum Y coordinate of the object's bounding box\n"},{"metadata":{},"cell_type":"markdown","source":"## Import libraries"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport pydicom\nimport warnings\nwarnings.filterwarnings(\"ignore\")\nimport os\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 1. Data Directory"},{"metadata":{"trusted":true},"cell_type":"code","source":"DataDir = \"../input/vinbigdata-chest-xray-abnormalities-detection/\"\n!ls {DataDir}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Ntrain = len(os.listdir(DataDir+'train'))\nNtest = len(os.listdir(DataDir+'test'))\nprint(f\"\\n... The number of training files is {Ntrain} ...\")\nprint(f\"... The number of testing files is {Ntest} ...\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 2. Reading Data"},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(DataDir+'train.csv')\nsub = pd.read_csv(DataDir+'sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(train)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"So, 67914 rows mean that most of the images have multiple labels."},{"metadata":{"trusted":true},"cell_type":"code","source":"len(sub)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The prediction string contain 6 values \"14 1 0 0 1 1\";  the class ID ,14 for'no finding', followed by the confidence value, 1 for class 14, then the coordinate of the bounding box (in this case a one-pixel bounding box).\n \nThe images are in DICOM format, which means they contain additional data that might be useful for visualizing and classifying."},{"metadata":{},"cell_type":"markdown","source":"## Coverting DICOM Images into Data Arrays (PNG images)"},{"metadata":{},"cell_type":"markdown","source":"Most of DICOM's store pixel values in exponential scale, which is resolved by standard standard DICOM viewers. So in order to get jpg/png the correct way (DICOM metadata stores information how to make such \"human-friendly\" transformations), we need to apply some transformations."},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Reading the image as numpy.array\n#\ndcm_File_Name = DataDir+ 'train/'+train['image_id'][2]+'.dicom'\ndcm_file = pydicom.dcmread(dcm_File_Name)\n\ndcm_pixels = dcm_file.pixel_array\ndcm_pixels","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Displaying the X-Ray Image"},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(10,8))\nplt.imshow(dcm_pixels, cmap=plt.cm.gray)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Now transform image data into a data array"},{"metadata":{"trusted":true},"cell_type":"code","source":"from pydicom.pixel_data_handlers.util import apply_voi_lut\n\ndef dicom2png(path, voi_lut = True, fix_monochrome = True):\n    dicom = pydicom.read_file(path)\n    \n    # VOI LUT (if available by DICOM device) is used to transform raw DICOM data to \"human-friendly\" view\n    if voi_lut:\n        data = apply_voi_lut(dicom.pixel_array, dicom)\n    else:\n        data = dicom.pixel_array\n               \n    # depending on this value, X-ray may look inverted - fix that:\n    if fix_monochrome and dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = np.amax(data) - data\n        \n    data = data - np.min(data)\n    data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n        \n    return data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Image without fixing Monochrome.\n#\npng_image = dicom2png(dcm_File_Name, fix_monochrome = False)\nplt.figure(figsize = (8,8))\nplt.imshow(png_image, 'gray')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Image after fixing Monochrome.\n#\npng_image = dicom2png(dcm_File_Name)\nplt.figure(figsize = (8,8))\nplt.imshow(png_image, 'gray')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Extract Patient's and Image attributes from Raw Dicom files. "},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Finding the keywords that access the Attributes (Data elements).\n#\ndcm_file.dir()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_dcm_attributes(path):\n\n    df = pd.DataFrame(columns=['image_id', 'Age', 'Gender','Image_Hieght',\n                    'ImageWidth','x_spacing','y_spacing'])\n    #Read some files for testing\n    files = list(os.listdir(path))[0:10]\n    #Read All files\n    #files = list(os.listdir(path))\n   \n    try:\n        i = 0\n        for file in files:\n\n            file_path = os.path.join(path,file)\n            dcmData = pydicom.dcmread(file_path,stop_before_pixels=True)\n\n            file_name = file.split(\".\")[0]\n\n            attributes = dcmData.dir()\n            if 'PatientAge' in attributes:\n                age_str = dcmData.PatientAge\n                if age_str != '' and age_str != 'Y':\n                    age = int(age_str[:-1])\n                else:\n                    age = np.NaN\n            else:\n                age = np.NaN\n            if 'PatientSex' in attributes:\n                gender = dcmData.PatientSex\n                if gender =='' : gender = np.NaN\n            else:\n                gender = np.NaN\n            if 'Rows' in attributes:\n                rows = dcmData.Rows\n            else:\n                rows = np.NaN\n            if 'Columns' in attributes:\n                clmns = dcmData.Columns\n            else:\n                clmns = np.NaN\n            if 'PixelSpacing' in attributes:\n                ps = dcmData.PixelSpacing\n            else:\n                ps = [np.NaN,np.NaN]\n\n            df = df.append(pd.DataFrame({'image_id': file_name, \n                    'Age': age, 'Gender': gender,'Image_Hieght': rows,\n                    'ImageWidth': clmns,\n                    'x_spacing': ps[0],'y_spacing': ps[1]}, index=[i]))\n            i+=1\n    except ValueError:\n            print('age_str',\"   \", age_str)\n    return df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Reading attributes. (it takes several minutes for the whole dataset)\n#\nTrainDir = DataDir+'train/'\ndcm_attr = get_dcm_attributes(TrainDir)\ndcm_attr.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"np.sum(dcm_attr.isna())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plotting Bounding Boxes\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"trainf = train[train['class_id'] != 14]\ntrainf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from glob import glob\nimport cv2\nimport random\nfrom random import randint","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"    \ndef plot_img(img, size=(8, 8), is_rgb=True, title=\"\", cmap='gray'):\n    plt.figure(figsize=size)\n    plt.imshow(img, cmap=cmap)\n    plt.suptitle(title)\n    plt.show()\n\n\ndef plot_imgs(imgs, cols=4, size=8, is_rgb=True, title=\"\", cmap='gray', img_size=(500,500)):\n    rows = len(imgs)//cols + 1\n    fig = plt.figure(figsize=(cols*size, rows*size))\n    for i, img in enumerate(imgs):\n        if img_size is not None:\n            img = cv2.resize(img, img_size)\n        fig.add_subplot(rows, cols, i+1)\n        plt.imshow(img, cmap=cmap)\n    plt.suptitle(title)\n    plt.show()\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"imgs = []\nimg_ids = trainf['image_id'].values\nclass_ids = trainf['class_id'].unique()\n\n# map class_id to a color code\nclass2color = {class_id:[randint(0,255) for i in range(3)] for class_id in class_ids}\nthickness = 3\nscale = 5\n\n\nfor i in range(8):\n    img_id = random.choice(img_ids)\n    img_path = f'{DataDir}/train/{img_id}.dicom'\n    img = dicom2png(img_path)\n    img = cv2.resize(img, None, fx=1/scale, fy=1/scale)\n    img = np.stack([img, img, img], axis=-1)\n    \n    boxes = trainf.loc[trainf['image_id'] == img_id, ['x_min', 'y_min', 'x_max', 'y_max']].values/scale\n    classes = trainf.loc[trainf['image_id'] == img_id, ['class_id']].values.squeeze()\n    \n    for c_id, box in zip(classes, boxes):\n        color = class2color[c_id]\n        img = cv2.rectangle(\n            img,\n            (int(box[0]), int(box[1])),\n            (int(box[2]), int(box[3])),\n            color, thickness\n    )\n    img = cv2.resize(img, (500,500))\n    imgs.append(img)\n    \nplot_imgs(imgs, cmap=None)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## EDA of the Training Data\n\nNow we explore the provided annotations for the training data in the **'train.csv'** file. \n\nFrom the below analysis, we see that:\n\n* The minimum number of annotations per patient are 3 and the maximum are 57.\n* There are 17 radiologists, only radiologists (from R8 to R17) who annotated the images with findings. Most annotations are done by Radiologists R8, R9, and R10.\n* Radiologists R8 and R9 are consistent with each others, while R9 seams to have more annotations.\n* The most identified classes are 0, 3, and 11 by R9.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"trainf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Visualization Imports\nfrom matplotlib.colors import ListedColormap\nimport plotly.graph_objects as go\nimport plotly.express as px\nimport plotly\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = px.histogram(trainf, x=\"image_id\", \n                  labels={\"value\":\"# of Annotations\"},\n                  title=\"<b>    Number of annotations per patient </b>\",\n                  )\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = px.histogram(trainf, x=\"rad_id\", \n                  log_y = True,\n                  labels={\"value\":\"# of Annotations\"},\n                  title=\"<b>    Distribution of Radiologists for the 'Findings'</b>\")\nfig.update_layout(showlegend=False,\n                  xaxis_title=\"<b>Radiologist ID</b>\",\n                  yaxis_title=\"<b>Count   (log scale)</b>\",\n                  )\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = px.histogram(train[train['class_id']==14] , x=\"rad_id\", \n                  log_y = True,\n                  #labels={\"value\":\"# of Annotations\"},\n                  title=\"<b>Distribution of Radiologists for the 'No Finding'</b>\")\nfig.update_layout(showlegend=False,\n                  xaxis_title=\"<b>Radiologist ID</b>\",\n                  yaxis_title=\"<b>Count   (log scale)</b>\",\n                 )\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = px.density_heatmap(train, x=\"rad_id\", y=\"class_id\",\n                 title=\"Distribution of Classes per Radiologists\")\nfig.update_layout(showlegend=False,\n                 xaxis_title=\"Radiologist ID\",\n                 yaxis_title=\"Class ID\",\n                 )\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#from pandas_profiling import ProfileReport\n#profile = ProfileReport(train, title='Pandas Profiling Report')#, explorative=True)\n#profile.to_widgets()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Classes' Visualization"},{"metadata":{"trusted":true},"cell_type":"code","source":"#\n# Create Mapping of the Class id\n#\n# Create dictionary mappings\n\ncid2cname = {i:train[train[\"class_id\"]==i].iloc[0][\"class_name\"] for i in range(15)}\ncname2cid = {v:k for k,v in cid2cname.items()}\n\nprint(\"\\n... Dictionary Mapping of class_id to class name ...\\n\")\ndisplay(cid2cname)\n\nprint(\"\\n... Dictionary Mapping class name to class_id ...\\n\")\ndisplay(cname2cid)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = go.Figure()\n\nfor i in range(15):\n    fig.add_trace(go.Histogram(x=train[train[\"class_id\"]==i][\"rad_id\"],\n                name=f\"<b>{cid2cname[i]}</b>\"))\n\nfig.update_xaxes(categoryorder=\"total descending\")\nfig.update_layout(title=\"Distribution of Classes Annotation per Radiologist\",\n                  barmode='stack',\n                  xaxis_title=\"Radiologist ID\",\n                  yaxis_title=\"Number of Annotations\",\n                  )\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from math import ceil\nimport matplotlib.patches\nimport matplotlib.gridspec as gridspec\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def example(fclass, ex_num, path=DataDir+'train'):\n    image_list = train[train['class_id']==fclass][0:ex_num]['image_id'].values\n    image_index = train[train['class_id']==fclass][0:ex_num]['image_id'].index.values\n    rows = int(ceil(ex_num / 4))\n    gs = gridspec.GridSpec(rows, 4)\n    fig = plt.figure(figsize=(30, 20))\n    fig.suptitle(f\"{cid2cname[fclass]}\", fontsize=30)\n    for i, (name, image_index) in enumerate(zip(image_list, image_index)):\n        ax = fig.add_subplot(gs[i])\n        ax.set_title(train.loc[i, 'image_id'])\n        ax.axis(\"off\")\n        data = dicom2png(f\"{path}/{name}.dicom\")\n        ax.imshow(data, cmap='gray')\n        if cid2cname[fclass] != 'No finding':\n            bbox = [train.loc[image_index, 'x_min'],\n                    train.loc[image_index, 'y_min'],\n                    train.loc[image_index, 'x_max'],\n                    train.loc[image_index, 'y_max']]\n            p = matplotlib.patches.Rectangle((bbox[0], bbox[1]),\n                                             bbox[2]-bbox[0],\n                                             bbox[3]-bbox[1],\n                                             ec='r', fc='none', lw=2.)\n            ax.add_patch(p)\n    fig.tight_layout()\n    plt.subplots_adjust(hspace=0.1, wspace=0.1, top=0.95)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"example(0,4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### =============================="},{"metadata":{"trusted":true},"cell_type":"code","source":"example(4,4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### More to come, stay tuned ... Kindly, upvote (^_^)"},{"metadata":{},"cell_type":"markdown","source":"### ============================\n### Credit\nI foundsome of the functions here in many notebooks without attribution, however, I benefited from notebooks by: @raddar, @trungthanhnguyen0502, and @dschuttler8845.\n### ============================="}],"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":4,"nbformat_minor":4}