{"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":"# Chest X-ray Diagnostic \n","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport sys\nimport time\nimport json\nimport glob\nimport random\nfrom pathlib import Path\nimport pandas as pd\n\nfrom PIL import Image\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom imgaug import augmenters as iaa\n\nimport itertools\nfrom tqdm import tqdm\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\nimport warnings \nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-07-05T16:33:06.173608Z","iopub.execute_input":"2023-07-05T16:33:06.173899Z","iopub.status.idle":"2023-07-05T16:33:07.64765Z","shell.execute_reply.started":"2023-07-05T16:33:06.17386Z","shell.execute_reply":"2023-07-05T16:33:07.646854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data preparation","metadata":{}},{"cell_type":"code","source":"training_folder = \"../input/vinbigdata-chest-xray-abnormalities-detection/train/\"\ndf = pd.read_csv(\"../input/vinbigdata-chest-xray-abnormalities-detection/train.csv\")\ndf = df.query(\"class_id<14\")  #This line filters df to keep only the rows where the \"class_id\" column values are less than 14. \ndf = df.query(\"rad_id=='R9'\")  #This line filters the df to keep only the rows where the \"rad_id\" column values are equal to 'R9' where rad_id is the id of the radiologist that made the observation","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","execution":{"iopub.status.busy":"2023-07-05T16:33:07.650054Z","iopub.execute_input":"2023-07-05T16:33:07.650434Z","iopub.status.idle":"2023-07-05T16:33:07.81761Z","shell.execute_reply.started":"2023-07-05T16:33:07.650394Z","shell.execute_reply":"2023-07-05T16:33:07.816934Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[\"several_issues\"] = df.duplicated(subset=['image_id'])\ndf[\"box_size\"] = [(row.y_max-row.y_min)*(row.x_max-row.x_min) for idx, row in df.iterrows()]  #it calculates the size of the bounding box and adds ","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:07.819314Z","iopub.execute_input":"2023-07-05T16:33:07.819713Z","iopub.status.idle":"2023-07-05T16:33:09.555342Z","shell.execute_reply.started":"2023-07-05T16:33:07.819674Z","shell.execute_reply":"2023-07-05T16:33:09.554427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head(n=10)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.556931Z","iopub.execute_input":"2023-07-05T16:33:09.557405Z","iopub.status.idle":"2023-07-05T16:33:09.584015Z","shell.execute_reply.started":"2023-07-05T16:33:09.557351Z","shell.execute_reply":"2023-07-05T16:33:09.583227Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.class_name.unique()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.587698Z","iopub.execute_input":"2023-07-05T16:33:09.588048Z","iopub.status.idle":"2023-07-05T16:33:09.597396Z","shell.execute_reply.started":"2023-07-05T16:33:09.588013Z","shell.execute_reply":"2023-07-05T16:33:09.595926Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.groupby(\"class_id\")[\"box_size\"].mean()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.601008Z","iopub.execute_input":"2023-07-05T16:33:09.60141Z","iopub.status.idle":"2023-07-05T16:33:09.61294Z","shell.execute_reply.started":"2023-07-05T16:33:09.601372Z","shell.execute_reply":"2023-07-05T16:33:09.612224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.groupby(\"class_id\")[\"box_size\"].std()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.615629Z","iopub.execute_input":"2023-07-05T16:33:09.615896Z","iopub.status.idle":"2023-07-05T16:33:09.62495Z","shell.execute_reply.started":"2023-07-05T16:33:09.61587Z","shell.execute_reply":"2023-07-05T16:33:09.623877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.groupby(\"class_id\").image_id.count()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.626487Z","iopub.execute_input":"2023-07-05T16:33:09.627068Z","iopub.status.idle":"2023-07-05T16:33:09.638177Z","shell.execute_reply.started":"2023-07-05T16:33:09.627021Z","shell.execute_reply":"2023-07-05T16:33:09.636824Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"After checking the box sizes per abnormality, their standard deviation, and the number of examples, I decided to pick the 5 abnormalities below. I am carefully picking abnormalities with bounding boxes large enough as I will be significantly downsizing the images.","metadata":{}},{"cell_type":"code","source":"selected_classes = [0,3,5,7,10]\ncategory_list = [\"Aortic enlargement\", \"Cardiomegaly\", \"ILD\", \"Lung Opacity\", \"Pleural effusion\"]\nfiltered_df = df.query(\"class_id in @selected_classes\")","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.639637Z","iopub.execute_input":"2023-07-05T16:33:09.640155Z","iopub.status.idle":"2023-07-05T16:33:09.650498Z","shell.execute_reply.started":"2023-07-05T16:33:09.640118Z","shell.execute_reply":"2023-07-05T16:33:09.649695Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"selected_classes_dict = {\"0\":0,\"3\":1,\"5\":2,\"7\":3,\"10\":4}\nfiltered_df[\"reclass_id\"] = [selected_classes_dict[str(row.class_id)] for idx, row in filtered_df.iterrows()]","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:09.652055Z","iopub.execute_input":"2023-07-05T16:33:09.652534Z","iopub.status.idle":"2023-07-05T16:33:10.288436Z","shell.execute_reply.started":"2023-07-05T16:33:09.652496Z","shell.execute_reply":"2023-07-05T16:33:10.287525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"filtered_df","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.289892Z","iopub.execute_input":"2023-07-05T16:33:10.290272Z","iopub.status.idle":"2023-07-05T16:33:10.321549Z","shell.execute_reply.started":"2023-07-05T16:33:10.290234Z","shell.execute_reply":"2023-07-05T16:33:10.320684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The 2 functions below allows to go from bounding boxes to the right format for a MaskRCNN.","metadata":{}},{"cell_type":"code","source":"def get_mask(img_dimensions, x_min, y_min, x_max, y_max):\n    img_height, img_width = img_dimensions\n    img_mask = np.full((img_height,img_width),0)\n    img_mask[y_min:y_max,x_min:x_max] = 255\n    \n    return img_mask.astype(np.float32)\n\n#get_mask function takes the image dimensions as a tuple, \n#along with the bounding box coordinates (x_min, y_min, x_max, y_max). \n#It creates a mask image (img_mask) with the same dimensions as the original\n#image, initialized with all zeros. The region defined by the bounding box coordinates is filled with the value 255. \n#get_mask returns the mask image as a numy array\n\ndef rle_encoding(x):  #this function performs run length encoding on the mask image obtained from get_mask method.\n    dots = np.where(x.T.flatten() == 255)[0]\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if (b>prev+1): run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return ' '.join([str(x) for x in run_lengths])\n\n","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.323214Z","iopub.execute_input":"2023-07-05T16:33:10.323591Z","iopub.status.idle":"2023-07-05T16:33:10.333516Z","shell.execute_reply.started":"2023-07-05T16:33:10.323554Z","shell.execute_reply":"2023-07-05T16:33:10.33245Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The following function helps to load the images in the cleanest way possible.","metadata":{}},{"cell_type":"code","source":"def read_xray(path, voi_lut = True, fix_monochrome = True):\n    dicom = pydicom.read_file(path) #PyDicom is used for working with DICOM files such as medical images or radiotherapy objects. \n    \n    # VOI LUT (if available by DICOM device) is used to transform raw DICOM data to \"human-friendly\" view\n    if voi_lut:\n        data = apply_voi_lut(dicom.pixel_array, dicom)\n    else:\n        data = dicom.pixel_array\n    \n    # depending on this value, X-ray may look inverted so in order to  fix that:\n    if fix_monochrome and dicom.PhotometricInterpretation == \"MONOCHROME1\":   #fix_monochrome tells whether to fix the inversion or not and dicom.PhotometricInterpretation is to verify that the image is monochromatic or not. \n        data = np.amax(data) - data    #performing digital negative of an image by subtracting highest pixel value with each and every pixel\n    #normalization and scaling operations are performed in the below lines of code.\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-07-05T16:33:10.334846Z","iopub.execute_input":"2023-07-05T16:33:10.335464Z","iopub.status.idle":"2023-07-05T16:33:10.347122Z","shell.execute_reply.started":"2023-07-05T16:33:10.335427Z","shell.execute_reply":"2023-07-05T16:33:10.346383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"resized_folder = \"../working/resized_train/\"\nos.mkdir(resized_folder)   ","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.348987Z","iopub.execute_input":"2023-07-05T16:33:10.349369Z","iopub.status.idle":"2023-07-05T16:33:10.359961Z","shell.execute_reply.started":"2023-07-05T16:33:10.349314Z","shell.execute_reply":"2023-07-05T16:33:10.359171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"filtered_df.groupby(\"class_id\").image_id.count()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.361504Z","iopub.execute_input":"2023-07-05T16:33:10.3619Z","iopub.status.idle":"2023-07-05T16:33:10.373602Z","shell.execute_reply.started":"2023-07-05T16:33:10.361861Z","shell.execute_reply":"2023-07-05T16:33:10.372697Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"balanced_filtered_df = pd.DataFrame()\nsamples_per_class = 500\nfor class_name in filtered_df.class_name.unique():\n    balanced_filtered_df = balanced_filtered_df.append(filtered_df.query(\"class_name==@class_name\")[:samples_per_class], \n                                                       ignore_index=True)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.377002Z","iopub.execute_input":"2023-07-05T16:33:10.377339Z","iopub.status.idle":"2023-07-05T16:33:10.416092Z","shell.execute_reply.started":"2023-07-05T16:33:10.377288Z","shell.execute_reply":"2023-07-05T16:33:10.415331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"balanced_filtered_df  #x_min,y_min,x_max,y_max are the coordinates of the bounding box around the abnormality.","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.417595Z","iopub.execute_input":"2023-07-05T16:33:10.417958Z","iopub.status.idle":"2023-07-05T16:33:10.44839Z","shell.execute_reply.started":"2023-07-05T16:33:10.417923Z","shell.execute_reply":"2023-07-05T16:33:10.447505Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"balanced_filtered_df.head(n=201)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.450142Z","iopub.execute_input":"2023-07-05T16:33:10.450677Z","iopub.status.idle":"2023-07-05T16:33:10.48027Z","shell.execute_reply.started":"2023-07-05T16:33:10.45064Z","shell.execute_reply":"2023-07-05T16:33:10.479252Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Here each row in dataset is iterated over, processes each image and its corresponding diagnostic information, \n#and creates a list of dictionaries (diagnostic_per_image) containing the image_id, associated class labels (CategoryId), \n#and encoded mask information (EncodedPixels) for each image.\ndiagnostic_per_image = []\n\nimage_size=512\nwith tqdm(total=len(balanced_filtered_df)) as pbar:  #tqdm to display progress bar while processing the image.\n    for idx,row in balanced_filtered_df.iterrows(): #iterrows method is to iterate over all the rows in the dataframe.\n        image_id = row.image_id\n        image_df = balanced_filtered_df.query(\"image_id==@image_id\")\n        class_list = []\n        RLE_list = []\n        \n        for diagnostic_id, diagnostic in image_df.iterrows():\n            class_list.append(diagnostic.reclass_id)\n\n            dicom_image = read_xray(training_folder+image_id+\".dicom\")\n            image_dimensions = dicom_image.shape\n            \n            resized_img = cv2.resize(dicom_image, (image_size,image_size), interpolation = cv2.INTER_AREA)\n            cv2.imwrite(resized_folder+image_id+\".jpg\", resized_img) \n            \n            mask = get_mask(image_dimensions, int(diagnostic.x_min), int(diagnostic.y_min), int(diagnostic.x_max), int(diagnostic.y_max))\n            resized_mask = cv2.resize(mask, (image_size,image_size))\n            RLE_list.append(rle_encoding(resized_mask))\n        diagnostic_per_image.append({\"image_id\":image_id,\n                                     \"CategoryId\":class_list,\n                                     \"EncodedPixels\":RLE_list})\n        pbar.update(1)  ","metadata":{"execution":{"iopub.status.busy":"2023-07-05T16:33:10.482017Z","iopub.execute_input":"2023-07-05T16:33:10.482544Z","iopub.status.idle":"2023-07-05T17:40:58.788942Z","shell.execute_reply.started":"2023-07-05T16:33:10.482505Z","shell.execute_reply":"2023-07-05T17:40:58.788027Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"samples_df = pd.DataFrame(diagnostic_per_image)\nsamples_df[\"Height\"] = image_size\nsamples_df[\"Width\"] = image_size","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:40:58.793123Z","iopub.execute_input":"2023-07-05T17:40:58.795367Z","iopub.status.idle":"2023-07-05T17:40:58.811501Z","shell.execute_reply.started":"2023-07-05T17:40:58.795308Z","shell.execute_reply":"2023-07-05T17:40:58.810468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"samples_df","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:40:58.816116Z","iopub.execute_input":"2023-07-05T17:40:58.818389Z","iopub.status.idle":"2023-07-05T17:40:58.852133Z","shell.execute_reply.started":"2023-07-05T17:40:58.818345Z","shell.execute_reply":"2023-07-05T17:40:58.851311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training And Fine-Tuning a Mask-RCNN model","metadata":{}},{"cell_type":"code","source":"!cp -r ../input/maskrcnn-tf2-keras ../working/maskrcnn-tf2-keras","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:40:58.856134Z","iopub.execute_input":"2023-07-05T17:40:58.85835Z","iopub.status.idle":"2023-07-05T17:40:59.904231Z","shell.execute_reply.started":"2023-07-05T17:40:58.858296Z","shell.execute_reply":"2023-07-05T17:40:59.903006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"DATA_DIR = Path('../working/')\nROOT_DIR = \"../../working\"\n\nNUM_CATS = len(selected_classes)\nIMAGE_SIZE = 512\nos.chdir('../working/maskrcnn-tf2-keras')\nsys.path.append(ROOT_DIR+'/maskrcnn-tf2-keras')\nfrom mrcnn.config import Config\n\nfrom mrcnn import utils\nimport mrcnn.model as modellib\nfrom mrcnn import visualize\nfrom mrcnn.model import log","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:40:59.906053Z","iopub.execute_input":"2023-07-05T17:40:59.906463Z","iopub.status.idle":"2023-07-05T17:41:03.909069Z","shell.execute_reply.started":"2023-07-05T17:40:59.906417Z","shell.execute_reply":"2023-07-05T17:41:03.908124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"COCO_WEIGHTS_PATH = '/kaggle/input/mask-rcnn-cocoh5/mask_rcnn_coco.h5'\n#The DiagnosticConfig class isubclass of Config, which is the base configuration class for the Mask R-CNN model.\nclass DiagnosticConfig(Config):\n    NAME = \"Diagnostic\"\n    NUM_CLASSES = NUM_CATS + 1 # +1 for the background class\n    \n    GPU_COUNT = 1\n    IMAGES_PER_GPU = 2 #That is the maximum with the memory available on kernels\n    \n    BACKBONE = 'resnet50'   #for feature extraction\n    \n    IMAGE_MIN_DIM = IMAGE_SIZE    \n    IMAGE_MAX_DIM = IMAGE_SIZE    \n    IMAGE_RESIZE_MODE = 'none'\n\n    POST_NMS_ROIS_TRAINING = 250\n    POST_NMS_ROIS_INFERENCE = 150\n    MAX_GROUNDTRUTH_INSTANCES = 5\n    BACKBONE_STRIDES = [4, 8, 16, 32, 64]\n    BACKBONESHAPE = (8, 16, 24, 32, 48)\n    RPN_ANCHOR_SCALES = (8,16,24,32,48)\n    ROI_POSITIVE_RATIO = 0.33\n    DETECTION_MAX_INSTANCES = 300\n    DETECTION_MIN_CONFIDENCE = 0.7    \n    # STEPS_PER_EPOCH should be the number of instances \n    # divided by (GPU_COUNT*IMAGES_PER_GPU), and so should VALIDATION_STEPS;\n    # however, due to the time limit, I set them so that this kernel can be run in 9 hours\n    STEPS_PER_EPOCH = int(len(samples_df)*0.9/IMAGES_PER_GPU)\n    VALIDATION_STEPS = len(samples_df)-int(len(samples_df)*0.9/IMAGES_PER_GPU)   \n    \nconfig = DiagnosticConfig()\nconfig.display()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:03.910622Z","iopub.execute_input":"2023-07-05T17:41:03.910981Z","iopub.status.idle":"2023-07-05T17:41:03.926507Z","shell.execute_reply.started":"2023-07-05T17:41:03.910943Z","shell.execute_reply":"2023-07-05T17:41:03.925182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DiagnosticDataset(utils.Dataset):  #The DiagnosticDataset class is defined as a subclass of utils.Dataset, which is a base class provided by the Mask R-CNN library for creating custom datasets.\n    def __init__(self, df):\n        super().__init__(self)\n        \n        # This Adds classes\n        for i, name in enumerate(category_list):\n            self.add_class(\"diagnostic\", i+1, name)\n        \n        # This Adds images \n        for i, row in df.iterrows():\n            self.add_image(\"diagnostic\", \n                           image_id=row.name,\n                           path=\"../\"+resized_folder+str(row.image_id)+\".jpg\", \n                           labels=row['CategoryId'],\n                           annotations=row['EncodedPixels'], \n                           height=row['Height'], width=row['Width'])\n\n    def image_reference(self, image_id):  #The image_reference method is overridden to provide a reference to an image. Given an image_id, it retrieves the image information from self.image_info and returns the image path and a list of corresponding class labels.\n        info = self.image_info[image_id]\n        return info['path'], [category_list[int(x)] for x in info['labels']]\n    \n    def load_image(self, image_id):   #it loads an image from the specified image path using cv2.imread() and returns the loaded image.\n        return cv2.imread(self.image_info[image_id]['path'])\n\n    def load_mask(self, image_id):\n        info = self.image_info[image_id]\n                \n        mask = np.zeros((IMAGE_SIZE, IMAGE_SIZE, len(info['annotations'])), dtype=np.uint8)\n        labels = []\n        \n        for m, (annotation, label) in enumerate(zip(info['annotations'], info['labels'])):\n            sub_mask = np.full(info['height']*info['width'], 0, dtype=np.uint8)\n            annotation = [int(x) for x in annotation.split(' ')]\n            \n            for i, start_pixel in enumerate(annotation[::2]):\n                sub_mask[start_pixel: start_pixel+annotation[2*i+1]] = 1\n\n            sub_mask = sub_mask.reshape((info['height'], info['width']), order='F')\n            sub_mask = cv2.resize(sub_mask, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_NEAREST)\n            \n            mask[:, :, m] = sub_mask\n            labels.append(int(label)+1)\n        return mask, np.array(labels)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:03.92844Z","iopub.execute_input":"2023-07-05T17:41:03.928983Z","iopub.status.idle":"2023-07-05T17:41:03.951363Z","shell.execute_reply.started":"2023-07-05T17:41:03.928944Z","shell.execute_reply":"2023-07-05T17:41:03.950404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"training_percentage = 0.9  #percentage of data to be used for training\n\ntraining_set_size = int(training_percentage*len(samples_df))\nvalidation_set_size = int((1-training_percentage)*len(samples_df))\n\ntrain_dataset = DiagnosticDataset(samples_df[:training_set_size])\ntrain_dataset.prepare()\n\nvalid_dataset = DiagnosticDataset(samples_df[training_set_size:training_set_size+validation_set_size])\nvalid_dataset.prepare()\n\nfor i in range(10):\n    image_id = random.choice(train_dataset.image_ids)  #for every loop a random image is chosen.\n    image = train_dataset.load_image(image_id)      \n    mask, class_ids = train_dataset.load_mask(image_id)\n    \n    visualize.display_top_masks(image, mask, class_ids, train_dataset.class_names, limit=5)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:03.955949Z","iopub.execute_input":"2023-07-05T17:41:03.956246Z","iopub.status.idle":"2023-07-05T17:41:07.317832Z","shell.execute_reply.started":"2023-07-05T17:41:03.956219Z","shell.execute_reply":"2023-07-05T17:41:07.316849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!wget --quiet https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:07.319687Z","iopub.execute_input":"2023-07-05T17:41:07.320109Z","iopub.status.idle":"2023-07-05T17:41:22.025579Z","shell.execute_reply.started":"2023-07-05T17:41:07.320067Z","shell.execute_reply":"2023-07-05T17:41:22.024391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LR = 1e-4\nEPOCHS = [1,18]   #best results at epoch=18 or greater than that.\n\nmodel = modellib.MaskRCNN(mode='training', config=config, model_dir=\"\")\nmodel.load_weights(COCO_WEIGHTS_PATH, by_name=True, exclude=['mrcnn_class_logits', 'mrcnn_bbox_fc', 'mrcnn_bbox', 'mrcnn_mask'])","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:22.027389Z","iopub.execute_input":"2023-07-05T17:41:22.027789Z","iopub.status.idle":"2023-07-05T17:41:35.786149Z","shell.execute_reply.started":"2023-07-05T17:41:22.027737Z","shell.execute_reply":"2023-07-05T17:41:35.7854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nmodel.train(train_dataset, valid_dataset,\n            learning_rate=LR,\n            epochs=EPOCHS[0],\n            layers='heads')\n\nhistory = model.keras_model.history.history","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:41:35.787444Z","iopub.execute_input":"2023-07-05T17:41:35.788093Z","iopub.status.idle":"2023-07-05T17:49:56.538591Z","shell.execute_reply.started":"2023-07-05T17:41:35.78805Z","shell.execute_reply":"2023-07-05T17:49:56.537412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nmodel.train(train_dataset, valid_dataset,\n            learning_rate=LR/10,\n            epochs=EPOCHS[1],\n            layers='all')\n\nnew_history = model.keras_model.history.history\nfor k in new_history: history[k] = history[k] + new_history[k]","metadata":{"execution":{"iopub.status.busy":"2023-07-05T17:49:56.541864Z","iopub.execute_input":"2023-07-05T17:49:56.5423Z","iopub.status.idle":"2023-07-05T20:16:55.799265Z","shell.execute_reply.started":"2023-07-05T17:49:56.54225Z","shell.execute_reply":"2023-07-05T20:16:55.798031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epochs = range(EPOCHS[-1])\n\nplt.figure(figsize=(18, 6))\n\nplt.subplot(131)\nplt.plot(epochs, history['loss'], label=\"train loss\")\nplt.plot(epochs, history['val_loss'], label=\"valid loss\")\nplt.legend()\nplt.subplot(132)\nplt.plot(epochs, history['mrcnn_class_loss'], label=\"train class loss\")\nplt.plot(epochs, history['val_mrcnn_class_loss'], label=\"valid class loss\")\nplt.legend()\nplt.subplot(133)\nplt.plot(epochs, history['mrcnn_mask_loss'], label=\"train mask loss\")\nplt.plot(epochs, history['val_mrcnn_mask_loss'], label=\"valid mask loss\")\nplt.legend()\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:16:55.801661Z","iopub.execute_input":"2023-07-05T20:16:55.802124Z","iopub.status.idle":"2023-07-05T20:16:56.299098Z","shell.execute_reply.started":"2023-07-05T20:16:55.802077Z","shell.execute_reply":"2023-07-05T20:16:56.29817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_epoch = np.argmin(history[\"val_loss\"][1:]) + 1\nprint(\"Best epoch: \", best_epoch)\nprint(\"Valid loss: \", history[\"val_loss\"][1:][best_epoch-1])","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:16:56.304485Z","iopub.execute_input":"2023-07-05T20:16:56.304783Z","iopub.status.idle":"2023-07-05T20:16:56.311138Z","shell.execute_reply.started":"2023-07-05T20:16:56.304753Z","shell.execute_reply":"2023-07-05T20:16:56.309748Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predict on test images","metadata":{}},{"cell_type":"code","source":"resized_test_folder = \"../../working/resized_test/\"\nos.mkdir(resized_test_folder)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:16:56.313011Z","iopub.execute_input":"2023-07-05T20:16:56.313415Z","iopub.status.idle":"2023-07-05T20:16:56.323921Z","shell.execute_reply.started":"2023-07-05T20:16:56.313374Z","shell.execute_reply":"2023-07-05T20:16:56.32287Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class InferenceConfig(DiagnosticConfig):\n    GPU_COUNT = 1\n    IMAGES_PER_GPU = 1\n    IMAGE_MIN_DIM = IMAGE_SIZE\n    IMAGE_MAX_DIM = IMAGE_SIZE    \n    IMAGE_RESIZE_MODE = 'none'\n    DETECTION_MIN_CONFIDENCE = 0.8\n    DETECTION_NMS_THRESHOLD = 0.5\n\ninference_config = InferenceConfig()\n\nmodel = modellib.MaskRCNN(mode='inference', \n                          config=inference_config,\n                          model_dir=\"\")","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:16:56.325254Z","iopub.execute_input":"2023-07-05T20:16:56.325728Z","iopub.status.idle":"2023-07-05T20:16:59.847979Z","shell.execute_reply.started":"2023-07-05T20:16:56.325686Z","shell.execute_reply":"2023-07-05T20:16:59.847074Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"glob_list = glob.glob(f'diagnostic*/mask_rcnn_diagnostic_{best_epoch:04d}.h5')\nmodel_path = glob_list[0] if glob_list else ''\nmodel.load_weights(model_path, by_name=True)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:16:59.849286Z","iopub.execute_input":"2023-07-05T20:16:59.849664Z","iopub.status.idle":"2023-07-05T20:17:04.015565Z","shell.execute_reply.started":"2023-07-05T20:16:59.849626Z","shell.execute_reply":"2023-07-05T20:17:04.014661Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from skimage.measure import find_contours\nfrom matplotlib.patches import Polygon\n\n\n# This is done to avoid overlapping region of interests.\ndef refine_masks(masks, rois):\n    areas = np.sum(masks.reshape(-1, masks.shape[-1]), axis=0)\n    mask_index = np.argsort(areas)\n    union_mask = np.zeros(masks.shape[:-1], dtype=bool)\n    for m in mask_index:\n        masks[:, :, m] = np.logical_and(masks[:, :, m], np.logical_not(union_mask))\n        union_mask = np.logical_or(masks[:, :, m], union_mask)\n    for m in range(masks.shape[-1]):\n        mask_pos = np.where(masks[:, :, m]==True)\n        if np.any(mask_pos):\n            y1, x1 = np.min(mask_pos, axis=1)\n            y2, x2 = np.max(mask_pos, axis=1)\n            rois[m, :] = [y1, x1, y2, x2]\n    return masks, rois\n#It converts run length encoding representation of binary mask into binary image.\ndef decode_rle(rle, height, width):\n    s = rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(height*width, dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape((height, width)).T\n\n#The annotations are converted into a mask of shape (height, width, num_annotations)\ndef annotations_to_mask(annotations, height, width):\n    if isinstance(annotations, list):\n        # The annotation consists in a list of RLE codes\n        mask = np.zeros((height, width, len(annotations)))\n        for i, rle_code in enumerate(annotations):\n            mask[:, :, i] = decode_rle(rle_code, height, width)\n    else:\n        error_message = \"{} is expected to be a list or str but received {}\".format(annotation, type(annotation))\n        raise TypeError(error_message)\n    return mask\n\ndef find_anomalies(dicom_image, display=False):\n\n    image_dimensions = dicom_image.shape\n\n    resized_img = cv2.resize(dicom_image, (image_size,image_size), interpolation = cv2.INTER_AREA)\n    saved_filename = resized_test_folder+\"temp_image.jpg\"\n    cv2.imwrite(saved_filename, resized_img) \n    img = cv2.imread(saved_filename)\n\n    result = model.detect([img])\n    r = result[0]\n    \n    if r['masks'].size > 0:\n        masks = np.zeros((img.shape[0], img.shape[1], r['masks'].shape[-1]), dtype=np.uint8)\n        for m in range(r['masks'].shape[-1]):\n            masks[:, :, m] = cv2.resize(r['masks'][:, :, m].astype('uint8'), \n                                        (img.shape[1], img.shape[0]), interpolation=cv2.INTER_NEAREST)\n        \n        y_scale = image_dimensions[0]/IMAGE_SIZE\n        x_scale = image_dimensions[1]/IMAGE_SIZE\n        rois = (r['rois'] * [y_scale, x_scale, y_scale, x_scale]).astype(int)\n        \n        masks, rois = refine_masks(masks, rois)\n    else:\n        masks, rois = r['masks'], r['rois']\n        \n    if display:\n        visualize.display_instances(img, rois, masks, r['class_ids'], \n                                    ['bg']+category_list, r['scores'],\n                                    title=\"prediction\", figsize=(12, 12))\n    return rois, r['class_ids'], r['scores']","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:17:04.019263Z","iopub.execute_input":"2023-07-05T20:17:04.019591Z","iopub.status.idle":"2023-07-05T20:17:04.052441Z","shell.execute_reply.started":"2023-07-05T20:17:04.01956Z","shell.execute_reply":"2023-07-05T20:17:04.051555Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_folder = \"../../input/vinbigdata-chest-xray-abnormalities-detection/test/\"\ntest_file_list = os.listdir(test_folder)[:5]\n\nfor test_file in test_file_list:\n    dicom_image = read_xray(test_folder+test_file)\n    find_anomalies(dicom_image, display=True)","metadata":{"execution":{"iopub.status.busy":"2023-07-05T20:17:04.053888Z","iopub.execute_input":"2023-07-05T20:17:04.054509Z","iopub.status.idle":"2023-07-05T20:17:13.755084Z","shell.execute_reply.started":"2023-07-05T20:17:04.05447Z","shell.execute_reply":"2023-07-05T20:17:13.754145Z"},"trusted":true},"execution_count":null,"outputs":[]}]}