{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":26680,"databundleVersionId":2283525,"sourceType":"competition"}],"dockerImageVersionId":30096,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# COVID-19 Detection from Chest X-Ray Images: \n\nwe are provided with images of chest X-ray radiographs, and we are asked to identify and localize COVID-19 abnormalities. This is important because typical diagnosis of COVID-19 requires molecular testing (polymerase chain reaction) requires several hours, while chest radiographs can be obtained in minutes, but it is hard to distinguish between COVID-19 pneumonia and other viral and bacterial pneumonias. Therefore, we hope to develop AI that eventually help radiologists diagnose the millions of COVID-19 patients more confidently and quickly.","metadata":{}},{"cell_type":"markdown","source":"# Imports\nLet's start out by setting up our environment by importing the required modules:","metadata":{}},{"cell_type":"code","source":"! conda install -c conda-forge gdcm -y","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-06-09T07:17:28.954947Z","iopub.execute_input":"2024-06-09T07:17:28.955386Z","iopub.status.idle":"2024-06-09T07:56:40.238229Z","shell.execute_reply.started":"2024-06-09T07:17:28.955313Z","shell.execute_reply":"2024-06-09T07:56:40.236688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport pydicom\nimport glob\nfrom tqdm.notebook import tqdm\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport matplotlib.pyplot as plt\nfrom skimage import exposure\nimport cv2\nimport warnings\nfrom fastai.vision.all import *\nfrom fastai.medical.imaging import *\nwarnings.filterwarnings('ignore')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:40.240525Z","iopub.execute_input":"2024-06-09T07:56:40.240967Z","iopub.status.idle":"2024-06-09T07:56:44.223469Z","shell.execute_reply.started":"2024-06-09T07:56:40.240915Z","shell.execute_reply":"2024-06-09T07:56:44.222297Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# A look at the provided data","metadata":{}},{"cell_type":"markdown","source":"Let's check what data is available to us:","metadata":{}},{"cell_type":"code","source":"dataset_path = Path('../input/siim-covid19-detection')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-06-09T07:56:44.225769Z","iopub.execute_input":"2024-06-09T07:56:44.226122Z","iopub.status.idle":"2024-06-09T07:56:44.230993Z","shell.execute_reply.started":"2024-06-09T07:56:44.226086Z","shell.execute_reply":"2024-06-09T07:56:44.229777Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset_path.ls()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.233286Z","iopub.execute_input":"2024-06-09T07:56:44.233797Z","iopub.status.idle":"2024-06-09T07:56:44.252249Z","shell.execute_reply.started":"2024-06-09T07:56:44.233747Z","shell.execute_reply":"2024-06-09T07:56:44.250974Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see that we have:\n\n* `train_study_level.csv` - the train study-level metadata, with one row for each study, including correct labels.\n* `train_image_level.csv` - the train image-level metadata, with one row for each image, including both correct labels and any bounding boxes in a dictionary format. Some images in both test and train have multiple bounding boxes.\n* `sample_submission.csv` - a sample submission file containing all image- and study-level IDs.\n* `train` folder - comprises 6,334 chest scans in DICOM format, stored in paths with the form `study`/`series`/`image`\n* `test` folder - The hidden test dataset is of roughly the same scale as the training dataset.\n","metadata":{}},{"cell_type":"markdown","source":"# A look at the CSVs\n\nLet's check the `train_study_level.csv` file:","metadata":{}},{"cell_type":"code","source":"train_study_df = pd.read_csv(dataset_path/'train_study_level.csv')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.253965Z","iopub.execute_input":"2024-06-09T07:56:44.254356Z","iopub.status.idle":"2024-06-09T07:56:44.285530Z","shell.execute_reply.started":"2024-06-09T07:56:44.254316Z","shell.execute_reply":"2024-06-09T07:56:44.284403Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_study_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.287003Z","iopub.execute_input":"2024-06-09T07:56:44.287363Z","iopub.status.idle":"2024-06-09T07:56:44.314375Z","shell.execute_reply.started":"2024-06-09T07:56:44.287310Z","shell.execute_reply":"2024-06-09T07:56:44.313321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's look at the unique labels:","metadata":{}},{"cell_type":"code","source":"study_classes = ['Negative for Pneumonia', 'Typical Appearance', 'Indeterminate Appearance', 'Atypical Appearance']\nnp.unique(train_study_df[study_classes].values, axis=0)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.315728Z","iopub.execute_input":"2024-06-09T07:56:44.316050Z","iopub.status.idle":"2024-06-09T07:56:44.343536Z","shell.execute_reply.started":"2024-06-09T07:56:44.316018Z","shell.execute_reply":"2024-06-09T07:56:44.342124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As you can see, at the study-level, we are predicting the following classes:\n* Negative for Pneumonia\n* Typical Appearance\n* Indeterminate Appearance\n* Atypical Appearance\n\nThis here is a standard multi-label classification problem. In the training set, interestingly they are not multi-label, but it is mentioned that:\n> Studies in the test set may contain more than one label.\n\nLet's look at the distribution:\n","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize = (10,5))\nplt.bar([1,2,3,4], train_study_df[study_classes].values.sum(axis=0))\nplt.xticks([1,2,3,4],study_classes)\nplt.ylabel('Frequency')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.345312Z","iopub.execute_input":"2024-06-09T07:56:44.345745Z","iopub.status.idle":"2024-06-09T07:56:44.542823Z","shell.execute_reply.started":"2024-06-09T07:56:44.345704Z","shell.execute_reply":"2024-06-09T07:56:44.541593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's now look at `train_image_level.csv`:","metadata":{}},{"cell_type":"code","source":"train_image_df = pd.read_csv(dataset_path/'train_image_level.csv')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.547047Z","iopub.execute_input":"2024-06-09T07:56:44.547585Z","iopub.status.idle":"2024-06-09T07:56:44.602224Z","shell.execute_reply.started":"2024-06-09T07:56:44.547542Z","shell.execute_reply":"2024-06-09T07:56:44.601035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_image_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.604400Z","iopub.execute_input":"2024-06-09T07:56:44.604864Z","iopub.status.idle":"2024-06-09T07:56:44.620021Z","shell.execute_reply.started":"2024-06-09T07:56:44.604823Z","shell.execute_reply":"2024-06-09T07:56:44.618613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We have our bounding box labels provided in the `label` column. The format is as follows:\n\n`[class ID] [confidence score] [bounding box]`\n\n* class ID - either `opacity` or `none`\n* confidence score - confidence from your neural network model. If none, the confidence is `1`.\n* bounding box - typical `xmin ymin xmax ymax` format. If class ID is none, the bounding box is `1 0 0 1 1`.\n\nThe bounding boxes are also provided in easily readable dictionary format in column `boxes`, and the study that each image is a part of is provided in`StudyInstanceUID`.\n\nLet's quick look at the distribution of opacity vs none:","metadata":{}},{"cell_type":"code","source":"train_image_df['split_label'] = train_image_df.label.apply(lambda x: [x.split()[offs:offs+6] for offs in range(0, len(x.split()), 6)])","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.621865Z","iopub.execute_input":"2024-06-09T07:56:44.622320Z","iopub.status.idle":"2024-06-09T07:56:44.664043Z","shell.execute_reply.started":"2024-06-09T07:56:44.622279Z","shell.execute_reply":"2024-06-09T07:56:44.662296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"classes_freq = []\nfor i in range(len(train_image_df)):\n    for j in train_image_df.iloc[i].split_label: classes_freq.append(j[0])\nplt.hist(classes_freq)\nplt.ylabel('Frequency')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:44.665685Z","iopub.execute_input":"2024-06-09T07:56:44.666114Z","iopub.status.idle":"2024-06-09T07:56:45.770139Z","shell.execute_reply.started":"2024-06-09T07:56:44.666074Z","shell.execute_reply":"2024-06-09T07:56:45.768842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's also look at the distribution of the bounding box areas:","metadata":{}},{"cell_type":"code","source":"bbox_areas = []\nfor i in range(len(train_image_df)):\n    for j in train_image_df.iloc[i].split_label:\n        bbox_areas.append((float(j[4])-float(j[2]))*(float(j[5])*float(j[3])))\nplt.hist(bbox_areas)\nplt.ylabel('Frequency')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:45.771657Z","iopub.execute_input":"2024-06-09T07:56:45.772031Z","iopub.status.idle":"2024-06-09T07:56:47.080024Z","shell.execute_reply.started":"2024-06-09T07:56:45.771992Z","shell.execute_reply":"2024-06-09T07:56:47.078803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# A look at the images\n\nOkay, let's now look at some example images:","metadata":{}},{"cell_type":"code","source":"def dicom2array(path, voi_lut=True, fix_monochrome=True):\n    dicom = pydicom.read_file(path)\n    # VOI LUT (if available by DICOM device) is used to\n    # 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    # 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    data = data - np.min(data)\n    data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    return data\n        \n    \ndef plot_img(img, size=(7, 7), 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=7, 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()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:47.081414Z","iopub.execute_input":"2024-06-09T07:56:47.081799Z","iopub.status.idle":"2024-06-09T07:56:47.095152Z","shell.execute_reply.started":"2024-06-09T07:56:47.081763Z","shell.execute_reply":"2024-06-09T07:56:47.093842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dicom_paths = get_dicom_files(dataset_path/'train')\nimgs = [dicom2array(path) for path in dicom_paths[:4]]\nplot_imgs(imgs)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:56:47.096978Z","iopub.execute_input":"2024-06-09T07:56:47.097499Z","iopub.status.idle":"2024-06-09T07:58:15.333240Z","shell.execute_reply.started":"2024-06-09T07:56:47.097424Z","shell.execute_reply":"2024-06-09T07:58:15.332095Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's actually look at how many images are available per study:","metadata":{}},{"cell_type":"code","source":"num_images_per_study = []\nfor i in (dataset_path/'train').ls():\n    num_images_per_study.append(len(get_dicom_files(i)))\n    if len(get_dicom_files(i)) > 5:\n        print(f'Study {i} had {len(get_dicom_files(i))} images')\n    ","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:15.334676Z","iopub.execute_input":"2024-06-09T07:58:15.335030Z","iopub.status.idle":"2024-06-09T07:58:30.630431Z","shell.execute_reply.started":"2024-06-09T07:58:15.334995Z","shell.execute_reply":"2024-06-09T07:58:30.629176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.hist(num_images_per_study)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:30.632099Z","iopub.execute_input":"2024-06-09T07:58:30.632470Z","iopub.status.idle":"2024-06-09T07:58:30.878042Z","shell.execute_reply.started":"2024-06-09T07:58:30.632432Z","shell.execute_reply":"2024-06-09T07:58:30.876867Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def image_path(row):\n    study_path = dataset_path/'train'/row.StudyInstanceUID\n    for i in get_dicom_files(study_path):\n        if row.id.split('_')[0] == i.stem: return i \n        \ntrain_image_df['image_path'] = train_image_df.apply(image_path, axis=1)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:30.879520Z","iopub.execute_input":"2024-06-09T07:58:30.879906Z","iopub.status.idle":"2024-06-09T07:58:41.340058Z","shell.execute_reply.started":"2024-06-09T07:58:30.879868Z","shell.execute_reply":"2024-06-09T07:58:41.338814Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"imgs = []\nimage_paths = train_image_df['image_path'].values\n\n# map label_id to specify color\nthickness = 10\nscale = 5\n\n\nfor i in range(8):\n    image_path = random.choice(image_paths)\n    print(image_path)\n    img = dicom2array(path=image_path)\n    img = cv2.resize(img, None, fx=1/scale, fy=1/scale)\n    img = np.stack([img, img, img], axis=-1)\n    for i in train_image_df.loc[train_image_df['image_path'] == image_path].split_label.values[0]:\n        if i[0] == 'opacity':\n            img = cv2.rectangle(img,\n                                (int(float(i[2])/scale), int(float(i[3])/scale)),\n                                (int(float(i[4])/scale), int(float(i[5])/scale)),\n                                [255,0,0], thickness)\n    \n    img = cv2.resize(img, (500,500))\n    imgs.append(img)\n    \nplot_imgs(imgs, cmap=None)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:41.341639Z","iopub.execute_input":"2024-06-09T07:58:41.342011Z","iopub.status.idle":"2024-06-09T07:58:45.744454Z","shell.execute_reply.started":"2024-06-09T07:58:41.341967Z","shell.execute_reply":"2024-06-09T07:58:45.742408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# How to submit\n\nLet's now go over the `sample_submission.csv` file so we know how to submit our predictions.\n\nBefore we do so, it's worth reminding ourselves that this is a code-only competition, meaning that your submission file has to be generated in a script/notebook. The `sample_submission.csv` file demonstrated what kind of file needs to be produced:","metadata":{}},{"cell_type":"code","source":"submission_df = pd.read_csv(dataset_path/'sample_submission.csv')","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:45.746211Z","iopub.execute_input":"2024-06-09T07:58:45.746699Z","iopub.status.idle":"2024-06-09T07:58:45.766101Z","shell.execute_reply.started":"2024-06-09T07:58:45.746646Z","shell.execute_reply":"2024-06-09T07:58:45.764578Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:45.768030Z","iopub.execute_input":"2024-06-09T07:58:45.768396Z","iopub.status.idle":"2024-06-09T07:58:45.780849Z","shell.execute_reply.started":"2024-06-09T07:58:45.768361Z","shell.execute_reply":"2024-06-09T07:58:45.779261Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see we have to provide the study-level class label. These will be of the format `[class]` `1 0 0 1 1`","metadata":{}},{"cell_type":"code","source":"submission_df.iloc[2000:2010]","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:45.782744Z","iopub.execute_input":"2024-06-09T07:58:45.783262Z","iopub.status.idle":"2024-06-09T07:58:45.800916Z","shell.execute_reply.started":"2024-06-09T07:58:45.783209Z","shell.execute_reply":"2024-06-09T07:58:45.799615Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We also have to provide the image-level bounding box. These will be of the format `[class ID] [confidence score] [bounding box]` as described earlier.\n\nOf course, in both cases, you can have multi-label scenarios.","metadata":{}},{"cell_type":"code","source":"submission_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-06-09T07:58:45.802594Z","iopub.execute_input":"2024-06-09T07:58:45.803081Z","iopub.status.idle":"2024-06-09T07:58:45.825742Z","shell.execute_reply.started":"2024-06-09T07:58:45.803029Z","shell.execute_reply":"2024-06-09T07:58:45.824599Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"That's it for now!\n\n**Please upvote if you found this helpful!**","metadata":{}}]}