{"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":"![GLLogo.png](data:image/png;base64,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)","metadata":{"id":"V9XEdjyejuLn"}},{"cell_type":"markdown","source":"# AIML Online Capstone - Pneumonia Detection Challenge\n\nThe goal is to build a pneumonia detection system, to locate the position of\ninflammation in an image.\n\nTissues with sparse material, such as lungs which are full of air, do not absorb the X-rays and appear\nblack in the image. Dense tissues such as bones absorb X-rays and appear white in the image.\nWhile we are theoretically detecting “lung opacities”, there are lung opacities that are not pneumonia\nrelated.\n\nIn the data, some of these are labeled “Not Normal No Lung Opacity”. This extra third class indicates\nthat while pneumonia was determined not to be present, there was nonetheless some type of\nabnormality on the image and oftentimes this finding may mimic the appearance of true pneumonia.\n\nDicom original images: - Medical images are stored in a special format called DICOM files (*.dcm). They\ncontain a combination of header metadata as well as underlying raw image arrays for pixel data","metadata":{"execution":{"iopub.status.busy":"2022-03-10T17:19:10.772608Z","iopub.execute_input":"2022-03-10T17:19:10.773374Z","iopub.status.idle":"2022-03-10T17:19:16.277864Z","shell.execute_reply.started":"2022-03-10T17:19:10.773275Z","shell.execute_reply":"2022-03-10T17:19:16.277114Z"},"id":"-Z05LguQjrWo"}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom glob import glob\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport seaborn as sns\nimport pydicom as dcm\nimport math\nfrom tensorflow.keras.layers import Layer, Convolution2D, Flatten, Dense\nfrom tensorflow.keras.layers import Concatenate, UpSampling2D, Conv2D, Reshape, GlobalAveragePooling2D\nfrom tensorflow.keras.models import Model\n\nimport cv2\n\nfrom tensorflow.keras.applications.mobilenet import MobileNet\nfrom tensorflow.keras.applications.mobilenet import preprocess_input \n\nimport tensorflow.keras.utils as pltUtil\nfrom tensorflow.keras.utils import Sequence\n\nimport math\n\nfrom tensorflow.keras.applications.resnet import ResNet50\nfrom tensorflow.keras.applications.resnet import preprocess_input as resnetProcess_input","metadata":{"id":"DQOprShejrWz","execution":{"iopub.status.busy":"2022-03-27T03:19:56.666926Z","iopub.execute_input":"2022-03-27T03:19:56.667411Z","iopub.status.idle":"2022-03-27T03:20:03.168999Z","shell.execute_reply.started":"2022-03-27T03:19:56.667295Z","shell.execute_reply":"2022-03-27T03:20:03.168152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## **Reading the Data Set and EDA**","metadata":{"id":"Urev2vSdkFVd"}},{"cell_type":"code","source":"## reading the labels data set and showing first few records\nlabels = pd.read_csv(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv\")\nlabels.head()","metadata":{"id":"mBxbhg2xjrW0","outputId":"2e2cd3f6-50eb-4f14-f660-a68309179faf","execution":{"iopub.status.busy":"2022-03-27T03:20:10.568077Z","iopub.execute_input":"2022-03-27T03:20:10.568637Z","iopub.status.idle":"2022-03-27T03:20:10.659799Z","shell.execute_reply.started":"2022-03-27T03:20:10.568596Z","shell.execute_reply":"2022-03-27T03:20:10.658965Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels.shape\n## There are 30227 records and 6 rows","metadata":{"id":"AiP4cWGNjrW1","outputId":"975db9c8-be57-4554-c946-5792034bb7d1","execution":{"iopub.status.busy":"2022-03-27T03:20:13.587737Z","iopub.execute_input":"2022-03-27T03:20:13.588090Z","iopub.status.idle":"2022-03-27T03:20:13.604383Z","shell.execute_reply.started":"2022-03-27T03:20:13.588046Z","shell.execute_reply":"2022-03-27T03:20:13.603539Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels.info()\n## There are 30227 rows \n## there are 30277 target as well\n## x ,y, WIdth and height count is 9555 , the others are null","metadata":{"id":"rHra4-96jrW2","outputId":"74086dcb-273c-4e15-a83b-f554c3aeab84","execution":{"iopub.status.busy":"2022-03-27T03:20:18.911952Z","iopub.execute_input":"2022-03-27T03:20:18.912218Z","iopub.status.idle":"2022-03-27T03:20:18.940805Z","shell.execute_reply.started":"2022-03-27T03:20:18.912189Z","shell.execute_reply":"2022-03-27T03:20:18.940077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## we can see that all the null column values are with Target 0 indicating that those patients do not have penumonia\nlabels[labels.isnull().any(axis=1)].Target.value_counts()","metadata":{"id":"7YrXiOBqjrW3","outputId":"e5819453-fea5-4d47-e39c-d8b48ab07759","execution":{"iopub.status.busy":"2022-03-27T03:20:21.543274Z","iopub.execute_input":"2022-03-27T03:20:21.543839Z","iopub.status.idle":"2022-03-27T03:20:21.560224Z","shell.execute_reply.started":"2022-03-27T03:20:21.543799Z","shell.execute_reply":"2022-03-27T03:20:21.559414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## we can see that all the non null column values are with Target 1 indicating that those patients have pneumonia\nlabels[~labels.isnull().any(axis=1)].Target.value_counts()","metadata":{"id":"qRcQXWrTjrW3","outputId":"e2078e25-9f96-4c63-d7fd-732d73cc83af","execution":{"iopub.status.busy":"2022-03-27T03:20:27.952942Z","iopub.execute_input":"2022-03-27T03:20:27.953219Z","iopub.status.idle":"2022-03-27T03:20:27.969353Z","shell.execute_reply.started":"2022-03-27T03:20:27.953189Z","shell.execute_reply":"2022-03-27T03:20:27.968471Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Distubution of Targets , there are 20672 records with no pneumonia and 9555 with pneumonia\nlabels.Target.value_counts()","metadata":{"id":"QTAI0sXOjrW4","outputId":"9c062473-3c5b-4ede-c898-a15e5abcbad8","execution":{"iopub.status.busy":"2022-03-27T03:20:30.148573Z","iopub.execute_input":"2022-03-27T03:20:30.148838Z","iopub.status.idle":"2022-03-27T03:20:30.156187Z","shell.execute_reply.started":"2022-03-27T03:20:30.148811Z","shell.execute_reply":"2022-03-27T03:20:30.155282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Disturbution of Target, there are 31% of patients with pneumonia and the remaining are no pneumonia\n## There is a class imbalance issue\nlabel_count=labels['Target'].value_counts()\nexplode = (0.01,0.01)  \n\nfig1, ax1 = plt.subplots(figsize=(5,5))\nax1.pie(label_count.values, explode=explode, labels=label_count.index, autopct='%1.1f%%',\n        shadow=True, startangle=90)\nax1.axis('equal') \nplt.title('Target Distribution')\nplt.show()","metadata":{"id":"_zVhfc1IjrW4","outputId":"6e0809d7-8950-4135-830a-508013809508","execution":{"iopub.status.busy":"2022-03-27T03:20:35.425022Z","iopub.execute_input":"2022-03-27T03:20:35.425293Z","iopub.status.idle":"2022-03-27T03:20:35.601767Z","shell.execute_reply.started":"2022-03-27T03:20:35.425264Z","shell.execute_reply":"2022-03-27T03:20:35.600653Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Are there Unique Patients In Data Set ?? \" ,labels['patientId'].is_unique)\n## There is no point in finding unique for x,y,width and height as there can be duplcaie values in them\n## There are duplicate patients in the data set","metadata":{"id":"5ZWa_W8vjrW5","outputId":"9f01db69-8104-4aa2-a09f-1ae68ce1cdbd","execution":{"iopub.status.busy":"2022-03-27T03:20:40.395193Z","iopub.execute_input":"2022-03-27T03:20:40.395494Z","iopub.status.idle":"2022-03-27T03:20:40.409117Z","shell.execute_reply.started":"2022-03-27T03:20:40.395450Z","shell.execute_reply":"2022-03-27T03:20:40.408239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#labels.loc[labels.index.repeat(labels.patientId)]\nduplicateRowsDF = labels[labels.duplicated(['patientId'])]\nduplicateRowsDF.shape\n\n## There are 3543 duplicates","metadata":{"id":"G737NKi8jrW5","outputId":"f94bbf04-8fa9-4f5a-bb32-7479853c1e31","execution":{"iopub.status.busy":"2022-03-27T03:20:42.200901Z","iopub.execute_input":"2022-03-27T03:20:42.201162Z","iopub.status.idle":"2022-03-27T03:20:42.217121Z","shell.execute_reply.started":"2022-03-27T03:20:42.201133Z","shell.execute_reply":"2022-03-27T03:20:42.216382Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"duplicateRowsDF.head(2)","metadata":{"id":"C6Y6y_zZjrW6","outputId":"9e8cdead-4c5f-434e-fb33-9fa7af437428","execution":{"iopub.status.busy":"2022-03-27T03:20:43.968906Z","iopub.execute_input":"2022-03-27T03:20:43.969330Z","iopub.status.idle":"2022-03-27T03:20:43.988314Z","shell.execute_reply.started":"2022-03-27T03:20:43.969286Z","shell.execute_reply":"2022-03-27T03:20:43.987474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Examining one of the patient id which is duplicate , we can see that the x,y, widht and height is not the same\n## This indicates that the same patient has two bounding boxes in the same dicom image\nlabels[labels.patientId=='00436515-870c-4b36-a041-de91049b9ab4']","metadata":{"id":"skFJ5KMJjrW6","outputId":"60fd177c-4723-48d4-b1ab-7dcb877f74a8","execution":{"iopub.status.busy":"2022-03-27T03:20:49.661441Z","iopub.execute_input":"2022-03-27T03:20:49.662123Z","iopub.status.idle":"2022-03-27T03:20:49.677954Z","shell.execute_reply.started":"2022-03-27T03:20:49.662086Z","shell.execute_reply":"2022-03-27T03:20:49.676943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels[labels.patientId=='00704310-78a8-4b38-8475-49f4573b2dbb']","metadata":{"id":"VG2H9MhWjrW7","outputId":"b5406075-e6c1-4b58-e15b-66d5f066ff64","execution":{"iopub.status.busy":"2022-03-27T03:20:52.444809Z","iopub.execute_input":"2022-03-27T03:20:52.445488Z","iopub.status.idle":"2022-03-27T03:20:52.462386Z","shell.execute_reply.started":"2022-03-27T03:20:52.445451Z","shell.execute_reply":"2022-03-27T03:20:52.461421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Reading the class Info Data Set**","metadata":{"id":"NBPKkmF6kQsc"}},{"cell_type":"code","source":"## Reading the classes label , \nclass_labels = pd.read_csv('/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_detailed_class_info.csv')\nclass_labels.head()\n","metadata":{"id":"v1QwQ4fyjrW7","outputId":"21859b1a-3775-48b3-a5ed-1d5b6e587c9a","execution":{"iopub.status.busy":"2022-03-27T03:20:56.464926Z","iopub.execute_input":"2022-03-27T03:20:56.465343Z","iopub.status.idle":"2022-03-27T03:20:56.531859Z","shell.execute_reply.started":"2022-03-27T03:20:56.465308Z","shell.execute_reply":"2022-03-27T03:20:56.530609Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class_labels.shape\n## There are 30277 rows and two columns , 30277 rows same as the labels data set","metadata":{"id":"AIsiSSBGjrW8","outputId":"d80c566f-371e-4664-ac4f-a0c68d667cbb","execution":{"iopub.status.busy":"2022-03-27T03:20:59.013082Z","iopub.execute_input":"2022-03-27T03:20:59.013360Z","iopub.status.idle":"2022-03-27T03:20:59.019081Z","shell.execute_reply.started":"2022-03-27T03:20:59.013330Z","shell.execute_reply":"2022-03-27T03:20:59.018265Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class_labels.info()\n## There are no null columns ","metadata":{"id":"ssDAkFxFjrW8","outputId":"23c3edc6-5f7b-4797-8982-42b835b91576","execution":{"iopub.status.busy":"2022-03-27T03:21:00.776494Z","iopub.execute_input":"2022-03-27T03:21:00.777034Z","iopub.status.idle":"2022-03-27T03:21:00.795633Z","shell.execute_reply.started":"2022-03-27T03:21:00.776999Z","shell.execute_reply":"2022-03-27T03:21:00.794861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class_labels['class'].value_counts()\n\n## there are 8851 normal cases , person with lung opactiry are 9555 and No Lung Opacity / Not Normal are 11821 ","metadata":{"id":"qiFmB3IbjrW9","outputId":"6e24e6ea-d0f0-4d83-d079-c895401471ed","execution":{"iopub.status.busy":"2022-03-27T03:21:04.480480Z","iopub.execute_input":"2022-03-27T03:21:04.481022Z","iopub.status.idle":"2022-03-27T03:21:04.492957Z","shell.execute_reply.started":"2022-03-27T03:21:04.480986Z","shell.execute_reply":"2022-03-27T03:21:04.492194Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Disturbution of Classes, there are 39% of patients with No Lung opacity , 29.3% Normal \n## and the remaining are with Lung Opacity\nlabel_count=class_labels['class'].value_counts()\nexplode = (0.01,0.01,0.01)  \n\nfig1, ax1 = plt.subplots(figsize=(5,5))\nax1.pie(label_count.values, explode=explode, labels=label_count.index, autopct='%1.1f%%',\n        shadow=True, startangle=90)\nax1.axis('equal') \nplt.title('Class Distribution')\nplt.show()","metadata":{"id":"9tsYybyejrW-","outputId":"8d923bc8-51b5-4639-989b-fde268231b89","execution":{"iopub.status.busy":"2022-03-27T03:21:06.448555Z","iopub.execute_input":"2022-03-27T03:21:06.449177Z","iopub.status.idle":"2022-03-27T03:21:06.590204Z","shell.execute_reply.started":"2022-03-27T03:21:06.449139Z","shell.execute_reply":"2022-03-27T03:21:06.589438Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n#labels.loc[labels.index.repeat(labels.patientId)]\nduplicateClassRowsDF = class_labels[class_labels.duplicated(['patientId'])]\nduplicateClassRowsDF.shape\n\n## There are 3543 duplicates similar to the labels dataset","metadata":{"id":"W-p3UahjjrW-","outputId":"d2ab395f-0803-4943-8ba2-ae54d9f8c25c","execution":{"iopub.status.busy":"2022-03-27T03:21:10.724642Z","iopub.execute_input":"2022-03-27T03:21:10.725177Z","iopub.status.idle":"2022-03-27T03:21:10.740933Z","shell.execute_reply.started":"2022-03-27T03:21:10.725144Z","shell.execute_reply":"2022-03-27T03:21:10.740123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"duplicateClassRowsDF.head(2)","metadata":{"id":"lQr5yXIYjrW_","outputId":"d2b0d947-442e-4e57-d397-5f75a7c57559","execution":{"iopub.status.busy":"2022-03-27T03:21:12.672518Z","iopub.execute_input":"2022-03-27T03:21:12.673038Z","iopub.status.idle":"2022-03-27T03:21:12.684215Z","shell.execute_reply.started":"2022-03-27T03:21:12.673004Z","shell.execute_reply":"2022-03-27T03:21:12.683303Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## The same patient id has the same class even though they are duplicate\nclass_labels[class_labels.patientId=='00704310-78a8-4b38-8475-49f4573b2dbb']","metadata":{"id":"TTEw1ErnjrW_","outputId":"4a25f350-2e0c-4d2c-ee37-41cb8d0c4fbb","execution":{"iopub.status.busy":"2022-03-27T03:21:20.881230Z","iopub.execute_input":"2022-03-27T03:21:20.881496Z","iopub.status.idle":"2022-03-27T03:21:20.897429Z","shell.execute_reply.started":"2022-03-27T03:21:20.881468Z","shell.execute_reply":"2022-03-27T03:21:20.896623Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Merging the class and labels data set into training dataset","metadata":{"id":"7BIfS-QYkpnG"}},{"cell_type":"code","source":"# Conctinating the two dataset - 'labels' and 'class_labels':\ntraining_data = pd.concat([labels, class_labels['class']], axis = 1)\n\ntraining_data.head()","metadata":{"id":"ZoD-zZEPjrXA","outputId":"9e158abb-3d14-4bf3-86d5-189d30269ebf","execution":{"iopub.status.busy":"2022-03-27T03:21:28.295750Z","iopub.execute_input":"2022-03-27T03:21:28.296016Z","iopub.status.idle":"2022-03-27T03:21:28.315036Z","shell.execute_reply.started":"2022-03-27T03:21:28.295989Z","shell.execute_reply":"2022-03-27T03:21:28.314368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = plt.subplots(nrows = 1, figsize = (12, 6))\ntemp = training_data.groupby('Target')['class'].value_counts()\ndata_target_class = pd.DataFrame(data = {'Values': temp.values}, index = temp.index).reset_index()\nsns.barplot(ax = ax, x = 'Target', y = 'Values', hue = 'class', data = data_target_class, palette = 'Set3')\nplt.title('Class and Target  Distrubution')\n\n## it shows that class distrubution grouped by Target \n## Target 0 has only Normal or No Lung Opacity class\n## Target 1 has only Lung Opacity class","metadata":{"id":"xFxB5VsEjrXA","outputId":"4998a25b-3399-4431-8e28-f0f820f25b9e","execution":{"iopub.status.busy":"2022-03-27T03:21:30.743635Z","iopub.execute_input":"2022-03-27T03:21:30.743905Z","iopub.status.idle":"2022-03-27T03:21:31.018824Z","shell.execute_reply.started":"2022-03-27T03:21:30.743876Z","shell.execute_reply":"2022-03-27T03:21:31.018145Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## ANalysing the dicom image\nimport matplotlib.patches as patches\n\ndef inspectImages(data):\n    img_data = list(data.T.to_dict().values())\n    f, ax = plt.subplots(3,3, figsize=(16,18))\n    for i,data_row in enumerate(img_data):\n        patientImage = data_row['patientId']\n        dcm_file = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format(patientImage)\n        data_row_img_data = dcm.read_file(dcm_file)\n        modality = data_row_img_data.Modality\n        age = data_row_img_data.PatientAge\n        sex = data_row_img_data.PatientSex\n        data_row_img = dcm.dcmread(dcm_file)\n        ax[i//3, i%3].imshow(data_row_img.pixel_array, cmap=plt.cm.bone) \n        ax[i//3, i%3].axis('off')\n        ax[i//3, i%3].set_title('ID: {}\\nModality: {} Age: {} Sex: {} Target: {}\\nClass: {}\\Bounds: {}:{}:{}:{}'.format(\n                data_row['patientId'],\n                modality, age, sex, data_row['Target'], data_row['class'], \n                data_row['x'],data_row['y'],data_row['width'],data_row['height']))\n        label = data_row[\"class\"]\n        if not math.isnan(data_row['x']):\n            x, y, width, height  =  data_row['x'],data_row['y'],data_row['width'],data_row['height']\n            rect = patches.Rectangle((x, y),width, height,\n                                 linewidth = 2,\n                                 edgecolor = 'r',\n                                 facecolor = 'none')\n\n        # Draw the bounding box on top of the image\n            ax[i//3, i%3].add_patch(rect)\n\n    plt.show()","metadata":{"id":"Q_SCbccmjrXA","execution":{"iopub.status.busy":"2022-03-27T03:21:35.463623Z","iopub.execute_input":"2022-03-27T03:21:35.464136Z","iopub.status.idle":"2022-03-27T03:21:35.475634Z","shell.execute_reply.started":"2022-03-27T03:21:35.464099Z","shell.execute_reply":"2022-03-27T03:21:35.474902Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Displaying Chest Xray Images of Patients who have Pneuomina","metadata":{"id":"mnNSehAokZE0"}},{"cell_type":"code","source":"## checking few images which has pneuonia \ninspectImages(training_data[training_data['Target']==1].sample(9))","metadata":{"id":"DorQPLEJjrXB","outputId":"cf608cf4-c8a0-4a41-a2fa-6546d85fd04e","execution":{"iopub.status.busy":"2022-03-27T03:21:40.075307Z","iopub.execute_input":"2022-03-27T03:21:40.075948Z","iopub.status.idle":"2022-03-27T03:21:41.866316Z","shell.execute_reply.started":"2022-03-27T03:21:40.075911Z","shell.execute_reply":"2022-03-27T03:21:41.865593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## checking few images which does not have pneuonia \ninspectImages(training_data[training_data['Target']==0].sample(9))","metadata":{"id":"BMDgsZy-jrXB","outputId":"3726975e-3e07-4d64-b3f3-93affe18d9f1","execution":{"iopub.status.busy":"2022-03-27T03:21:49.516053Z","iopub.execute_input":"2022-03-27T03:21:49.516301Z","iopub.status.idle":"2022-03-27T03:21:51.652770Z","shell.execute_reply.started":"2022-03-27T03:21:49.516273Z","shell.execute_reply":"2022-03-27T03:21:51.650126Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Reading the Dicom images meta data and appending it to the training set","metadata":{"id":"sFwHgv0Lk0Vl"}},{"cell_type":"code","source":"## DCIM image contain the meta data alon with it, \n## Function to read the dcim data and appending to the resultset\ndef readDCIMData(rowData):\n    dcm_file = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format(rowData.patientId)\n    dcm_data = dcm.read_file(dcm_file)\n    img = dcm_data.pixel_array\n    return dcm_data.PatientSex,dcm_data.PatientAge\n","metadata":{"id":"hbV2F8q1jrXB","execution":{"iopub.status.busy":"2022-03-27T03:21:57.562554Z","iopub.execute_input":"2022-03-27T03:21:57.563081Z","iopub.status.idle":"2022-03-27T03:21:57.567968Z","shell.execute_reply.started":"2022-03-27T03:21:57.563045Z","shell.execute_reply":"2022-03-27T03:21:57.566932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Reading the image data and append it to the training_data dataset\ntraining_data['sex'], training_data['age'] = zip(*training_data.apply(readDCIMData, axis=1))\n","metadata":{"id":"lp1fDY3-jrXC","execution":{"iopub.status.busy":"2022-03-27T03:22:02.198314Z","iopub.execute_input":"2022-03-27T03:22:02.199004Z","iopub.status.idle":"2022-03-27T03:29:08.555304Z","shell.execute_reply.started":"2022-03-27T03:22:02.198966Z","shell.execute_reply":"2022-03-27T03:29:08.554453Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"training_data.info()\n## There are 30227  records\n## sex and age also have the smae count indicating that there are no images missing every patient has an dicom image \n## Age should be a numeric , currently it is an objetc","metadata":{"id":"YQnhTEvKjrXC","outputId":"373a5f1f-2eca-4cce-fa02-7cd434e925e9","execution":{"iopub.status.busy":"2022-03-27T03:33:19.602209Z","iopub.execute_input":"2022-03-27T03:33:19.602481Z","iopub.status.idle":"2022-03-27T03:33:19.630054Z","shell.execute_reply.started":"2022-03-27T03:33:19.602453Z","shell.execute_reply":"2022-03-27T03:33:19.629231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Converting age to Numeric as the current data type is a String\ntraining_data['age'] = training_data.age.astype(int)\n","metadata":{"id":"Mg4Vu8UrjrXC","execution":{"iopub.status.busy":"2022-03-27T03:33:43.357038Z","iopub.execute_input":"2022-03-27T03:33:43.357301Z","iopub.status.idle":"2022-03-27T03:33:43.367114Z","shell.execute_reply.started":"2022-03-27T03:33:43.357271Z","shell.execute_reply":"2022-03-27T03:33:43.366433Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"training_data.describe(include=\"all\").T\n## The mean age is 46 years , where as minimum age is 1 year and the max age is 155 which seems to be an outlier\n## 50% of the patiens are of aroudn 49 age , the std deviation is 16 which sugges that age is not normally distubuted","metadata":{"id":"G4kGdc2QjrXD","outputId":"df93601e-0407-470e-9fc2-75436fa0a316","execution":{"iopub.status.busy":"2022-03-27T03:33:46.116440Z","iopub.execute_input":"2022-03-27T03:33:46.116730Z","iopub.status.idle":"2022-03-27T03:33:46.182890Z","shell.execute_reply.started":"2022-03-27T03:33:46.116697Z","shell.execute_reply":"2022-03-27T03:33:46.182204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"training_data.sex.value_counts()\n## there are only two genders ","metadata":{"id":"J2687B6xjrXD","outputId":"01dd2994-15fb-48e5-b193-35c087539441","execution":{"iopub.status.busy":"2022-03-27T03:33:52.133927Z","iopub.execute_input":"2022-03-27T03:33:52.134175Z","iopub.status.idle":"2022-03-27T03:33:52.143937Z","shell.execute_reply.started":"2022-03-27T03:33:52.134146Z","shell.execute_reply":"2022-03-27T03:33:52.143156Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Distbution of Sex Among the tragets\nfig, ax = plt.subplots(nrows = 1, figsize = (12, 6))\ntemp = training_data.groupby('Target')['sex'].value_counts()\ndata_target_class = pd.DataFrame(data = {'Values': temp.values}, index = temp.index).reset_index()\nsns.barplot(ax = ax, x = 'Target', y = 'Values', hue = 'sex', data = data_target_class, palette = 'Set3')\nplt.title('Sex and Target for Chest Exams')\n\n## the number of males in both category are higher than women","metadata":{"id":"de-bnem9jrXD","outputId":"d5a93474-13af-4923-df33-b38024cbfc52","execution":{"iopub.status.busy":"2022-03-27T03:33:53.859260Z","iopub.execute_input":"2022-03-27T03:33:53.859532Z","iopub.status.idle":"2022-03-27T03:33:54.074065Z","shell.execute_reply.started":"2022-03-27T03:33:53.859501Z","shell.execute_reply":"2022-03-27T03:33:54.073242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Distbution of Sex Among the classes\nfig, ax = plt.subplots(nrows = 1, figsize = (12, 6))\ntemp = training_data.groupby('class')['sex'].value_counts()\ndata_target_class = pd.DataFrame(data = {'Values': temp.values}, index = temp.index).reset_index()\nsns.barplot(ax = ax, x = 'class', y = 'Values', hue = 'sex', data = data_target_class, palette = 'Set3')\nplt.title('Sex and class for Chest Exams')\n\n## the number of males in all classes are higher than women","metadata":{"id":"UhCxETx0jrXD","outputId":"239305da-adb6-42fe-e789-4ecb6dc7f8c3","execution":{"iopub.status.busy":"2022-03-27T03:33:59.428658Z","iopub.execute_input":"2022-03-27T03:33:59.428908Z","iopub.status.idle":"2022-03-27T03:33:59.664896Z","shell.execute_reply.started":"2022-03-27T03:33:59.428880Z","shell.execute_reply":"2022-03-27T03:33:59.664228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.distplot(training_data.age) \n# plots the distrubution of age\n## Looks like normal distubution of age","metadata":{"id":"UKqhmEYwjrXE","outputId":"06ddd275-3e3c-44c8-a3b8-097659d598a8","execution":{"iopub.status.busy":"2022-03-27T03:34:06.861611Z","iopub.execute_input":"2022-03-27T03:34:06.862278Z","iopub.status.idle":"2022-03-27T03:34:07.502272Z","shell.execute_reply.started":"2022-03-27T03:34:06.862235Z","shell.execute_reply":"2022-03-27T03:34:07.501604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(10,5))  # setting the figure size\nax = sns.barplot(x='class', y='age', data=training_data, palette='muted')  # barplot'\n## This is the distubution of Age with class, maximum age of person with pneuomina is arund 45","metadata":{"id":"efwxIOLOjrXE","outputId":"a5687a39-a227-43c9-f90f-cf1d2bdf54e5","execution":{"iopub.status.busy":"2022-03-27T03:34:12.068327Z","iopub.execute_input":"2022-03-27T03:34:12.068982Z","iopub.status.idle":"2022-03-27T03:34:12.625103Z","shell.execute_reply.started":"2022-03-27T03:34:12.068942Z","shell.execute_reply":"2022-03-27T03:34:12.624283Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(10,5))  # setting the figure size\nax = sns.barplot(x='Target', y='age', data=training_data, palette='muted')  # barplot'\n## This is the distubution of Age with class, maximum age of person with pneuomina is arund 45","metadata":{"id":"MEiKKod2jrXF","outputId":"b9b5eb7c-3374-4147-f797-96df450da890","execution":{"iopub.status.busy":"2022-03-27T03:34:17.845163Z","iopub.execute_input":"2022-03-27T03:34:17.845501Z","iopub.status.idle":"2022-03-27T03:34:18.579281Z","shell.execute_reply.started":"2022-03-27T03:34:17.845455Z","shell.execute_reply":"2022-03-27T03:34:18.578519Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(15,7))\nsns.boxplot(x='class', y='age', data= training_data)\nplt.show()\n\n## The  class which has no pneuomia has few outliers , theie age is somewhere aroun 150 years","metadata":{"id":"MCTahXxZjrXF","outputId":"4fe2fed4-1c95-496c-9939-964f795bec0b","execution":{"iopub.status.busy":"2022-03-27T03:34:23.628887Z","iopub.execute_input":"2022-03-27T03:34:23.629150Z","iopub.status.idle":"2022-03-27T03:34:23.846724Z","shell.execute_reply.started":"2022-03-27T03:34:23.629118Z","shell.execute_reply":"2022-03-27T03:34:23.846017Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('Distribution of `Age`: Overall and Target = 1')\nfig = plt.figure(figsize = (10, 6))\n\nax = fig.add_subplot(121)\ng = (sns.distplot(training_data['age']).set_title('Distribution of PatientAge'))\n\nax = fig.add_subplot(122)\ng = (sns.distplot(training_data.loc[training_data['Target'] == 1, 'age']).set_title('Distribution of PatientAge who have pneumonia'))\n\n## Overall Distrubution of Age looks normal with very little skwe\n## Distubution of Patients afe who have penuomonia are a left skewed ","metadata":{"id":"sxM_n474jrXF","outputId":"4665e9be-4196-4506-e13e-46d9d75ca413","execution":{"iopub.status.busy":"2022-03-27T03:34:30.157665Z","iopub.execute_input":"2022-03-27T03:34:30.157935Z","iopub.status.idle":"2022-03-27T03:34:30.851804Z","shell.execute_reply.started":"2022-03-27T03:34:30.157906Z","shell.execute_reply":"2022-03-27T03:34:30.851125Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"corr = training_data.corr()\nplt.figure(figsize=(12,5))\n\nsns.heatmap(corr,annot=True)\n\n## There is high corelation between widht and height\n","metadata":{"id":"XXrRdchsjrXF","outputId":"2f67d37f-75b6-4ee9-9e75-d3d62c66fc56","execution":{"iopub.status.busy":"2022-03-27T03:34:35.477076Z","iopub.execute_input":"2022-03-27T03:34:35.478555Z","iopub.status.idle":"2022-03-27T03:34:35.879912Z","shell.execute_reply.started":"2022-03-27T03:34:35.478511Z","shell.execute_reply":"2022-03-27T03:34:35.879228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### **MODEL BUILDING**","metadata":{"id":"PFoGbZyylY0n"}},{"cell_type":"code","source":"## Just taking a 200 samples from the dataset\nsample_trainigdata = training_data.groupby('class', group_keys=False).apply(lambda x: x.sample(800))","metadata":{"id":"b-xgMdG4jrXG","execution":{"iopub.status.busy":"2022-03-27T03:34:42.704117Z","iopub.execute_input":"2022-03-27T03:34:42.704382Z","iopub.status.idle":"2022-03-27T03:34:42.724034Z","shell.execute_reply.started":"2022-03-27T03:34:42.704352Z","shell.execute_reply":"2022-03-27T03:34:42.723336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Checking the training data set with class distbution \nsample_trainigdata[\"class\"].value_counts()","metadata":{"id":"5H899GFAjrXG","outputId":"8abbd540-1669-4c2b-a4a4-cc8c2e70699e","execution":{"iopub.status.busy":"2022-03-27T03:34:48.349802Z","iopub.execute_input":"2022-03-27T03:34:48.350453Z","iopub.status.idle":"2022-03-27T03:34:48.357896Z","shell.execute_reply.started":"2022-03-27T03:34:48.350407Z","shell.execute_reply":"2022-03-27T03:34:48.357122Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_trainigdata.info()","metadata":{"id":"DPOy5iFRjrXG","outputId":"2d9097f8-9438-4e31-848d-a5a0a20f6b13","execution":{"iopub.status.busy":"2022-03-27T03:34:54.841871Z","iopub.execute_input":"2022-03-27T03:34:54.842146Z","iopub.status.idle":"2022-03-27T03:34:54.857090Z","shell.execute_reply.started":"2022-03-27T03:34:54.842110Z","shell.execute_reply":"2022-03-27T03:34:54.856414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Pre Processing the image\nfrom tensorflow.keras.applications.mobilenet import preprocess_input\n\nimages = []\nADJUSTED_IMAGE_SIZE = 128\nimageList = []\nclassLabels = []\nlabels = []\noriginalImage = []\n# Function to read the image from the path and reshape the image to size\ndef readAndReshapeImage(image):\n    img = np.array(image).astype(np.uint8)\n    ## Resize the image\n    res = cv2.resize(img,(ADJUSTED_IMAGE_SIZE,ADJUSTED_IMAGE_SIZE), interpolation = cv2.INTER_LINEAR)\n    return res\n\n## Read the imahge and resize the image\ndef populateImage(rowData):\n    for index, row in rowData.iterrows():\n        patientId = row.patientId\n        classlabel = row[\"class\"]\n        dcm_file = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format(patientId)\n        dcm_data = dcm.read_file(dcm_file)\n        img = dcm_data.pixel_array\n        ## Converting the image to 3 channels as the dicom image pixel does not have colour classes wiht it\n        if len(img.shape) != 3 or img.shape[2] != 3:\n            img = np.stack((img,) * 3, -1)\n        imageList.append(readAndReshapeImage(img))\n#         originalImage.append(img)\n        classLabels.append(classlabel)\n    tmpImages = np.array(imageList)\n    tmpLabels = np.array(classLabels)\n#     originalImages = np.array(originalImage)\n    return tmpImages,tmpLabels\n","metadata":{"id":"JkU35-qrjrXH","execution":{"iopub.status.busy":"2022-03-27T03:34:59.986251Z","iopub.execute_input":"2022-03-27T03:34:59.986707Z","iopub.status.idle":"2022-03-27T03:34:59.996092Z","shell.execute_reply.started":"2022-03-27T03:34:59.986669Z","shell.execute_reply":"2022-03-27T03:34:59.995194Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Reading the images into numpy array\nimages,labels = populateImage(sample_trainigdata)","metadata":{"id":"4tWXExjOjrXI","execution":{"iopub.status.busy":"2022-03-27T03:35:04.737805Z","iopub.execute_input":"2022-03-27T03:35:04.738447Z","iopub.status.idle":"2022-03-27T03:35:31.579883Z","shell.execute_reply.started":"2022-03-27T03:35:04.738385Z","shell.execute_reply":"2022-03-27T03:35:31.579130Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images.shape , labels.shape\n## The image is of 128*128 with 3 channels","metadata":{"id":"nQhB6EwhjrXI","outputId":"f317f5c6-7b22-4581-e5d3-b67d7ca62030","execution":{"iopub.status.busy":"2022-03-27T03:35:44.929583Z","iopub.execute_input":"2022-03-27T03:35:44.930231Z","iopub.status.idle":"2022-03-27T03:35:44.935302Z","shell.execute_reply.started":"2022-03-27T03:35:44.930194Z","shell.execute_reply":"2022-03-27T03:35:44.934533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Checking one of the converted image \nplt.imshow(images[100])","metadata":{"id":"LXZNYMxqjrXJ","outputId":"3724e22c-4f86-43d2-900b-cfc24ad81a5c","execution":{"iopub.status.busy":"2022-03-27T03:35:47.832274Z","iopub.execute_input":"2022-03-27T03:35:47.832822Z","iopub.status.idle":"2022-03-27T03:35:48.035042Z","shell.execute_reply.started":"2022-03-27T03:35:47.832783Z","shell.execute_reply":"2022-03-27T03:35:48.034226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## check the unique labels\nnp.unique(labels),len(np.unique(labels))","metadata":{"id":"kJBZy1d-jrXJ","outputId":"02d03333-7bde-410b-8c35-efa3b21ada31","execution":{"iopub.status.busy":"2022-03-27T03:35:54.412872Z","iopub.execute_input":"2022-03-27T03:35:54.413236Z","iopub.status.idle":"2022-03-27T03:35:54.422103Z","shell.execute_reply.started":"2022-03-27T03:35:54.413181Z","shell.execute_reply":"2022-03-27T03:35:54.421139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\nimport numpy as np\nimport tensorflow\nfrom tensorflow.keras.models import Sequential\n# define model\nfrom tensorflow.keras import losses,optimizers\nfrom tensorflow.keras.layers import Dense,  Activation, Flatten,Dropout,MaxPooling2D,BatchNormalization\nimport pandas as pd\nimport tensorflow as tf\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport scipy.stats as stats \nimport matplotlib.pyplot as plt\nfrom tensorflow import keras\n#from keras.models import Sequential\n#from keras.layers import Dense\n#from sklearn.model_selection import StratifiedKFold\n%matplotlib inline\n#Test Train Split\nfrom sklearn.model_selection import train_test_split\n#Feature Scaling library\nfrom sklearn.preprocessing import StandardScaler\n#import pickle\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Flatten, Dense ,LeakyReLU\nfrom tensorflow.keras import regularizers, optimizers\nfrom sklearn.metrics import r2_score\nfrom tensorflow.keras.models import load_model\nimport warnings\nwarnings.filterwarnings('ignore')\n\nfrom keras.models import Sequential  # initial NN\nfrom keras.layers import Dense, Dropout # construct each layer\nfrom keras.layers import Conv2D # swipe across the image by 1\nfrom keras.layers import MaxPooling2D # swipe across by pool size\nfrom keras.layers import Flatten, GlobalAveragePooling2D,GlobalMaxPooling2D\nfrom tensorflow.keras.optimizers import RMSprop\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.optimizers import Adam","metadata":{"id":"UYh7eI_MjrXJ","execution":{"iopub.status.busy":"2022-03-27T03:36:00.929109Z","iopub.execute_input":"2022-03-27T03:36:00.929375Z","iopub.status.idle":"2022-03-27T03:36:00.944802Z","shell.execute_reply.started":"2022-03-27T03:36:00.929346Z","shell.execute_reply":"2022-03-27T03:36:00.944111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## encoding the labels\nfrom sklearn.preprocessing import LabelBinarizer\nenc = LabelBinarizer()\ny2 = enc.fit_transform(labels)","metadata":{"id":"ZGhxjLPljrXK","execution":{"iopub.status.busy":"2022-03-27T03:36:03.217099Z","iopub.execute_input":"2022-03-27T03:36:03.217359Z","iopub.status.idle":"2022-03-27T03:36:03.227014Z","shell.execute_reply.started":"2022-03-27T03:36:03.217330Z","shell.execute_reply":"2022-03-27T03:36:03.226287Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## splitting into train ,test and validation data\nfrom sklearn.model_selection import train_test_split\nX_train, X_test, y_train, y_test = train_test_split(images, y2, test_size=0.3, random_state=50)\nX_test, X_val, y_test, y_val = train_test_split(X_test,y_test, test_size = 0.5, random_state=50)\n\n","metadata":{"id":"oE-39Ja0jrXK","execution":{"iopub.status.busy":"2022-03-27T03:36:09.554086Z","iopub.execute_input":"2022-03-27T03:36:09.554341Z","iopub.status.idle":"2022-03-27T03:36:09.608606Z","shell.execute_reply.started":"2022-03-27T03:36:09.554313Z","shell.execute_reply":"2022-03-27T03:36:09.607832Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## FUnction to create a dataframe for results\ndef createResultDf(name,accuracy,testscore):\n    result = pd.DataFrame({'Method':[name], 'accuracy': [accuracy] ,'Test Score':[testscore]})\n    return result\n\n","metadata":{"id":"BJrc8M_sjrXL","execution":{"iopub.status.busy":"2022-03-27T03:36:11.964889Z","iopub.execute_input":"2022-03-27T03:36:11.965354Z","iopub.status.idle":"2022-03-27T03:36:11.969821Z","shell.execute_reply.started":"2022-03-27T03:36:11.965316Z","shell.execute_reply":"2022-03-27T03:36:11.968984Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## CNN Model without transfer learning , we start with 32 filters with 5,5 kernal and no padding , then 64 and 128 wiht drop layers in between \n## And softmax activaation as the last layer\ndef cnn_model(height, width, num_channels, num_classes, loss='categorical_crossentropy', metrics=['accuracy']):\n  batch_size = None\n\n  model = Sequential()\n\n  model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', \n                  activation ='relu', batch_input_shape = (batch_size,height, width, num_channels)))\n\n\n  model.add(Conv2D(filters = 32, kernel_size = (5,5),padding = 'Same', \n                  activation ='relu'))\n  model.add(MaxPooling2D(pool_size=(2,2)))\n  model.add(Dropout(0.2))\n\n\n  model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'Same', \n                  activation ='relu'))\n  model.add(Conv2D(filters = 64, kernel_size = (3,3),padding = 'same', \n                  activation ='relu'))\n  model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2)))\n  model.add(Dropout(0.3))\n\n  model.add(Conv2D(filters = 128, kernel_size = (3,3),padding = 'Same', \n                  activation ='relu'))\n  model.add(Conv2D(filters = 128, kernel_size = (3,3),padding = 'Same', \n                  activation ='relu'))\n  model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2)))\n  model.add(Dropout(0.4))\n\n\n\n  model.add(GlobalMaxPooling2D())\n  model.add(Dense(256, activation = \"relu\"))\n  model.add(Dropout(0.5))\n  model.add(Dense(num_classes, activation = \"softmax\"))\n\n  optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)\n  model.compile(optimizer = optimizer, loss = loss, metrics = metrics)\n  model.summary()\n  return model","metadata":{"id":"6wcRGVMyjrXL","execution":{"iopub.status.busy":"2022-03-27T03:36:16.573075Z","iopub.execute_input":"2022-03-27T03:36:16.573835Z","iopub.status.idle":"2022-03-27T03:36:16.587056Z","shell.execute_reply.started":"2022-03-27T03:36:16.573783Z","shell.execute_reply":"2022-03-27T03:36:16.586290Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Model Summary\ncnn = cnn_model(ADJUSTED_IMAGE_SIZE,ADJUSTED_IMAGE_SIZE,3,3)","metadata":{"id":"UMv_bWwJjrXL","outputId":"e7579dfc-5622-4cbf-d252-bbef2577c763","execution":{"iopub.status.busy":"2022-03-27T03:36:23.736714Z","iopub.execute_input":"2022-03-27T03:36:23.737070Z","iopub.status.idle":"2022-03-27T03:36:26.282150Z","shell.execute_reply.started":"2022-03-27T03:36:23.737028Z","shell.execute_reply":"2022-03-27T03:36:26.281453Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Training for 30 epocs with batch size of 30\nhistory = cnn.fit(X_train, \n                  y_train, \n                  epochs = 30, \n                  validation_data = (X_val,y_val),\n                  batch_size = 30)","metadata":{"id":"MkmcMUzFjrXL","outputId":"b202fbba-6dcc-47c7-ee79-9e6089618c3d","execution":{"iopub.status.busy":"2022-03-27T03:36:32.597299Z","iopub.execute_input":"2022-03-27T03:36:32.598067Z","iopub.status.idle":"2022-03-27T03:37:55.804198Z","shell.execute_reply.started":"2022-03-27T03:36:32.598018Z","shell.execute_reply":"2022-03-27T03:37:55.803451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## evakualting the acuracy , we have only got accuracy of 43-46% where as the training accuracy is around 60%\nfcl_loss, fcl_accuracy = cnn.evaluate(X_test, y_test, verbose=1)\nprint('Test loss:', fcl_loss)\nprint('Test accuracy:', fcl_accuracy)","metadata":{"id":"SwNDgKvVjrXM","outputId":"1fa9456d-87b5-49a8-8dc2-03df4511d8ba","execution":{"iopub.status.busy":"2022-03-27T03:39:42.407306Z","iopub.execute_input":"2022-03-27T03:39:42.407922Z","iopub.status.idle":"2022-03-27T03:39:43.084597Z","shell.execute_reply.started":"2022-03-27T03:39:42.407884Z","shell.execute_reply":"2022-03-27T03:39:43.083881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## PLottting the accuracy vs loss graph\nacc = history.history['accuracy']\nval_acc = history.history['val_accuracy']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\nepochs_range = range(30)\n\nplt.figure(figsize=(15, 15))\nplt.subplot(2, 2, 1)\nplt.plot(epochs_range, acc, label='Training Accuracy')\nplt.plot(epochs_range, val_acc, label='Validation Accuracy')\nplt.legend(loc='lower right')\nplt.title('Training and Validation Accuracy')\n\nplt.subplot(2, 2, 2)\nplt.plot(epochs_range, loss, label='Training Loss')\nplt.plot(epochs_range, val_loss, label='Validation Loss')\nplt.legend(loc='upper right')\nplt.title('Training and Validation Loss')\nplt.show()\n\n#The training and Validation loss is almost same, but for the training and validation accuracy chart, \n#the validation accuracy falls down in the later epochs, this could be because we have only taken 200 images for processing.","metadata":{"id":"JuOIpAcBjrXM","outputId":"3e832382-3838-4fba-a70a-3865244eebe8","execution":{"iopub.status.busy":"2022-03-27T03:39:48.830545Z","iopub.execute_input":"2022-03-27T03:39:48.831003Z","iopub.status.idle":"2022-03-27T03:39:49.126539Z","shell.execute_reply.started":"2022-03-27T03:39:48.830965Z","shell.execute_reply":"2022-03-27T03:39:49.125835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"resultDF = createResultDf(\"CNN\",acc[-1],fcl_accuracy)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:40:29.597783Z","iopub.execute_input":"2022-03-27T03:40:29.598544Z","iopub.status.idle":"2022-03-27T03:40:29.603572Z","shell.execute_reply.started":"2022-03-27T03:40:29.598508Z","shell.execute_reply":"2022-03-27T03:40:29.602670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nimport itertools\nplt.subplots(figsize=(22,7)) #set the size of the plot \n\ndef plot_confusion_matrix(cm, classes,\n                          normalize=False,\n                          title='Confusion matrix',\n                          cmap=plt.cm.Blues):\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar()\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45)\n    plt.yticks(tick_marks, classes)\n\n    if normalize:\n        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, cm[i, j],\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n\n# Predict the values from the validation dataset\nY_pred = cnn.predict(X_test)\n# Convert predictions classes to one hot vectors \nY_pred_classes = np.argmax(Y_pred,axis = 1) \n# Convert validation observations to one hot vectors\nY_true = np.argmax(y_test,axis = 1) \n# compute the confusion matrix\nconfusion_mtx = confusion_matrix(Y_true, Y_pred_classes) \n# plot the confusion matrix\nplot_confusion_matrix(confusion_mtx, classes = range(3))\n\n#Class 0 ,1 and 2\n#Class 0 is Lung Opacity\n#Class 1 is No Lung Opacity/Normal, the model has predicted mostly wrong in this case to the Target 0. Type 2 error\n#Class 2 is Normal\n","metadata":{"id":"64Vf2LX3jrXM","outputId":"42ab677f-85e4-49fd-e3bf-243c904719a2","execution":{"iopub.status.busy":"2022-03-27T03:40:31.601170Z","iopub.execute_input":"2022-03-27T03:40:31.602009Z","iopub.status.idle":"2022-03-27T03:40:32.104678Z","shell.execute_reply.started":"2022-03-27T03:40:31.601958Z","shell.execute_reply":"2022-03-27T03:40:32.103951Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import recall_score, confusion_matrix, precision_score, f1_score, accuracy_score, roc_auc_score,classification_report\nfrom sklearn.metrics import classification_report\n\nY_truepred = np.argmax(y_test,axis = 1) \n\nY_testPred = cnn.predict(X_test)\n# Convert predictions classes to one hot vectors \nY_pred_classes = np.argmax(Y_pred,axis = 1) \n\nreportData = classification_report(Y_truepred, Y_pred_classes,output_dict=True)\n\nfor data in reportData:\n    if(data == '-1' or data == '1'):\n        if(type(reportData[data]) is dict):\n            for subData in reportData[data]:\n                resultDF[data+\"_\"+subData] = reportData[data][subData]\n\nresultDF","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:40:37.141974Z","iopub.execute_input":"2022-03-27T03:40:37.142574Z","iopub.status.idle":"2022-03-27T03:40:37.291755Z","shell.execute_reply.started":"2022-03-27T03:40:37.142533Z","shell.execute_reply":"2022-03-27T03:40:37.290966Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Creating a Copy\nX_train1 = X_train.copy()\nX_val1 = X_val.copy()\nX_test1 = X_test.copy()","metadata":{"id":"50zq_KEajrXN","execution":{"iopub.status.busy":"2022-03-27T03:40:43.551266Z","iopub.execute_input":"2022-03-27T03:40:43.551985Z","iopub.status.idle":"2022-03-27T03:40:43.599042Z","shell.execute_reply.started":"2022-03-27T03:40:43.551935Z","shell.execute_reply":"2022-03-27T03:40:43.598303Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**CNN with Tranfer learning using VGG16**","metadata":{"id":"BJQ0XdmGnMSx"}},{"cell_type":"code","source":"from tensorflow.keras.applications.vgg16 import VGG16\nfrom tensorflow.keras.applications.vgg16 import preprocess_input\n\n##VGGNet is a well-documented and globally used architecture for convolutional neural network\n## Include_top=False to remove the classification layer that was trained on the ImageNet dataset and set the model as not trainable\n\nbase_model = VGG16(weights=\"imagenet\", include_top=False, input_shape=X_train[0].shape)\nbase_model.trainable = False ## Not trainable weights\n\n## Preprocessing input\ntrain_ds = preprocess_input(X_train1) \ntrain_val_df = preprocess_input(X_val1)\n","metadata":{"id":"WTMSAUZ2jrXN","outputId":"0a35930e-5ecf-49ef-c9ff-6359010c03b4","execution":{"iopub.status.busy":"2022-03-27T03:40:46.355338Z","iopub.execute_input":"2022-03-27T03:40:46.356016Z","iopub.status.idle":"2022-03-27T03:40:47.830702Z","shell.execute_reply.started":"2022-03-27T03:40:46.355981Z","shell.execute_reply":"2022-03-27T03:40:47.829722Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Adding two hidden later and one softmax layer as an output layer\nfrom tensorflow.keras import layers, models\n\nflatten_layer = layers.Flatten()\ndense_layer_1 = layers.Dense(50, activation='relu')\ndense_layer_2 = layers.Dense(20, activation='relu')\nprediction_layer = layers.Dense(3, activation='softmax')\n\n\ncnn_VGG16_model = models.Sequential([\n    base_model,\n    flatten_layer,\n    dense_layer_1,\n    dense_layer_2,\n    prediction_layer\n])","metadata":{"id":"ewRFIfZGjrXO","execution":{"iopub.status.busy":"2022-03-27T03:40:51.678693Z","iopub.execute_input":"2022-03-27T03:40:51.679338Z","iopub.status.idle":"2022-03-27T03:40:51.754199Z","shell.execute_reply.started":"2022-03-27T03:40:51.679300Z","shell.execute_reply":"2022-03-27T03:40:51.753563Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping\n\ncnn_VGG16_model.compile(\n    optimizer='Adam',\n    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n    metrics=['accuracy'],\n)\n\n## Early stopping whe validation accuracy does not change for 7 iteration \nes = EarlyStopping(monitor='val_accuracy', mode='auto', patience=7,  restore_best_weights=True)\n\n#Trainign the model\nhistory = cnn_VGG16_model.fit(train_ds, y_train, epochs=30, validation_data=(train_val_df,y_val) ,callbacks=es)","metadata":{"id":"CypuyIoyjrXO","outputId":"881e8afd-69fe-46ad-b949-08be7a34b0de","execution":{"iopub.status.busy":"2022-03-27T03:40:57.130942Z","iopub.execute_input":"2022-03-27T03:40:57.131681Z","iopub.status.idle":"2022-03-27T03:41:14.627822Z","shell.execute_reply.started":"2022-03-27T03:40:57.131641Z","shell.execute_reply":"2022-03-27T03:41:14.627102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_ds = preprocess_input(X_test1) \nfcl_loss, fcl_accuracy = cnn_VGG16_model.evaluate(test_ds, y_test, verbose=1)\nprint('Test loss:', fcl_loss)\nprint('Test accuracy:', fcl_accuracy)\n\n## The Test accuracy score if 64% but the training accurayc is 100 , this model is overfit","metadata":{"id":"NetEeVOhjrXO","outputId":"addc342c-a630-467a-95b9-ea82b5bfb5b9","execution":{"iopub.status.busy":"2022-03-27T03:43:39.581287Z","iopub.execute_input":"2022-03-27T03:43:39.581899Z","iopub.status.idle":"2022-03-27T03:43:40.129560Z","shell.execute_reply.started":"2022-03-27T03:43:39.581857Z","shell.execute_reply":"2022-03-27T03:43:40.128450Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"resultsDf1 = pd.concat([resultDF, createResultDf(\"CNN With VGG16\",0.9571,fcl_accuracy)])\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:43:46.344230Z","iopub.execute_input":"2022-03-27T03:43:46.344495Z","iopub.status.idle":"2022-03-27T03:43:46.350881Z","shell.execute_reply.started":"2022-03-27T03:43:46.344466Z","shell.execute_reply":"2022-03-27T03:43:46.350219Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import recall_score, confusion_matrix, precision_score, f1_score, accuracy_score, roc_auc_score,classification_report\nfrom sklearn.metrics import classification_report\n\nY_truepred = np.argmax(y_test,axis = 1) \n\nY_testPred = cnn.predict(test_ds)\n# Convert predictions classes to one hot vectors \nY_pred_classes = np.argmax(Y_pred,axis = 1) \n\nreportData = classification_report(Y_truepred, Y_pred_classes,output_dict=True)\n\nfor data in reportData:\n    if(data == '-1' or data == '1'):\n        if(type(reportData[data]) is dict):\n            for subData in reportData[data]:\n                resultsDf1[data+\"_\"+subData] = reportData[data][subData]\n\nresultsDf1","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:43:51.069999Z","iopub.execute_input":"2022-03-27T03:43:51.070262Z","iopub.status.idle":"2022-03-27T03:43:51.495051Z","shell.execute_reply.started":"2022-03-27T03:43:51.070232Z","shell.execute_reply":"2022-03-27T03:43:51.494316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**CNN with ResNet50**","metadata":{"id":"qWHwlrSXn5nC"}},{"cell_type":"code","source":"from tensorflow.keras.applications.resnet50 import ResNet50 \nfrom tensorflow.keras.applications.resnet50 import preprocess_input\nfrom tensorflow.keras import layers, models\n\nresnet_base_model = ResNet50(include_top=False, weights='imagenet', input_shape=X_train[0].shape)\n#model = VGG16(include_top=False, weights='imagenet', input_shape=(256,256,3))\n\ntrain_ds = preprocess_input(X_train1) \ntrain_val_df = preprocess_input(X_val1)\n\n\nflatten_layer = layers.Flatten()\ndense_layer_1 = layers.Dense(50, activation='relu')\ndense_layer_2 = layers.Dense(32, activation='relu')\nprediction_layer = layers.Dense(3, activation='softmax')\n\ncnn_resnet_model = models.Sequential([\n    resnet_base_model,\n    flatten_layer,\n    dense_layer_1,\n    dense_layer_2,\n    prediction_layer\n])","metadata":{"id":"DYdfxGAKjrXP","outputId":"6c617539-039a-48ed-9c6f-b00f31fc3b25","execution":{"iopub.status.busy":"2022-03-27T03:43:59.025263Z","iopub.execute_input":"2022-03-27T03:43:59.025806Z","iopub.status.idle":"2022-03-27T03:44:02.049139Z","shell.execute_reply.started":"2022-03-27T03:43:59.025769Z","shell.execute_reply":"2022-03-27T03:44:02.048370Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping\n\ncnn_resnet_model.compile(\n    optimizer='Adam',\n    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n    metrics=['accuracy'],\n)\nhistory =cnn_resnet_model.fit(train_ds, y_train, epochs=30, validation_data=(train_val_df,y_val))","metadata":{"id":"iC19vMMwjrXP","outputId":"912ff3b3-1b63-4bbc-e96c-ed9ffbe85c43","execution":{"iopub.status.busy":"2022-03-27T03:44:05.384190Z","iopub.execute_input":"2022-03-27T03:44:05.384601Z","iopub.status.idle":"2022-03-27T03:46:22.073480Z","shell.execute_reply.started":"2022-03-27T03:44:05.384559Z","shell.execute_reply":"2022-03-27T03:46:22.072664Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fcl_loss, fcl_accuracy = cnn_resnet_model.evaluate(test_ds, y_test, verbose=1)\nprint('Test loss:', fcl_loss)\nprint('Test accuracy:', fcl_accuracy)\n\n## Thw accuracy of test score is 58% abut training accuracy is 99% again it is an overfit model","metadata":{"id":"WnRZm_q7jrXQ","outputId":"5af376b9-b408-4839-80d4-5b2b540014f5","execution":{"iopub.status.busy":"2022-03-27T03:46:44.662651Z","iopub.execute_input":"2022-03-27T03:46:44.662904Z","iopub.status.idle":"2022-03-27T03:46:45.202864Z","shell.execute_reply.started":"2022-03-27T03:46:44.662877Z","shell.execute_reply":"2022-03-27T03:46:45.202117Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"resultsDf2 = pd.concat([resultsDf1, createResultDf(\"CNN With ResNet50\",history.history['accuracy'][-1],fcl_accuracy)])\n","metadata":{"id":"C651kK3ejrXQ","execution":{"iopub.status.busy":"2022-03-27T03:46:47.877250Z","iopub.execute_input":"2022-03-27T03:46:47.877728Z","iopub.status.idle":"2022-03-27T03:46:47.884412Z","shell.execute_reply.started":"2022-03-27T03:46:47.877692Z","shell.execute_reply":"2022-03-27T03:46:47.883727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import recall_score, confusion_matrix, precision_score, f1_score, accuracy_score, roc_auc_score,classification_report\nfrom sklearn.metrics import classification_report\n\nY_truepred = np.argmax(y_test,axis = 1) \n\nY_testPred = cnn.predict(test_ds)\n# Convert predictions classes to one hot vectors \nY_pred_classes = np.argmax(Y_pred,axis = 1) \n\nreportData = classification_report(Y_truepred, Y_pred_classes,output_dict=True)\n\nfor data in reportData:\n    if(data == '-1' or data == '1'):\n        if(type(reportData[data]) is dict):\n            for subData in reportData[data]:\n                resultsDf2[data+\"_\"+subData] = reportData[data][subData]\n\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:46:53.789512Z","iopub.execute_input":"2022-03-27T03:46:53.790073Z","iopub.status.idle":"2022-03-27T03:46:54.081023Z","shell.execute_reply.started":"2022-03-27T03:46:53.790036Z","shell.execute_reply":"2022-03-27T03:46:54.080314Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nimport itertools\nplt.subplots(figsize=(22,7)) #set the size of the plot \n\ndef plot_confusion_matrix(cm, classes,\n                          normalize=False,\n                          title='Confusion matrix',\n                          cmap=plt.cm.Blues):\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar()\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45)\n    plt.yticks(tick_marks, classes)\n\n    if normalize:\n        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, cm[i, j],\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n\n    \n# Predict the values from the validation dataset\nY_pred = cnn_resnet_model.predict(test_ds)\n# Convert predictions classes to one hot vectors \nY_pred_classes = np.argmax(Y_pred,axis = 1) \n# Convert validation observations to one hot vectors\nY_true = np.argmax(y_test,axis = 1) \n# compute the confusion matrix\nconfusion_mtx = confusion_matrix(Y_true, Y_pred_classes) \n# plot the confusion matrix\nplot_confusion_matrix(confusion_mtx, classes = range(3))","metadata":{"id":"2kYsDpkxjrXQ","outputId":"29dcb004-63fc-422a-9b91-86fd6251b4e7","execution":{"iopub.status.busy":"2022-03-27T03:46:58.493510Z","iopub.execute_input":"2022-03-27T03:46:58.494100Z","iopub.status.idle":"2022-03-27T03:47:00.077426Z","shell.execute_reply.started":"2022-03-27T03:46:58.494063Z","shell.execute_reply":"2022-03-27T03:47:00.076739Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"resultsDf2.head()\n## with transfer learning we have the issue of overfitting, it does well in the training data set but not well in test\n## Without TRansfer learning , the accuracy is not high but the model is general\n## We will need to pre process tha image further and take more sample to conclude ","metadata":{"id":"kFnfRw8pjrXR","outputId":"83cc6e60-7ff0-4467-8418-fdbde6159795","execution":{"iopub.status.busy":"2022-03-27T03:47:05.089616Z","iopub.execute_input":"2022-03-27T03:47:05.090112Z","iopub.status.idle":"2022-03-27T03:47:05.102118Z","shell.execute_reply.started":"2022-03-27T03:47:05.090053Z","shell.execute_reply":"2022-03-27T03:47:05.101464Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Bounding Box Prediction : UNet","metadata":{}},{"cell_type":"code","source":"labels = pd.read_csv(\"/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_labels.csv\")\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:17.335339Z","iopub.execute_input":"2022-03-27T03:47:17.335876Z","iopub.status.idle":"2022-03-27T03:47:17.376224Z","shell.execute_reply.started":"2022-03-27T03:47:17.335840Z","shell.execute_reply":"2022-03-27T03:47:17.375576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Getting the first 5000 data set for traingin and the next 5000 for validations\ntrain_CombinedData = labels[0:5000]\nvalidate_CombinedData = labels[5000:10000]\n\ntrain_CombinedData.fillna(0, inplace=True)\nvalidate_CombinedData.fillna(0, inplace=True)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:19.122417Z","iopub.execute_input":"2022-03-27T03:47:19.122855Z","iopub.status.idle":"2022-03-27T03:47:19.137843Z","shell.execute_reply.started":"2022-03-27T03:47:19.122811Z","shell.execute_reply":"2022-03-27T03:47:19.136967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Checking training data distuburtion , they have almos the same target distubution\ntrain_CombinedData.Target.value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:26.785738Z","iopub.execute_input":"2022-03-27T03:47:26.785988Z","iopub.status.idle":"2022-03-27T03:47:26.792619Z","shell.execute_reply.started":"2022-03-27T03:47:26.785959Z","shell.execute_reply":"2022-03-27T03:47:26.791918Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ** Creating Custom Train Generator. This will read the files in batches of 10 while training the model**","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications.mobilenet import preprocess_input \n\n#\nBATCH_SIZE = 10\n\n## Image Size to be scaled\nIMAGE_SIZE = 224\n\n\n## Actual Image size \nIMG_WIDTH = 1024\nIMG_HEIGHT = 1024\n\n\nclass TrainGenerator(Sequence):\n\n    def __init__(self,  _labels):       \n        self.pids = _labels[\"patientId\"].to_numpy()\n        self.coords = _labels[[\"x\", \"y\", \"width\", \"height\"]].to_numpy()\n        self.coords = self.coords * IMAGE_SIZE / IMG_WIDTH\n        \n\n    def __len__(self):\n        return math.ceil(len(self.coords) / BATCH_SIZE)\n    \n    \n    \"\"\"\n    The contrast of an image is enhanced when various shades in the image becomes more distinct.\n    We can do so by darkening the shades of the darker pixels and vice versa. \n    This is equivalent to widening the range of pixel intensities. To have a good contrast, \n    the following histogram characteristics are desirable:\n\n    1) the pixel intensities are uniformly distributed across the full range of values (each intensity value is equally probable), and\n    2) the cumulative histogram is increasing linearly across the full intensity range.\n\n    Histogram equalization modifies the distribution of pixel intensities to achieve these characteristics.\n    \"\"\"\n\n    def __doHistogramEqualization(self,img):\n        # Pre processing Histogram equalization\n        histogram_array = np.bincount(img.flatten(), minlength=256)\n        #normalize\n        num_pixels = np.sum(histogram_array)\n        histogram_array = histogram_array/num_pixels\n        #normalized cumulative histogram\n        chistogram_array = np.cumsum(histogram_array)\n        \"\"\"\n        STEP 2: Pixel mapping lookup table\n        \"\"\"\n        transform_map = np.floor(255 * chistogram_array).astype(np.uint8)\n        \"\"\"\n        STEP 3: Transformation\n        \"\"\"\n        img_list = list(img.flatten())\n\n        # transform pixel values to equalize\n        eq_img_list = [transform_map[p] for p in img_list]\n\n        # reshape and write back into img_array\n        img = np.reshape(np.asarray(eq_img_list), img.shape)\n\n        return img\n\n    def __getitem__(self, idx): # Get a batch\n        batch_coords = self.coords[idx * BATCH_SIZE:(idx + 1) * BATCH_SIZE] # Image coords\n        batch_pids = self.pids[idx * BATCH_SIZE:(idx + 1) * BATCH_SIZE] # Image pids    \n        \n        batch_images = np.zeros((len(batch_pids), IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.float32)\n        batch_masks = np.zeros((len(batch_pids), IMAGE_SIZE, IMAGE_SIZE))\n        for _indx, _pid in enumerate(batch_pids):\n            _path = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format(_pid)\n            _imgData = dcm.read_file(_path)\n\n            img = _imgData.pixel_array \n#             img = np.stack((img,)*3, axis=-1) # Expand grayscale image to contain 3 channels\n\n            # Resize image\n            resized_img = cv2.resize(img,(IMAGE_SIZE,IMAGE_SIZE), interpolation = cv2.INTER_AREA)\n            \n            resized_img = self.__doHistogramEqualization(resized_img)\n        \n            batch_images[_indx][:,:,0] = preprocess_input(np.array(resized_img[:,:], dtype=np.float32)) \n            batch_images[_indx][:,:,1] = preprocess_input(np.array(resized_img[:,:], dtype=np.float32)) \n            batch_images[_indx][:,:,2] = preprocess_input(np.array(resized_img[:,:], dtype=np.float32)) \n            x = int(batch_coords[_indx, 0])\n            y = int(batch_coords[_indx, 1])\n            width = int(batch_coords[_indx, 2])\n            height = int(batch_coords[_indx, 3])\n            \n            batch_masks[_indx][y:y+height, x:x+width] = 1\n\n        return batch_images, batch_masks","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:29.563845Z","iopub.execute_input":"2022-03-27T03:47:29.564591Z","iopub.status.idle":"2022-03-27T03:47:29.581657Z","shell.execute_reply.started":"2022-03-27T03:47:29.564546Z","shell.execute_reply":"2022-03-27T03:47:29.580888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainUNetDataGen = TrainGenerator( train_CombinedData)\nvalidateUNetDataGen = TrainGenerator( validate_CombinedData)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:37.506900Z","iopub.execute_input":"2022-03-27T03:47:37.507154Z","iopub.status.idle":"2022-03-27T03:47:37.515539Z","shell.execute_reply.started":"2022-03-27T03:47:37.507125Z","shell.execute_reply":"2022-03-27T03:47:37.514692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# To show image with mask\ndef showMaskedImage(_imageSet, _maskSet, _index) :\n    maskImage = _imageSet[_index]\n\n    maskImage[:,:,0] = _maskSet[_index] * _imageSet[_index][:,:,0]\n    maskImage[:,:,1] = _maskSet[_index] * _imageSet[_index][:,:,1]\n    maskImage[:,:,2] = _maskSet[_index] * _imageSet[_index][:,:,2]\n\n    plt.imshow(maskImage[:,:,0])","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:39.201931Z","iopub.execute_input":"2022-03-27T03:47:39.202584Z","iopub.status.idle":"2022-03-27T03:47:39.210385Z","shell.execute_reply.started":"2022-03-27T03:47:39.202547Z","shell.execute_reply":"2022-03-27T03:47:39.209760Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## One of the pre processed image from custom train generotr\nimageSet0 = trainUNetDataGen[1][0][1]\nplt.imshow(imageSet0)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:43.474910Z","iopub.execute_input":"2022-03-27T03:47:43.475158Z","iopub.status.idle":"2022-03-27T03:47:43.997892Z","shell.execute_reply.started":"2022-03-27T03:47:43.475129Z","shell.execute_reply":"2022-03-27T03:47:43.997229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Masks for the same\nimageSet0 = trainUNetDataGen[2][0]\nmaskSet0 = trainUNetDataGen[2][1]    \nshowMaskedImage(imageSet0, maskSet0, 5)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:48.474995Z","iopub.execute_input":"2022-03-27T03:47:48.475600Z","iopub.status.idle":"2022-03-27T03:47:49.274079Z","shell.execute_reply.started":"2022-03-27T03:47:48.475564Z","shell.execute_reply":"2022-03-27T03:47:49.273409Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LR = 1e-4","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:54.166788Z","iopub.execute_input":"2022-03-27T03:47:54.167042Z","iopub.status.idle":"2022-03-27T03:47:54.171107Z","shell.execute_reply.started":"2022-03-27T03:47:54.167014Z","shell.execute_reply":"2022-03-27T03:47:54.170342Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom glob import glob\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\n\nfrom tensorflow.keras.layers import Conv2D, Activation, BatchNormalization ,Conv2DTranspose\nfrom tensorflow.keras.layers import UpSampling2D, Input, Concatenate\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.applications import MobileNet , VGG19\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom tensorflow.keras.metrics import Recall, Precision\nfrom tensorflow.keras import backend as K\nfrom PIL import Image\nfrom numpy import asarray\nfrom tensorflow.keras.applications.mobilenet import MobileNet\nfrom tensorflow.keras.layers import Concatenate, UpSampling2D, Conv2D, Reshape, GlobalAveragePooling2D\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:47:55.950846Z","iopub.execute_input":"2022-03-27T03:47:55.951588Z","iopub.status.idle":"2022-03-27T03:47:55.960136Z","shell.execute_reply.started":"2022-03-27T03:47:55.951536Z","shell.execute_reply":"2022-03-27T03:47:55.959368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**UNET USING MobileNet**","metadata":{}},{"cell_type":"code","source":"ALPHA = 1.0\ndef create_UNetModelUsingMobileNet(trainable=True):\n    \"\"\"Function to create UNet architecture with MobileNet.\n        \n    Arguments:\n        trainable -- Flag to make layers trainable. Default value is 'True'.\n    \"\"\"\n    # Get all layers with 'imagenet' weights\n    model = MobileNet(input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3), include_top=False, alpha=ALPHA, weights=\"imagenet\") \n    # Top layer is last layer of the model\n    \n    # Make all layers trainable\n    for layer in model.layers:\n        layer.trainable = trainable\n\n    # Add all the UNET layers here\n    convLayer_112by112 = model.get_layer(\"conv_pw_1_relu\").output\n    convLayer_56by56 = model.get_layer(\"conv_pw_3_relu\").output\n    convLayer_28by28 = model.get_layer(\"conv_pw_5_relu\").output\n    convLayer_14by14 = model.get_layer(\"conv_pw_11_relu\").output\n    convLayer_7by7 = model.get_layer(\"conv_pw_13_relu\").output\n    # The last layer of mobilenet model is of dimensions (7x7x1024)\n\n    # Start upsampling from 7x7 to 14x14 ...up to 224x224 to form UNET\n    # concatinate with the original image layer of the same size from MobileNet\n    x = Concatenate()([UpSampling2D()(convLayer_7by7), convLayer_14by14])\n    x = Concatenate()([UpSampling2D()(x), convLayer_28by28])\n    x = Concatenate()([UpSampling2D()(x), convLayer_56by56])\n    x = Concatenate()([UpSampling2D()(x), convLayer_112by112])\n    x = UpSampling2D(name=\"unet_last\")(x) # upsample to 224x224\n\n    # Add classification layer\n    x = Conv2D(1, kernel_size=1, activation=\"sigmoid\", name=\"masks\")(x)\n    x = Reshape((IMAGE_SIZE, IMAGE_SIZE))(x) \n\n    return Model(inputs=model.input, outputs=x)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:00.760891Z","iopub.execute_input":"2022-03-27T03:48:00.761142Z","iopub.status.idle":"2022-03-27T03:48:00.770359Z","shell.execute_reply.started":"2022-03-27T03:48:00.761109Z","shell.execute_reply":"2022-03-27T03:48:00.769674Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Build a model \ninput_shape = (IMAGE_SIZE,IMAGE_SIZE,3)\nmodel = create_UNetModelUsingMobileNet(input_shape)\nmodel.summary()","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:19.746230Z","iopub.execute_input":"2022-03-27T03:48:19.746500Z","iopub.status.idle":"2022-03-27T03:48:21.010254Z","shell.execute_reply.started":"2022-03-27T03:48:19.746471Z","shell.execute_reply":"2022-03-27T03:48:21.009582Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dice_coef 2 * the Area of Overlap divided by the total number of pixels in both images\ndef dice_coef(y_true, y_pred):\n    y_true = tf.keras.layers.Flatten()(y_true)\n    y_pred = tf.keras.layers.Flatten()(y_pred)\n    intersection = tf.reduce_sum(y_true * y_pred)\n    return (2. * intersection + tf.keras.backend.epsilon()) / (tf.reduce_sum(y_true) + tf.reduce_sum(y_pred) + tf.keras.backend.epsilon())\n\n## Loss is 1 - the coefficent of two images\ndef dice_loss(y_true, y_pred):\n    return 1.0 - dice_coef(y_true, y_pred)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:30.433345Z","iopub.execute_input":"2022-03-27T03:48:30.434229Z","iopub.status.idle":"2022-03-27T03:48:30.440533Z","shell.execute_reply.started":"2022-03-27T03:48:30.434181Z","shell.execute_reply":"2022-03-27T03:48:30.439688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Call Backs for ealy stopping and reduce learning rate \n## Reduce the learning rate when teh validation loss is same for 4 epocs\ncallbacks = [\n    ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=4),\n    EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=False)\n]","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:32.341302Z","iopub.execute_input":"2022-03-27T03:48:32.341842Z","iopub.status.idle":"2022-03-27T03:48:32.346479Z","shell.execute_reply.started":"2022-03-27T03:48:32.341804Z","shell.execute_reply":"2022-03-27T03:48:32.345583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Optimiser , metrics and loss for the model\nopt = tf.keras.optimizers.Nadam(LR)\nmetrics = [dice_coef, Recall(), Precision()]\nmodel.compile(loss=dice_loss, optimizer=opt, metrics=metrics)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:36.941717Z","iopub.execute_input":"2022-03-27T03:48:36.941976Z","iopub.status.idle":"2022-03-27T03:48:36.960637Z","shell.execute_reply.started":"2022-03-27T03:48:36.941949Z","shell.execute_reply":"2022-03-27T03:48:36.959971Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Running the model\ntrain_steps = len(trainUNetDataGen)//BATCH_SIZE\nvalid_steps = len(validateUNetDataGen)//BATCH_SIZE\n\nif len(trainUNetDataGen) % BATCH_SIZE != 0:\n    train_steps += 1\nif len(validateUNetDataGen) % BATCH_SIZE != 0:\n    valid_steps += 1\n\n\nhistory = model.fit(trainUNetDataGen,\n                        epochs=16,\n                        steps_per_epoch=train_steps,\n                        validation_data=validateUNetDataGen,\n                        callbacks=callbacks,\n                        use_multiprocessing=True,\n                        workers=4,\n                        validation_steps=valid_steps,                      \n                        shuffle=True)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T03:48:39.257549Z","iopub.execute_input":"2022-03-27T03:48:39.258216Z","iopub.status.idle":"2022-03-27T03:59:42.345708Z","shell.execute_reply.started":"2022-03-27T03:48:39.258183Z","shell.execute_reply":"2022-03-27T03:59:42.344809Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## PLot Matrix between training and validation data\nplt.figure(figsize=(8, 5))\nplt.grid(True)\nplt.plot(history.history['dice_coef'],     label='Train Dice-Coef',      color = \"green\" );\nplt.plot(history.history['val_dice_coef'], label='Val Dice-Coef',  color = \"yellow\");\nplt.plot(history.history['loss'],         label='Train Loss',          color = \"red\"   );\nplt.plot(history.history['val_loss'],     label='Val Loss',      color = \"orange\");\nplt.title(\"Validation and Training - Loss and Dice Coefficient vs Epoch\")\nplt.xlabel(\"Epoch\")\nplt.legend();","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:00:18.622075Z","iopub.execute_input":"2022-03-27T04:00:18.622350Z","iopub.status.idle":"2022-03-27T04:00:18.895826Z","shell.execute_reply.started":"2022-03-27T04:00:18.622318Z","shell.execute_reply":"2022-03-27T04:00:18.895145Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"val_dice-oefficient value is very low and pretty much a flat curve, indicating underfitting indicating model has not learnt sufficiently. It is steadily increasing, not sufficient training(more epochs needed).  \n\nWe have used an image size of 224x224 as against the original size of 1024x1024. Using a higher resolution, could also improve training capacity \n\n<li>Hyper-parameter tuning, image_augmentation, using different architectures will help in increasing model performance and generalization.\n","metadata":{}},{"cell_type":"code","source":"##Preparing test data , picked up random 20 images\ntest_CombinedData = labels[15000:15020]\ntest_CombinedData.fillna(0, inplace=True)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:00:39.477545Z","iopub.execute_input":"2022-03-27T04:00:39.478303Z","iopub.status.idle":"2022-03-27T04:00:39.484021Z","shell.execute_reply.started":"2022-03-27T04:00:39.478252Z","shell.execute_reply":"2022-03-27T04:00:39.483053Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"##Check target distrubution in test dataset, there are both the classes available with equal sdistrubution\ntest_CombinedData.Target.value_counts()","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:00:44.388649Z","iopub.execute_input":"2022-03-27T04:00:44.389467Z","iopub.status.idle":"2022-03-27T04:00:44.397363Z","shell.execute_reply.started":"2022-03-27T04:00:44.389418Z","shell.execute_reply":"2022-03-27T04:00:44.396533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Setting the custom generator for test data\ntestUNetDataGen = TrainGenerator(test_CombinedData)","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:02:21.607051Z","iopub.execute_input":"2022-03-27T04:02:21.607308Z","iopub.status.idle":"2022-03-27T04:02:21.612887Z","shell.execute_reply.started":"2022-03-27T04:02:21.607279Z","shell.execute_reply":"2022-03-27T04:02:21.612210Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## evaluating the model\ntest_steps = (len(testUNetDataGen)//BATCH_SIZE)\nif len(testUNetDataGen) % BATCH_SIZE != 0:\n    test_steps += 1\n\nmodel.evaluate(testUNetDataGen)\n\n## Model evaluation , dice coef = 50% m recall is good  wiht 61% , precission is very low , of about 43.37%","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:02:26.627856Z","iopub.execute_input":"2022-03-27T04:02:26.628588Z","iopub.status.idle":"2022-03-27T04:02:28.264248Z","shell.execute_reply.started":"2022-03-27T04:02:26.628548Z","shell.execute_reply":"2022-03-27T04:02:28.263428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Precidt the test data that we have\npred_mask = model.predict(testUNetDataGen)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:02:49.779261Z","iopub.execute_input":"2022-03-27T04:02:49.780029Z","iopub.status.idle":"2022-03-27T04:02:51.316096Z","shell.execute_reply.started":"2022-03-27T04:02:49.779978Z","shell.execute_reply":"2022-03-27T04:02:51.315321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_CombinedData = test_CombinedData.reset_index()\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:02:54.743953Z","iopub.execute_input":"2022-03-27T04:02:54.744225Z","iopub.status.idle":"2022-03-27T04:02:54.750898Z","shell.execute_reply.started":"2022-03-27T04:02:54.744195Z","shell.execute_reply":"2022-03-27T04:02:54.750126Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\ny_pred = []\ny_True = []\nimageList = []\npredMaskTemp = []\nIMAGE_HEIGHT = 224\nIMAGE_WIDTH = 224\ndef getPredictions(test_CombinedData):\n    masks = np.zeros((int(test_CombinedData.shape[0]), IMAGE_HEIGHT, IMAGE_WIDTH))\n\n    for index, row in test_CombinedData.iterrows():\n        patientId = row.patientId\n#         print(patientId)\n\n        classlabel = row[\"Target\"]\n        dcm_file = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format(patientId)\n        dcm_data = dcm.read_file(dcm_file)\n        img = dcm_data.pixel_array\n        resized_img = cv2.resize(img,(IMAGE_HEIGHT,IMAGE_WIDTH), interpolation = cv2.INTER_LINEAR)\n        predMaskTemp.append(pred_mask[index])\n        iou = (pred_mask[index] > 0.5) * 1.0\n        y_pred.append((1 in iou) * 1)\n        imageList.append(resized_img)\n        y_True.append(classlabel)\n        x_scale = IMAGE_HEIGHT / 1024\n        y_scale = IMAGE_WIDTH / 1024\n\n        if(classlabel == 1):\n            x = int(np.round(row['x'] * x_scale))\n            y = int(np.round(row['y'] * y_scale))\n            w = int(np.round(row['width'] * x_scale))\n            h = int(np.round(row['height'] * y_scale))\n            masks[index][y:y+h, x:x+w] = 1\n\n        \n        \n    tmpImages = np.array(imageList)\n    tmpMask = np.array(predMaskTemp)\n    originalMask = np.array(masks)\n    return (y_True,y_pred,tmpImages,tmpMask ,originalMask)\n    \ndef print_confusion_matrix(y_true, y_pred):\n    '''Function to print confusion_matrix'''\n\n    # Get confusion matrix array\n    array = confusion_matrix(y_true, y_pred)    \n    df_cm = pd.DataFrame(array, range(2), range(2))\n\n    print(\"Total samples = \", len(test_CombinedData))\n\n    # Plot heatmap and get sns heatmap values\n    sns.set(font_scale=1.4); # for label size\n    result = sns.heatmap(df_cm, annot=True, annot_kws={\"size\": 16}, fmt='g', cbar=False); \n\n    # Add labels to heatmap\n    labels = ['TN=','FP=','FN=','TP=']\n    i=0\n    for t in result.texts:\n        t.set_text(labels[i] + t.get_text())\n        i += 1\n    \n    plt.xlabel(\"Predicted Values\")\n    plt.ylabel('True Values')\n    plt.show()\n    return\n    \n\n    \n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:03:36.745279Z","iopub.execute_input":"2022-03-27T04:03:36.745947Z","iopub.status.idle":"2022-03-27T04:03:36.767311Z","shell.execute_reply.started":"2022-03-27T04:03:36.745911Z","shell.execute_reply":"2022-03-27T04:03:36.766495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Create predictions map\ny_true,y_pred ,imagelist , maskList , originalMask = getPredictions(test_CombinedData)\n","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:03:41.518634Z","iopub.execute_input":"2022-03-27T04:03:41.519311Z","iopub.status.idle":"2022-03-27T04:03:41.691717Z","shell.execute_reply.started":"2022-03-27T04:03:41.519273Z","shell.execute_reply":"2022-03-27T04:03:41.690988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ndcm_file = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'+'{}.dcm'.format('9358d1c5-ba61-4150-a233-41138208a3f9')\ndcm_data = dcm.read_file(dcm_file)\nimg = dcm_data.pixel_array\nplt.imshow(imagelist[12])","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:03:43.987206Z","iopub.execute_input":"2022-03-27T04:03:43.989695Z","iopub.status.idle":"2022-03-27T04:03:44.248046Z","shell.execute_reply.started":"2022-03-27T04:03:43.989652Z","shell.execute_reply":"2022-03-27T04:03:44.247426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Visualising the train and output data \n##\nfig = plt.figure(figsize=(15, 15))\n\na = fig.add_subplot(1, 4, 1)\nimgplot = plt.imshow(imagelist[1])\na.set_title('Original Images ',fontsize=20)\n\n\na = fig.add_subplot(1, 4, 2)\nimgplot = plt.imshow(imagelist[12])\n\na = fig.add_subplot(1, 4, 3)\nimgplot = plt.imshow(imagelist[13])\n\na = fig.add_subplot(1, 4, 4)\nimgplot = plt.imshow(imagelist[15])\n\n\nfig = plt.figure(figsize=(15, 15))\na = fig.add_subplot(1, 4, 1)\n\nimgplot = plt.imshow(originalMask[1])\na.set_title('Oringial Mask (Truth) ',fontsize=20)\n\na.set_xlabel('Pneumonia {}:'.format(y_true[1]), fontsize=20)\n\n\na = fig.add_subplot(1, 4, 2)\nimgplot = plt.imshow(originalMask[12])\na.set_xlabel('Pneumonia {}:'.format(y_true[12]), fontsize=20)\n\n\na = fig.add_subplot(1, 4, 3)\nimgplot = plt.imshow(originalMask[13])\na.set_xlabel('Pneumonia {}:'.format(y_true[13]), fontsize=20)\n\n\na = fig.add_subplot(1, 4, 4)\nimgplot = plt.imshow(originalMask[15])\na.set_xlabel('Pneumonia {}:'.format(y_true[15]), fontsize=20)\n\n\n\nfig = plt.figure(figsize=(15, 15))\na = fig.add_subplot(1, 4, 1)\na.set_title('Predicted Mask  ',fontsize=20)\nimgplot = plt.imshow(maskList[1])\na.set_xlabel('Pneumonia {}:'.format(y_pred[1]), fontsize=20)\n\na = fig.add_subplot(1, 4, 2)\nimgplot = plt.imshow(maskList[12])\na.set_xlabel('Pneumonia {}:'.format(y_pred[12]), fontsize=20)\n\na = fig.add_subplot(1, 4, 3)\nimgplot = plt.imshow(maskList[13])\na.set_xlabel('Pneumonia {}:'.format(y_pred[13]), fontsize=20)\n\na = fig.add_subplot(1, 4, 4)\nimgplot = plt.imshow(maskList[15])\na.set_xlabel('Pneumonia {}:'.format(y_pred[15]), fontsize=20)\n\n\n## we could see that the first  one is mis classifed\n## Second one is classified correctly , there are no masks\n## Thrid is predcited correctly and the bouding box is also almost at the same position\n## fourth is predicted correctly but there are two boudnign boxes, this culd be because there are duplicate patient id and we are picking only one mask to display inte Truth","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:03:49.197504Z","iopub.execute_input":"2022-03-27T04:03:49.197800Z","iopub.status.idle":"2022-03-27T04:03:50.998496Z","shell.execute_reply.started":"2022-03-27T04:03:49.197771Z","shell.execute_reply":"2022-03-27T04:03:50.995997Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print_confusion_matrix(y_true,y_pred )\n## There are two False Postive for Target 1","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:04:04.700827Z","iopub.execute_input":"2022-03-27T04:04:04.701091Z","iopub.status.idle":"2022-03-27T04:04:04.846308Z","shell.execute_reply.started":"2022-03-27T04:04:04.701062Z","shell.execute_reply":"2022-03-27T04:04:04.845644Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import classification_report\n\nprint(classification_report(y_true,y_pred))","metadata":{"execution":{"iopub.status.busy":"2022-03-27T04:04:13.601471Z","iopub.execute_input":"2022-03-27T04:04:13.601732Z","iopub.status.idle":"2022-03-27T04:04:13.612289Z","shell.execute_reply.started":"2022-03-27T04:04:13.601704Z","shell.execute_reply":"2022-03-27T04:04:13.611427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<font color=red> \n    <li>Recall socre is quite high for pneumonia class(target = 1), even for a very low dice-coefficient score for image mask prediction. This is a good score, because it indicates that 99% of patients who are positive are detected correctly by this model. \n    <li>Precision score is however low indicating only 83% of the predictions are correct. This is largely due to a high amount of false positives, as indicated in the confusion matrix.\n    <li>The accuracy score is  0.85%, w This probably can improve as the model trains with more number of samples, that will help it to distinguish non-pneumonia images better.","metadata":{}},{"cell_type":"markdown","source":"1)\tWe have done the EDA \n\n2)\tWe have done the pre-processing of the dicom images\n\n3)\tWe have built CNN model for classification without transfer learning and with Transfer learning\n\n4)\tCurrently with the limited sample data set that we have taken we have found the CNN model without transfer learning was the most general model even though the accuracy was not very high\n\n5)\tWe have built UNET model for regression using VGG16 Pre trained model \n\n6) We can improve the dice loss by image augmentation , increasing the sample size and hyper tuning\n","metadata":{"id":"3erDSOCroR26"}}]}