{"cells":[{"metadata":{},"cell_type":"markdown","source":"![Chest XRAY](https://media.springernature.com/lw685/springer-static/image/art%3A10.1186%2Fs12890-020-01286-5/MediaObjects/12890_2020_1286_Fig1_HTML.png)"},{"metadata":{},"cell_type":"markdown","source":"# Competition Aim\nLocalize and classify 14 types of thoracic abnormalities from chest radiographs\n## This is an Object Detection Competition"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import os\nimport cv2\nimport time\nimport numpy as np\nimport pandas as pd\nimport random\n\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nimport plotly.graph_objects as go\nimport plotly.express as px\nimport plotly.figure_factory as ff\n\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nfrom collections import Counter\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\npd.set_option('display.max_rows', None)\npd.set_option('display.max_columns', None)\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ROOT_DIR = \"../input/vinbigdata-chest-xray-abnormalities-detection\"\nTRAIN_DIR = \"../input/vinbigdata-chest-xray-abnormalities-detection/train\"\nTEST_DIR = \"../input/vinbigdata-chest-xray-abnormalities-detection/test\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(os.path.join(ROOT_DIR, \"train.csv\"))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Shape of the Dataframe: {df.shape}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Number of unique images in the training dataset: {df['image_id'].nunique()}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_images = os.listdir(TEST_DIR)\nprint(f\"Number of unique images in the training dataset: {len(test_images)}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"The dataset consists of observations made by {df['rad_id'].nunique()} radiologists\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"rad_df = df['rad_id'].value_counts().reset_index()\nfig = go.Figure(data=[go.Table(header=dict(values=['Radiologist ID', 'Number of Observations'], fill_color='yellow'),\n                 cells=dict(values=[rad_df['index'], rad_df['rad_id']], fill_color='lavender'))\n                     ])\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cnt = Counter(df['image_id'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"cnt","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are more than 1 box in an image so it is an Object Detection Competition"},{"metadata":{},"cell_type":"markdown","source":"# Maximum number of Boxes in an Image"},{"metadata":{},"cell_type":"markdown","source":"Let's get the image_id of the image with maximum bounding boxes"},{"metadata":{"trusted":true},"cell_type":"code","source":"max_boxes_image = max(cnt, key=cnt.get)\nprint(f\"Image ID of image with with maximum boxes is: \\'{max_boxes_image}\\' and number of boxes is {cnt[max_boxes_image]}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Let's Visualize this image\nAll the images are in **DICOM** format"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Code taken from https://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way\n\ndef read_xray(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":"def show_dicom(image_id, root_dir=TRAIN_DIR):\n    image_path = os.path.join(root_dir, image_id+\".dicom\")    \n    img = read_xray(image_path)\n    plt.figure(figsize = (12,12))\n    plt.imshow(img, 'gray')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_all_bboxes(df, image_id):\n    image_bboxes = df[df.image_id == image_id]\n    bboxes = []\n    for _,row in image_bboxes.iterrows():\n        bboxes.append((row.x_min, row.y_min, row.x_max, row.y_max))  \n    return bboxes\n\ndef plot_single_image(image_id, df):\n    show_dicom(image_id)\n    bboxes = get_all_bboxes(df, image_id)\n    for i, bbox in enumerate(bboxes): \n        patch = patches.Rectangle((bbox[0], bbox[1]), bbox[2]-bbox[0], bbox[3]-bbox[1],\n                                    edgecolor='r',linewidth=1., facecolor='none')\n        ax = plt.gca()\n        ax.add_patch(patch)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_max = df[df['image_id'] == max_boxes_image].copy()\ndf_max = df_max.reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_single_image(max_boxes_image, df_max)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### There are many Bounding Boxes with very high overlap\n### Let's investigate further"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_max.rad_id.value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### We have 3 different annotators which maybe the reason of so many boxes having very high IoU"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Code taken from https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/\n\ndef bb_intersection_over_union(boxA, boxB):\n    # determine the (x, y)-coordinates of the intersection rectangle\n    xA = max(boxA[0], boxB[0])\n    yA = max(boxA[1], boxB[1])\n    xB = min(boxA[2], boxB[2])\n    yB = min(boxA[3], boxB[3])\n    # compute the area of intersection rectangle\n    interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)\n    # compute the area of both the prediction and ground-truth\n    # rectangles\n    boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)\n    boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)\n    # compute the intersection over union by taking the intersection\n    # area and dividing it by the sum of prediction + ground-truth\n    # areas - the interesection area\n    iou = interArea / float(boxAArea + boxBArea - interArea)\n    # return the intersection over union value\n    return iou","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for i in range(len(df_max)):\n    for j in range(i+1, len(df_max)):\n        boxA = df_max.iloc[i, 4:].tolist()\n        boxB = df_max.iloc[j, 4:].tolist()\n        iou = bb_intersection_over_union(boxA, boxB)\n        if iou >= 0.5:\n            print(iou)\n            print(df_max.iloc[i, 1:4])\n            print(df_max.iloc[j, 1:4])\n            print(\"-\"*25, end='\\n')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## There are 18 combinations of boxes with IoU Score above 0.5\n## Most of them correspond to same class but have different annotators\n## But there are 2 instances where **\"ILD\"** class has very high IoU with **\"Nodule/Mass\"** "},{"metadata":{},"cell_type":"markdown","source":"# Class Distribution"},{"metadata":{"trusted":true},"cell_type":"code","source":"classes = [\"Aortic enlargement\", \"Atelectasis\", \"Calcification\", \"Cardiomegaly\", \"Consolidation\", \"ILD\", \"Infiltration\", \"Lung Opacity\",\n           \"Nodule/Mass\", \"Other lesion\", \"Pleural effusion\", \"Pleural thickening\", \"Pneumothorax\", \"Pulmonary fibrosis\", \"No finding\"]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = go.Figure(data=[go.Table(header=dict(values=['Class ID', 'Class Name'], fill_color='yellow'),\n                 cells=dict(values=[list(range(15)), classes], fill_color='lavender'))\n                     ])\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"temp_df = df[\"class_id\"].value_counts().sort_index()\n\ntrace1 = go.Bar(\n                x = classes,\n                y = temp_df.tolist(),\n                marker = dict(color = 'rgb(127, 16, 238)',\n                              line=dict(color='rgb(0,0,0)',width=1.5)),\n                text=temp_df.tolist(), textposition='outside')\nlayout = go.Layout(template= \"plotly_dark\",title = 'Number of classes' , xaxis = dict(title = 'Class'), yaxis = dict(title = 'Count'))\nfig = go.Figure(data = [trace1], layout = layout)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Bounding Box Area"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_findings = df[df[\"class_id\"] != 14].copy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_findings['bbox_area'] = (df_findings[\"x_max\"] - df_findings[\"x_min\"]) * (df_findings[\"y_max\"] - df_findings[\"y_min\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Total number of bounding boxes present in the dataset is {len(df_findings)}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Maximum area of bounding box is {df_findings['bbox_area'].max()}\")\nprint(f\"Minimum area of bounding box is {df_findings['bbox_area'].min()}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"trace = go.Histogram(\n    x=df_findings['bbox_area'],\n    name = \"Bounding Box Area\",\n    xbins=dict(size=30000),\n    marker=dict(color='rgb(12, 50, 196)'))\nlayout = go.Layout(template= \"plotly_dark\",title = 'Bounding Box Area' , xaxis = dict(title = 'Area'), yaxis = dict(title = 'Count'))\nfig = go.Figure(data = [trace], layout = layout)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Visualizing Images Classwise"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_findings.drop('class_id',axis=1).groupby('class_name').agg(['min', 'max', 'mean','median'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* **Nodule/Mass** has the smallest bounding boxes\n* **Pneumothorax** and **ILD** have the largest bounding boxes"},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_k_images(df, k=3):\n    image_ids = random.choices(df['image_id'].tolist(), k=3)\n    for image_id in image_ids:\n        plot_single_image(image_id, df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Aortic Enlargement"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_0 = df[df.class_id == 0]\nplot_k_images(df_0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Atelectasis"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_1 = df[df.class_id == 1]\nplot_k_images(df_1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Calcification"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_2 = df[df.class_id == 2]\nplot_k_images(df_2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Cardiomegaly"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_3 = df[df.class_id == 3]\nplot_k_images(df_3)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Consolidation"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_4 = df[df.class_id == 4]\nplot_k_images(df_4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# ILD"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_5 = df[df.class_id == 5]\nplot_k_images(df_5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Infiltration"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_6 = df[df.class_id == 6]\nplot_k_images(df_6)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Lung Opacity"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_7 = df[df.class_id == 7]\nplot_k_images(df_7)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Nodule/Mass"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_8 = df[df.class_id == 8]\nplot_k_images(df_8)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Other lesion"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_9 = df[df.class_id == 9]\nplot_k_images(df_9)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Pleural Effusion"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_10 = df[df.class_id == 10]\nplot_k_images(df_10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Pleural thickening"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_11 = df[df.class_id == 11]\nplot_k_images(df_11)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Pneumothorax"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_12 = df[df.class_id == 12]\nplot_k_images(df_12)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Pulmonary Fibrosis"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"df_13 = df[df.class_id == 13]\nplot_k_images(df_13)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## If you like this kernel, please leave an upvote :)"},{"metadata":{},"cell_type":"markdown","source":"## Work in Progress "}],"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}