{"cells":[{"metadata":{},"cell_type":"markdown","source":"<div style=\"text-align:center\"><img src=\"https://www.ftmc.com.au/wp-content/uploads/2020/01/slide2-1080x506.jpg\">\n    <h1 style=\"text-align:center; \n               font-weight:bold; \n               position: absolute; \n               top: 50%; left: 50%; \n               transform: translate(-50%, -50%); \n               font-size:500%; \n               color:white\">\n        Abnormalities Detection Using YOLOv4</h1>\n\n</div>"},{"metadata":{},"cell_type":"markdown","source":"# Import required packages"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport shutil\nimport threading\nimport time\nimport random\nimport glob\nfrom tqdm import tqdm\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport cv2\n\nimport tensorflow as tf\nfrom tensorflow import keras\nprint('Tensorflow version: %s' % tf.__version__)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Copy **dataset** to working folder for easy compiling"},{"metadata":{"trusted":true},"cell_type":"code","source":"src = '../input/vinbigdata-512-image-dataset'\ndst = './vinbigdata-512-image-dataset'\n\n    \n# Get total number of files from src folder\nsrc_files_count = 0\nfor (_, _, files) in os.walk(src):\n    src_files_count += len(files)\n\n    \ndef copied_check(src_files_count, dst):\n    dst_files_count = 0\n    for (_, _, files) in os.walk(dst):\n        dst_files_count += len(files)\n    return src_files_count == dst\n\ndef check_on_progress(src_files_count, dst):\n    dst_files_count = 0\n    logged = 0\n    while(dst_files_count < src_files_count):\n        dst_files_count = 0\n        for (_, _, files) in os.walk(dst):\n            dst_files_count += len(files)\n        percentage = int(dst_files_count/src_files_count * 100)\n        if percentage % 10 == 0 and percentage > logged:\n            print('Percentage: {:d}%'.format(percentage))\n            logged = percentage\n    \ndef copy_dir(src, dst):\n    print('Start copying')\n    shutil.copytree(src, dst)\n    time.sleep(0.1)\n    print('Done!')\n    print(dst)\n\nif not copied_check(src_files_count, dst):\n    if os.path.exists(dst):\n        shutil.rmtree(dst)\n    # Start the copying procedure on a separate thread\n    cp = threading.Thread(name='copy', target=copy_dir, args=(src, dst))\n    cp.start()\n    # Start the checking on a separate thread\n    ch = threading.Thread(name='check', target=check_on_progress, args=(src_files_count, dst))\n    ch.start()\nelse:\n    print('Dataset has been already copied!')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Copy **darknet** to working folder for writing files, making backup, config, etc."},{"metadata":{"trusted":true},"cell_type":"code","source":"darknet_src = '../input/darknetyolo'\ndarknet_dst = './darknetyolo'\n\n# Get total number of files from src folder\nd_src_files_count = 0\nfor (_, _, files) in os.walk(darknet_src):\n    d_src_files_count += len(files)\n    \nif not copied_check(d_src_files_count, darknet_dst):\n    if os.path.exists(darknet_dst):\n        shutil.rmtree(darknet_dst)\n    # Start the copying procedure on a separate thread\n    cp_d = threading.Thread(name='copy_darknet', target=copy_dir, args=(darknet_src, darknet_dst))\n    cp_d.start()\n    # Start the checking on a separate thread\n    ch_d = threading.Thread(name='check_darknet', target=check_on_progress, args=(d_src_files_count, darknet_dst))\n    ch_d.start()\nelse:\n    print('Dataset has been already copied!')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Sanity check"},{"metadata":{"trusted":true},"cell_type":"code","source":"!du -sh ../input/darknetyolo\n!du -sh ./darknetyolo\n!du -sh ../input/vinbigdata-512-image-dataset\n!du -sh ./vinbigdata-512-image-dataset","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# EDA + Preprocessing data"},{"metadata":{},"cell_type":"markdown","source":"Configuration"},{"metadata":{"trusted":true},"cell_type":"code","source":"IMG_WIDTH = 512\nIMG_HEIGHT = 512\nINPUT_SIZE = (512, 512)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Check the `.csv` files"},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_DIR = './vinbigdata-512-image-dataset/vinbigdata/train'\nTEST_DIR = './vinbigdata-512-image-dataset/vinbigdata/test'\n\ntrain_df = pd.read_csv('./vinbigdata-512-image-dataset/vinbigdata/train.csv')\ntest_df = pd.read_csv('./vinbigdata-512-image-dataset/vinbigdata/test.csv')\n\ntrain_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Drop \"no finding\" images which are no useful for training process"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df[train_df['class_id']!=14].reset_index(drop=True)\n\n# Retrieve duplicated image names (which have more than 1 annotations)\ntrain_image_names = pd.unique(train_df['image_id']).tolist()\n\nNUM_TRAIN_FILES = len(train_image_names)\nNUM_TEST_FILES = len(test_df)\nprint(f'Dataset has {len(train_df)} elements after removing normal records.')\nprint(f'Number of training files:\\t{NUM_TRAIN_FILES}')\nprint(f'Number of testing files:\\t{NUM_TEST_FILES}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Creating LabelMap\nlabel_map = train_df.loc[:, [\"class_name\", \"class_id\"]]\nlabel_map = label_map.drop_duplicates().reset_index(drop = True)\nN_CLASSES = len(label_map)\nlabel_map = label_map.sort_values(by=['class_id']).reset_index()['class_name']\nlabel_map","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(10, 10))\nplt.grid(axis='x')\nsns.countplot(data=train_df, y='class_name')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Normalize annotations and write to `.txt` files for YOLO training"},{"metadata":{"trusted":true},"cell_type":"code","source":"for name in tqdm(train_image_names):\n    element = train_df[train_df['image_id']==name].reset_index()\n    \n    # Extract and normalize annotations\n    class_id = element['class_id']\n    x_cen = 1/2 * (element['x_max'] + element['x_min']) / element['width']\n    y_cen = 1/2 * (element['y_max'] + element['y_min']) / element['height']\n    w = (element['x_max'] - element['x_min']) / element['width']\n    h = (element['y_max'] - element['y_min']) / element['height']\n    \n    \n    with open(os.path.join(TRAIN_DIR, name + '.txt'), 'w') as f:\n        for i in range(len(element)):\n            line = f'{class_id[i]} {x_cen[i]} {y_cen[i]} {w[i]} {h[i]}'\n            f.write(line)\n            if i < len(element) - 1:\n                f.write('\\n')\n                \nprint('Done!')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Do a sanity check"},{"metadata":{"trusted":true},"cell_type":"code","source":"indices = np.random.randint(NUM_TRAIN_FILES, size=4)\nfor name in train_df['image_id'][indices]:\n    element = train_df[train_df['image_id']==name]\n    with open(os.path.join(TRAIN_DIR, name + '.txt'), 'r') as f:\n        if len(f.readlines()) == len(element):\n            print(f'Correctly writing in file `{name}.txt`')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!cat ./vinbigdata-512-image-dataset/vinbigdata/train/fb929e0efd696fe0f54902da5e7ec57a.txt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"row = 4\ncol = 4\nindices = np.random.randint(len(train_image_names), size=row*col)\n\nplt.figure(figsize=(20, 20))\nfor i in tqdm(range(row*col)):\n    plt.subplot(row, col, i+1)\n    img = plt.imread(os.path.join(TRAIN_DIR, train_df['image_id'][indices[i]] + '.png'))\n    plt.imshow(img, cmap='gray')\n    plt.xticks([])\n    plt.yticks([])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Random colors represent for specific classes"},{"metadata":{"trusted":true},"cell_type":"code","source":"random_r = [random.uniform(0, 1) for _ in range(N_CLASSES)]\nrandom_g = [random.uniform(0, 1) for _ in range(N_CLASSES)]\nrandom_b = [random.uniform(0, 1) for _ in range(N_CLASSES)]\n\ncolor_map_with_label = list(zip(random_r, random_g, random_b))\nprint('\\n'.join(map(str, color_map_with_label)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Helper function for plotting annotations"},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_boxes(img_id, directory, ax=None):\n    img_path = os.path.join(directory, img_id + '.png')\n    anno_path = os.path.join(directory, img_id + '.txt')\n    \n    # Read image\n    img = plt.imread(img_path)\n    \n    # Convert to RGB\n    img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n    \n    with open(anno_path, 'r') as f:\n        box_infos = f.readlines()\n        class_id_list = []\n        for box_info in box_infos:\n            class_id, x_cen, y_cen, w, h = list(map(float, box_info.split()))\n            class_id = int(class_id)\n            class_id_list.append(class_id)\n            xmin = int((x_cen - w/2)*IMG_WIDTH)\n            ymin = int((y_cen - h/2)*IMG_HEIGHT)\n            xmax = int((x_cen + w/2)*IMG_WIDTH)\n            ymax = int((y_cen + h/2)*IMG_HEIGHT)\n            cv2.rectangle(\n                img, \n                pt1=(xmin, ymin), \n                pt2=(xmax, ymax), \n                color=color_map_with_label[class_id], \n                thickness=2\n            )\n            cv2.putText(\n                img, \n                label_map[class_id], \n                (xmin, ymin-5), \n                cv2.FONT_HERSHEY_SIMPLEX, \n                0.5, \n                color_map_with_label[class_id], \n                1\n            )\n    \n    if ax:\n        ax.imshow(img)\n        ax.set_title(f'{len(box_infos)} abnormalities detected belonging to {len(set(class_id_list))} classes')\n    else:\n        plt.figure(figsize=(10, 10))\n        plt.imshow(img)\n        plt.axis('off')\n        plt.title(f'{len(box_infos)} abnormalities detected belonging to {len(set(class_id_list))} classes')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"indices = random.sample(range(NUM_TRAIN_FILES), 2)\n\nfig, axes = plt.subplots(1, 2, figsize=(20, 10))\nplot_boxes(train_image_names[indices[0]], TRAIN_DIR, axes[0])\nplot_boxes(train_image_names[indices[1]], TRAIN_DIR, axes[1])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Setup configurations for YOLOv4"},{"metadata":{},"cell_type":"markdown","source":"> Create `train.txt` and `val.txt`"},{"metadata":{"trusted":true},"cell_type":"code","source":"data_paths = [os.path.join(TRAIN_DIR, image_name + '.png') for image_name in train_image_names]\ndarknet_path = '/kaggle/working/darknetyolo'\n# Split into training and validation\nsplit = int(len(data_paths) * 0.8)\ntrain_data_paths = data_paths[:split]\nval_data_paths = data_paths[split:]\n\n# Write to files\nwith open(os.path.join(darknet_path, 'train.txt'), 'w') as f:\n    f.write('\\n'.join(train_data_paths))\n    \nwith open(os.path.join(darknet_path, 'val.txt'), 'w') as f:\n    f.write('\\n'.join(val_data_paths))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Set darknet config folder as read-only for preservation"},{"metadata":{"trusted":true},"cell_type":"code","source":"!chmod 0444 ./darknetyolo/cfg","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class ModelConfiguration():\n    def __init__(self, input_size, class_names=None, darknet_path=None, backup=True):\n        self.input_size = input_size\n        self.class_names = class_names\n        self.n_classes = len(class_names)\n        self.config_files = ['obj.names', 'obj.data']\n        self.darknet_path = darknet_path\n        if backup:\n            self.backup_path = os.path.join(self.darknet_path, 'backup')\n            try:\n                # Create back-up folder\n                os.mkdir(self.backup_path)\n            except:\n                pass\n    \n    def create_config_files(self):\n        config_paths = [os.path.join(self.darknet_path, config_file) for config_file in self.config_files]\n        \n        # Writing `obj.names`\n        with open(config_paths[0], 'w') as f:\n            for i in range(self.n_classes):\n                f.write(self.class_names[i])\n                if i < self.n_classes - 1:\n                    f.write('\\n')\n                    \n        # Writing `obj.data`\n        with open(config_paths[1],'w') as f:\n            f.write(f'class={self.n_classes}\\n')\n            f.write('train=' + os.path.join(self.darknet_path, 'train.txt') + '\\n')\n            f.write('valid=' + os.path.join(self.darknet_path, 'val.txt') + '\\n')\n            f.write('names=' + config_paths[0] + '\\n')\n            f.write('backup=' + self.backup_path)\n            \n    def config_model(self, lines_with_contents):\n        '''Modify the model configuration file at certain lines\n        \n        Args: \n            `lines_with_contents`: dictionary, in which an element contains number of line and its respectively content\n        '''\n        sorted_keys = list(lines_with_contents.keys())\n        sorted_keys.sort()\n        \n        standard_yolocfg_path = os.path.join(self.darknet_path, 'cfg', 'yolov4-custom.cfg')\n        yolocfg_path = os.path.join(self.darknet_path, f'yolov4-{self.n_classes}c-{self.input_size}.cfg')\n        \n        with open(standard_yolocfg_path, 'r') as f:\n            line = f.readline()\n            modified_line = None\n            modified_f = open(yolocfg_path, 'w')\n            \n            num_line = 1\n            idx = 0\n            while line:\n                if idx < len(sorted_keys):\n                    if num_line == sorted_keys[idx]:\n                        modified_line = lines_with_contents[sorted_keys[idx]]\n                        print('Changed `{}` into `{}`'.format(line.strip(), modified_line.strip()))\n                        idx += 1\n                modified_line = line\n                modified_f.write(modified_line)\n                line = f.readline()\n                if line:\n                    modified_f.write('\\n')\n                num_line += 1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"config = ModelConfiguration(\n    INPUT_SIZE, \n    class_names=label_map, \n    darknet_path='./darknetyolo', \n    backup=True\n)\n\n# Create `obj.data` and `obj.names` \nconfig.create_config_files()\n\n# Customize config file for model\nlines_with_contents = {\n    # Define number of classes\n    970: f'classes={N_CLASSES}', \n    1058: f'classes={N_CLASSES}', \n    1146: f'classes={N_CLASSES}',\n    \n    # Batch size and Subdivisions\n    6: f'batch={64}',\n    7: f'subdivisions={16}',\n    \n    # Input size\n    8: f'width={IMG_WIDTH}',\n    9: f'height={IMG_HEIGHT}',\n    \n    # Max batches\n    20: f'max_batches={20000}',\n    \n    # Steps\n    22: f'steps={16000},{18000}',\n    \n    # Burn-in\n    19: f'burn_in={500}',\n    \n    # Filters before YOLO blocks\n    963: f'filters={57}',\n    1051: f'filters={57}',\n    1139: f'filters={57}'\n    \n    \n}\nconfig.config_model(lines_with_contents)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!sed -n '1139p' darknetyolo/cfg/yolov4-custom.cfg","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Build darknet"},{"metadata":{"trusted":true},"cell_type":"code","source":"%cd /kaggle/working/darknetyolo\n!chmod +x ./darknet","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!/usr/local/cuda/bin/nvcc --version\n\n!sed -i 's/OPENCV=0/OPENCV=1/' Makefile\n!sed -i 's/GPU=0/GPU=1/' Makefile\n!sed -i 's/CUDNN=0/CUDNN=1/' Makefile\n!sed -i 's/CUDNN_HALF=0/CUDNN_HALF=1/' Makefile","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Make darknet"},{"metadata":{"trusted":true},"cell_type":"code","source":"!make","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train the Object Detector"},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls kaggle/working","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}