{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":24800,"datasetId":1042002,"databundleVersionId":1831594}],"dockerImageVersionId":30042,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<center><h1 style=\"color:darkblue\">VinBigData Chest X-ray Abnormalities Detection</h1></center>\n<center><h1 style=\"color:brown\">Automatically localize and classify thoracic abnormalities from chest radiographs</h1></center>","metadata":{}},{"cell_type":"markdown","source":"# 1. About the Competition","metadata":{}},{"cell_type":"markdown","source":"When you have a broken arm, radiologists help save the day—and the bone. These doctors diagnose and treat medical conditions using imaging techniques like CT and PET scans, MRIs, and, of course, X-rays. Yet, as it happens when working with such a wide variety of medical tools, radiologists face many daily challenges, perhaps the most difficult being the chest radiograph. <span style=\"color:orange\">The interpretation of chest X-rays can lead to medical misdiagnosis</span>, even for the best practicing doctor. Computer-aided detection and diagnosis systems (CADe/CADx) would help reduce the pressure on doctors at metropolitan hospitals and improve diagnostic quality in rural areas.\n\nExisting methods of interpreting chest X-ray images classify them into a list of findings. ***There is currently no specification of their locations on the image which sometimes leads to inexplicable results***. A solution for <span style=\"color:brown\">localizing findings on chest X-ray images</span> is needed for providing doctors with more meaningful diagnostic assistance.\n\nIn this competition:\n- **Task**: Automatically localize and classify <span style=\"color:blue\">14 types of thoracic abnormalities</span> from chest radiographs. \n- **Dataset**: Consisting of <span style=\"color:blue\">18,000 scans</span>: <span style=\"color:blue\">15,000 train images</span> and will be evaluated on a test set of <span style=\"color:blue\">3,000 images</span>. \n\n- These annotations were collected via VinBigData's web-based platform, VinLab. Details on building the dataset can be found in the organizer's recent paper [“VinDr-CXR: An open dataset of chest X-rays with radiologist's annotations”](https://storage.googleapis.com/kaggle-media/competitions/VinBigData/VinDr_CXR_data_paper.pdf).","metadata":{}},{"cell_type":"markdown","source":"# 2. Data","metadata":{}},{"cell_type":"markdown","source":"# 2.1 Intro\n\nIn this competition, we are classifying common thoracic lung diseases and localizing critical findings. This is **an object detection and classification** problem.\n\n> For each test image, you will be predicting a bounding box and class for all findings. If you predict that there are no findings, you should create a prediction of \"14 1 0 0 1 1\" (14 is the class ID for no finding, and this provides a one-pixel bounding box with a confidence of 1.0).\n\nThe images are in DICOM format, which means they contain additional data that might be useful for visualizing and classifying.\n\n# 2.2 Dataset information\n\nThe dataset comprises 18,000 postero-anterior (PA) CXR scans in DICOM format, which were de-identified to protect patient privacy. All images were labeled by a panel of experienced radiologists for the presence of 14 critical radiographic findings as listed below:\n\n|class_id|class_name|\n|---|---|\n|0 | Aortic enlargement|\n|1 | Atelectasis|\n|2 | Calcification|\n|3 | Cardiomegaly|\n|4 | Consolidation|\n|5| ILD|\n|6 | Infiltration|\n|7 | Lung Opacity|\n|8 | Nodule/Mass|\n|9 | Other lesion|\n|10 | Pleural effusion|\n|11 | Pleural thickening|\n|12 | Pneumothorax|\n|13 | Pulmonary fibrosis|\n|14|No Finding|\n\n> The \"No finding\" observation (14) was intended to capture the absence of all findings above.\n\n***Note that a key part of this competition is working with ground truth from multiple radiologists.***","metadata":{}},{"cell_type":"markdown","source":"# 3. EDA","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport time\nimport random\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as ptc\n\nfrom functools import partial\nimport multiprocessing as mpc\nfrom joblib import Parallel, delayed\n\nimport pydicom as pdc\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n%matplotlib inline","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","execution":{"iopub.status.busy":"2023-12-15T15:38:04.683803Z","iopub.execute_input":"2023-12-15T15:38:04.684539Z","iopub.status.idle":"2023-12-15T15:38:06.130383Z","shell.execute_reply.started":"2023-12-15T15:38:04.684466Z","shell.execute_reply":"2023-12-15T15:38:06.128985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dir = \"../input/vinbigdata-chest-xray-abnormalities-detection/train\"\ntest_dir = \"../input/vinbigdata-chest-xray-abnormalities-detection/test\"\n\ntrain_files = os.listdir(train_dir)\ntest_files = os.listdir(test_dir)\n\ntrain_df = pd.read_csv(\"../input/vinbigdata-chest-xray-abnormalities-detection/train.csv\")\nsample_submission = pd.read_csv(\"../input/vinbigdata-chest-xray-abnormalities-detection/sample_submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:06.133577Z","iopub.execute_input":"2023-12-15T15:38:06.134132Z","iopub.status.idle":"2023-12-15T15:38:08.779349Z","shell.execute_reply.started":"2023-12-15T15:38:06.134077Z","shell.execute_reply":"2023-12-15T15:38:08.777877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3.1 Train.csv","metadata":{}},{"cell_type":"markdown","source":"The train set metadata, with one row for each object, including a class and a bounding box. Some images in both test and train have multiple objects.\n\n## Columns\n\n- **image_id** - unique image identifier\n- **class_name** - the name of the class of detected object (or \"No finding\")\n- **class_id** - the ID of the class of detected object\n- **rad_id** - the ID of the radiologist that made the observation\n- **x_min** - minimum X coordinate of the object's bounding box\n- **y_min** - minimum Y coordinate of the object's bounding box\n- **x_max** - maximum X coordinate of the object's bounding box\n- **y_max** - maximum Y coordinate of the object's bounding box","metadata":{}},{"cell_type":"code","source":"train_df","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:08.7816Z","iopub.execute_input":"2023-12-15T15:38:08.782142Z","iopub.status.idle":"2023-12-15T15:38:08.825109Z","shell.execute_reply.started":"2023-12-15T15:38:08.782087Z","shell.execute_reply":"2023-12-15T15:38:08.823791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.isna().sum().to_frame().rename(columns={0:\"Nan_counts\"}).style.background_gradient(cmap=\"cool\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:08.826741Z","iopub.execute_input":"2023-12-15T15:38:08.827139Z","iopub.status.idle":"2023-12-15T15:38:08.992997Z","shell.execute_reply.started":"2023-12-15T15:38:08.827101Z","shell.execute_reply":"2023-12-15T15:38:08.991708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.nunique().to_frame().rename(columns={0:\"Unique Values\"}).style.background_gradient(cmap=\"plasma\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:08.997758Z","iopub.execute_input":"2023-12-15T15:38:08.998171Z","iopub.status.idle":"2023-12-15T15:38:09.064165Z","shell.execute_reply.started":"2023-12-15T15:38:08.998129Z","shell.execute_reply":"2023-12-15T15:38:09.063123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(26, 8))\nsns.countplot(x=\"class_name\", data=train_df)\nplt.title(\"Class Name Distribution\")\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:09.066442Z","iopub.execute_input":"2023-12-15T15:38:09.066837Z","iopub.status.idle":"2023-12-15T15:38:09.445288Z","shell.execute_reply.started":"2023-12-15T15:38:09.066801Z","shell.execute_reply":"2023-12-15T15:38:09.443797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(8, 8))\nsns.countplot(x=\"class_id\", data=train_df)\nplt.title(\"Class ID Distribution\")\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:09.447242Z","iopub.execute_input":"2023-12-15T15:38:09.447827Z","iopub.status.idle":"2023-12-15T15:38:09.700123Z","shell.execute_reply.started":"2023-12-15T15:38:09.447643Z","shell.execute_reply":"2023-12-15T15:38:09.698796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(8, 8))\nsns.countplot(x=\"rad_id\", data=train_df)\nplt.title(\"RAD ID Distribution\")\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:09.701754Z","iopub.execute_input":"2023-12-15T15:38:09.70212Z","iopub.status.idle":"2023-12-15T15:38:09.991099Z","shell.execute_reply.started":"2023-12-15T15:38:09.702082Z","shell.execute_reply":"2023-12-15T15:38:09.99011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(10, 10))\nsns.pairplot(train_df, hue='class_id')\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:09.992332Z","iopub.execute_input":"2023-12-15T15:38:09.992656Z","iopub.status.idle":"2023-12-15T15:38:24.181193Z","shell.execute_reply.started":"2023-12-15T15:38:09.992611Z","shell.execute_reply":"2023-12-15T15:38:24.180014Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. DICOM Exploration","metadata":{}},{"cell_type":"code","source":"print(\"Number of train images: \", len(train_files))\nprint(\"Number of test images: \", len(test_files))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:24.182799Z","iopub.execute_input":"2023-12-15T15:38:24.183212Z","iopub.status.idle":"2023-12-15T15:38:24.190822Z","shell.execute_reply.started":"2023-12-15T15:38:24.183173Z","shell.execute_reply":"2023-12-15T15:38:24.189791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Okay, as described, we have **15000** images for train and **3000** images for test. But, the length of train_df is **67914**. So, are there any duplicacy or what? Let's have a sanity check on unique image_ids of train_df.","metadata":{}},{"cell_type":"code","source":"print(\"Unique images in train_df: \", train_df.image_id.nunique())","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:24.192135Z","iopub.execute_input":"2023-12-15T15:38:24.192585Z","iopub.status.idle":"2023-12-15T15:38:24.217811Z","shell.execute_reply.started":"2023-12-15T15:38:24.192532Z","shell.execute_reply":"2023-12-15T15:38:24.216717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So, it's confirmed that we have one or more than one ground truths in our train dataset. Now, we got a lot to explore, such as:\n- Whether the bboxes are overlapped on a single image?\n- What about different classes? Are they over-lapped too for different classes?\n- In a single image, what's the maximum number of class (or taregt present)? etc.\n\nWe will continue to explore these aspects and try to come with some insights, but let's worry about DICOM at the first place. Let's read out some image file names from both the train and test folders.","metadata":{}},{"cell_type":"code","source":"for _ in range(3):\n    print(train_files[random.randint(0, len(train_files))])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-12-15T15:38:24.219244Z","iopub.execute_input":"2023-12-15T15:38:24.219625Z","iopub.status.idle":"2023-12-15T15:38:24.225844Z","shell.execute_reply.started":"2023-12-15T15:38:24.219588Z","shell.execute_reply":"2023-12-15T15:38:24.224964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4.1. But What is [DICOM](https://en.wikipedia.org/wiki/DICOM)?\n\nWhat wiki says is: \n\n> Digital Imaging and Communications in Medicine (DICOM) is the standard for the communication and management of medical imaging information and related data. DICOM is most commonly used for storing and transmitting medical images enabling the integration of medical imaging devices such as scanners, servers, workstations, printers, network hardware, and picture archiving and communication systems (PACS) from multiple manufacturers. It has been widely adopted by hospitals and is making inroads into smaller applications like dentists' and doctors' offices.\n\nWhat else?\n\n- DICOM files can be exchanged between two entities that are capable of receiving <span style=\"color:red\">image</span> and <span style=\"color:orange\">patient data</span> in **DICOM format**. \n    \n- The different devices come with DICOM Conformance Statements which state which DICOM classes they support. The standard includes a file format definition and a network communications protocol that uses TCP/IP to communicate between systems.","metadata":{}},{"cell_type":"markdown","source":"Let's read one sample and grind it.","metadata":{}},{"cell_type":"code","source":"sample_fn = train_df.image_id.to_list()[random.randint(0, len(train_df))]\nsample = pdc.read_file(os.path.join(train_dir, sample_fn + \".dicom\"))\nsample","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:24.227278Z","iopub.execute_input":"2023-12-15T15:38:24.227622Z","iopub.status.idle":"2023-12-15T15:38:24.558953Z","shell.execute_reply.started":"2023-12-15T15:38:24.227587Z","shell.execute_reply":"2023-12-15T15:38:24.558045Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So, for a single patient(yes, there is one-one mapping in patients and image_ids, so there is no duplicates or more than one images per patient, as confirmed by the organizers in the discussion forum), we have following entities:\n\n- Patient's Sex                      \n- Samples per Pixel                 \n- Photometric Interpretation         \n- Rows                               \n- Columns                          \n- Pixel Spacing                      \n- Bits Allocated                      \n- Bits Stored                         \n- High Bit                            \n- Pixel Representation             \n- Window Center                       \n- Window Width                      \n- Rescale Intercept                   \n- Rescale Slope                       \n- Lossy Image Compression             \n- Pixel Data                         \n\n*NOTE: Do let me know about the extensions in the comment section, untill I update it myself in the next version :)*","metadata":{}},{"cell_type":"code","source":"def read_dicom_df(fn):\n    _ = pdc.read_file(os.path.join(train_dir, fn))\n    pass\n\n#_ = Parallel(-1, verbose=1)(delayed(read_dicom_df)(x) for x in train_files)","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:24.560381Z","iopub.execute_input":"2023-12-15T15:38:24.560995Z","iopub.status.idle":"2023-12-15T15:38:24.566281Z","shell.execute_reply.started":"2023-12-15T15:38:24.560953Z","shell.execute_reply":"2023-12-15T15:38:24.565313Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. Images(Extension from Dicom)","metadata":{}},{"cell_type":"markdown","source":"Update 1: Ref: https://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way by @raddar","metadata":{}},{"cell_type":"markdown","source":"# 5.1 Visualising Images","metadata":{}},{"cell_type":"code","source":"def plot_pixel_array(data, figsize=(10,10)):\n    plt.figure(figsize=figsize)\n    plt.imshow(data, cmap=plt.cm.bone)\n    plt.show()\n\n\n# ref kernel: https://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way\ndef read_xray(path, voi_lut=True, fix_monochrome=True):\n    dcm_data = pdc.read_file(path)\n    \n    def show_dcm_info(data):\n        print(\"Gender :\", data.PatientSex)\n        if 'PixelData' in data:\n            rows = int(data.Rows)\n            cols = int(data.Columns)\n            print(\"Image size : {rows:d} x {cols:d}, {size:d} bytes\".format(\n                rows=rows, cols=cols, size=len(data.PixelData)))\n            if 'PixelSpacing' in data:\n                print(\"Pixel spacing :\", data.PixelSpacing)\n    \n    show_dcm_info(dcm_data)\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(dcm_data.pixel_array, dcm_data)\n    else:\n        data = dcm_data.pixel_array\n               \n    # depending on this value, X-ray may look inverted - fix that:\n    if fix_monochrome and dcm_data.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","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:24.567588Z","iopub.execute_input":"2023-12-15T15:38:24.568082Z","iopub.status.idle":"2023-12-15T15:38:24.584175Z","shell.execute_reply.started":"2023-12-15T15:38:24.568046Z","shell.execute_reply":"2023-12-15T15:38:24.583109Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Examining train images...\")\nfor _ in range(5):\n    fn = train_files[np.random.randint(0, len(train_files))]\n    file_path = os.path.join(train_dir, fn)\n    data = read_xray(file_path)\n    plot_pixel_array(data)","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:24.585512Z","iopub.execute_input":"2023-12-15T15:38:24.58584Z","iopub.status.idle":"2023-12-15T15:38:37.927129Z","shell.execute_reply.started":"2023-12-15T15:38:24.585808Z","shell.execute_reply":"2023-12-15T15:38:37.92577Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Examining test images...\")\nfor _ in range(5):\n    fn = test_files[np.random.randint(0, len(test_files))]\n    file_path = os.path.join(test_dir, fn)\n    data = read_xray(file_path)\n    plot_pixel_array(data)","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:37.928807Z","iopub.execute_input":"2023-12-15T15:38:37.92926Z","iopub.status.idle":"2023-12-15T15:38:51.109533Z","shell.execute_reply.started":"2023-12-15T15:38:37.929215Z","shell.execute_reply":"2023-12-15T15:38:51.108185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5.2 Examine the localizations","metadata":{}},{"cell_type":"code","source":"for _ in range(10):\n    idx = np.random.randint(0, len(train_files))\n    img_id = train_df.loc[idx, 'image_id']\n    img = read_xray(os.path.join(train_dir, img_id+\".dicom\"))\n    plt.figure(figsize=(8, 14))\n    plt.imshow(img, cmap='gray')\n    plt.title(train_df.loc[idx, 'class_name'])\n    \n    if train_df.loc[idx, 'class_name'] != 'No finding':\n        bbox = [train_df.loc[idx, 'x_min'],\n                train_df.loc[idx, 'y_min'],\n                train_df.loc[idx, 'x_max'],\n                train_df.loc[idx, 'y_max']]\n        \n        patch = ptc.Rectangle((bbox[0], bbox[1]),\n                              bbox[2]-bbox[0],\n                              bbox[3]-bbox[1],\n                              ec='r', fc='none', lw=2.)\n        ax = plt.gca()\n        ax.add_patch(patch)","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:38:51.111246Z","iopub.execute_input":"2023-12-15T15:38:51.111609Z","iopub.status.idle":"2023-12-15T15:39:04.754128Z","shell.execute_reply.started":"2023-12-15T15:38:51.111573Z","shell.execute_reply":"2023-12-15T15:39:04.752733Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 6. Sample Submission","metadata":{}},{"cell_type":"code","source":"sample_submission","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:39:04.755924Z","iopub.execute_input":"2023-12-15T15:39:04.756311Z","iopub.status.idle":"2023-12-15T15:39:04.772869Z","shell.execute_reply.started":"2023-12-15T15:39:04.756274Z","shell.execute_reply":"2023-12-15T15:39:04.771515Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_submission.to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2023-12-15T15:39:04.774267Z","iopub.execute_input":"2023-12-15T15:39:04.774601Z","iopub.status.idle":"2023-12-15T15:39:05.398472Z","shell.execute_reply.started":"2023-12-15T15:39:04.774568Z","shell.execute_reply":"2023-12-15T15:39:05.396885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Thanks for reading till the end, consider upvoting the kernel, if you enjoyed reading it ;)","metadata":{"_kg_hide-input":false}}]}