{"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":"# Siim COVID-19 Detection yoloV4 sample\n\n- use [yoloV4 pytorch](https://github.com/Tianxiaomo/pytorch-YOLOv4/)\n\nin this [cometition rule](https://www.kaggle.com/c/siim-covid19-detection/rules), GPL license is not accepted. so yoloV5 is not capatible.","metadata":{}},{"cell_type":"code","source":"import os, shutil\nshutil.copytree('/kaggle/input/pytorch-yolov4', '/kaggle/working/yolov4')\nos.chdir('/kaggle/working/yolov4')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install /kaggle/input/easydict/easydict-1.9-py2.py3-none-any.whl\n!pip install /kaggle/input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Global parameters","metadata":{}},{"cell_type":"code","source":"RUN_TEST = False  # Use only 30 data for train\nNUM_EPOCH = 5\nDETECT_SIZE = (608,608)\nPRED_PABELS = [\"Negative for Pneumonia\",\"Typical Appearance\",\"Indeterminate Appearance\",\"Atypical Appearance\"]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Read DICM file","metadata":{}},{"cell_type":"code","source":"import pydicom as dicom\nimport numpy as np\nfrom PIL import Image\ndef dcm_to_pil(filename):\n    scan = dicom.dcmread(filename)\n    pil = None\n    siz = (scan.Columns, scan.Rows)\n    \"\"\"\n     if use pydicom some file read error.\n      https://www.kaggle.com/c/siim-covid19-detection/discussion/239899\n     so use jpeg-translated dataset\n      https://www.kaggle.com/xhlulu/siim-covid19-resized-to-1024px-jpg\n    \"\"\"\n    if filename.startswith(\"/kaggle/input/siim-covid19-detection/train/\"):\n        id = filename.split(\"/\")[-1].split('.')[0]\n        if os.path.isfile(f\"/kaggle/input/siim-covid19-resized-to-1024px-jpg/train/{id}.jpg\"):\n            pil = Image.open(f\"/kaggle/input/siim-covid19-resized-to-1024px-jpg/train/{id}.jpg\")\n    elif filename.startswith(\"/kaggle/input/siim-covid19-detection/test/\"):\n        id = filename.split(\"/\")[-1].split('.')[0]\n        if os.path.isfile(f\"/kaggle/input/siim-covid19-resized-to-1024px-jpg/test/{id}.jpg\"):\n            pil = Image.open(f\"/kaggle/input/siim-covid19-resized-to-1024px-jpg/test/{id}.jpg\")\n    if pil is None:\n        pix = scan.pixel_array\n        pix = (pix - np.min(pix)) / (np.max(pix) - np.min(pix))\n        img = np.clip(np.round(pix * 0xff), 0, 0xff).astype(np.uint8)\n        pil = Image.fromarray(img)\n        siz = pil.size\n    return pil.resize(DETECT_SIZE), siz","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Make Dataset Directory","metadata":{}},{"cell_type":"code","source":"os.mkdir('/kaggle/working/yolov4/siim')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport json\nfrom tqdm.notebook import tqdm\ndf = pd.read_csv(\"/kaggle/input/siim-covid19-detection/train_study_level.csv\")\ndf_train = pd.read_csv(\"/kaggle/input/siim-covid19-detection/train_image_level.csv\")\nwith open(f'/kaggle/working/yolov4/train.txt', 'w') as trainwf:\n    for idx in tqdm(range(30 if RUN_TEST else len(df))):\n        id = df.loc[idx].id.split('_')[0]\n        fl = os.listdir(f'/kaggle/input/siim-covid19-detection/train/{id}')[0]\n        fn = os.listdir(f'/kaggle/input/siim-covid19-detection/train/{id}/{fl}')[0]\n        try:\n            img, org_siz = dcm_to_pil(f'/kaggle/input/siim-covid19-detection/train/{id}/{fl}/{fn}')\n        except:\n            continue\n        label = 0\n        for i, l in enumerate(PRED_PABELS):\n            if df.loc[idx][l] == 1:\n                label = i\n                break\n        for j, box in enumerate(df_train[df_train.StudyInstanceUID==id].boxes):\n            line = [f\"{idx}_{j}.png\"]\n            if type(box) is not float:\n                boxes = json.loads(box.replace(\"\\'\",\"\\\"\"))\n                for b in boxes:\n                    x = b['x'] / org_siz[0]\n                    y = b['y'] / org_siz[1]\n                    w = b['width'] / org_siz[0]\n                    h = b['height'] / org_siz[1]\n                    x1 = int(x*DETECT_SIZE[0])\n                    x2 = x1 + int(w*DETECT_SIZE[0])\n                    y1 = int(y*DETECT_SIZE[1])\n                    y2 = y1 + int(h*DETECT_SIZE[1])\n                    line.append(f\"{x1},{y1},{x2},{y2},{label}\")\n            if len(line) > 1:\n                img = img.convert(\"RGB\")\n                img.save(f'/kaggle/working/yolov4/siim/{idx}_{j}.png')\n                trainwf.write(\" \".join(line)+\"\\n\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training YoloV4","metadata":{}},{"cell_type":"code","source":"from easydict import EasyDict\n\n_BASE_DIR = '/kaggle/working/yolov4'\n\nCfg = EasyDict()\n\nCfg.use_darknet_cfg = True\nCfg.cfgfile = os.path.join(_BASE_DIR, 'cfg', 'yolov4.cfg')\n\nCfg.batch = 4\nCfg.subdivisions = 1\nCfg.width = DETECT_SIZE[0]\nCfg.height = DETECT_SIZE[1]\nCfg.channels = 3\nCfg.momentum = 0.949\nCfg.decay = 0.0005\nCfg.angle = 0\nCfg.saturation = 1.5\nCfg.exposure = 1.5\nCfg.hue = .1\n\nCfg.learning_rate = 0.00261\nCfg.burn_in = 1000\nCfg.max_batches = 500500\nCfg.steps = [400000, 450000]\nCfg.policy = Cfg.steps\nCfg.scales = .1, .1\n\nCfg.cutmix = 0\nCfg.mosaic = 1\n\nCfg.letter_box = 0\nCfg.jitter = 0.2\nCfg.classes = 80\nCfg.track = 0\nCfg.w = Cfg.width\nCfg.h = Cfg.height\nCfg.flip = 1\nCfg.blur = 0\nCfg.gaussian = 0\nCfg.boxes = 60  # box num\nCfg.TRAIN_EPOCHS = 1 if RUN_TEST else 30\nCfg.train_label = 'train.txt'\nCfg.val_label = 'train.txt'\nCfg.TRAIN_OPTIMIZER = 'adam'\n\nif Cfg.mosaic and Cfg.cutmix:\n    Cfg.mixup = 4\nelif Cfg.cutmix:\n    Cfg.mixup = 2\nelif Cfg.mosaic:\n    Cfg.mixup = 3\n\nCfg.checkpoints = os.path.join(_BASE_DIR, 'checkpoints')\nCfg.TRAIN_TENSORBOARD_DIR = os.path.join(_BASE_DIR, 'log')\n\nCfg.iou_type = 'iou'  # 'giou', 'diou', 'ciou'\n\nCfg.keep_checkpoint_max = 10\n\nCfg.dataset_dir = 'siim'\nCfg.gpu = \"0\"\nCfg.pretrained = \"/kaggle/input/yolov4-predefend-weight/yolov4.conv.137.pth\"","metadata":{"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import time\nimport logging\nimport os, sys, math\nimport argparse\nfrom collections import deque\nimport datetime\n\nimport cv2\nfrom tqdm import tqdm\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nfrom torch import optim\nfrom torch.nn import functional as F\nfrom tensorboardX import SummaryWriter\nfrom easydict import EasyDict as edict\n\nfrom dataset import Yolo_dataset\nfrom models import Yolov4\nfrom tool.darknet2pytorch import Darknet\n\nfrom tool.tv_reference.utils import collate_fn as val_collate\nfrom tool.tv_reference.coco_utils import convert_to_coco_api\nfrom tool.tv_reference.coco_eval import CocoEvaluator","metadata":{"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from train import bboxes_iou,Yolo_loss, collate, init_logger, _get_date_str","metadata":{"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train(model, device, config, epochs=5, batch_size=1, save_cp=True, log_step=20, img_scale=0.5):\n    train_dataset = Yolo_dataset(config.train_label, config, train=True)\n\n    n_train = len(train_dataset)\n\n    train_loader = DataLoader(train_dataset, batch_size=config.batch // config.subdivisions, shuffle=True,\n                              num_workers=4, pin_memory=True, drop_last=True, collate_fn=collate)\n\n    writer = SummaryWriter(log_dir=config.TRAIN_TENSORBOARD_DIR,\n                           filename_suffix=f'OPT_{config.TRAIN_OPTIMIZER}_LR_{config.learning_rate}_BS_{config.batch}_Sub_{config.subdivisions}_Size_{config.width}',\n                           comment=f'OPT_{config.TRAIN_OPTIMIZER}_LR_{config.learning_rate}_BS_{config.batch}_Sub_{config.subdivisions}_Size_{config.width}')\n    # writer.add_images('legend',\n    #                   torch.from_numpy(train_dataset.label2colorlegend2(cfg.DATA_CLASSES).transpose([2, 0, 1])).to(\n    #                       device).unsqueeze(0))\n    max_itr = config.TRAIN_EPOCHS * n_train\n    # global_step = cfg.TRAIN_MINEPOCH * n_train\n    global_step = 0\n    logging.info(f'''Starting training:\n        Epochs:          {epochs}\n        Batch size:      {config.batch}\n        Subdivisions:    {config.subdivisions}\n        Learning rate:   {config.learning_rate}\n        Training size:   {n_train}\n        Checkpoints:     {save_cp}\n        Device:          {device.type}\n        Images size:     {config.width}\n        Optimizer:       {config.TRAIN_OPTIMIZER}\n        Dataset classes: {config.classes}\n        Train label path:{config.train_label}\n        Pretrained:\n    ''')\n\n    # learning rate setup\n    def burnin_schedule(i):\n        if i < config.burn_in:\n            factor = pow(i / config.burn_in, 4)\n        elif i < config.steps[0]:\n            factor = 1.0\n        elif i < config.steps[1]:\n            factor = 0.1\n        else:\n            factor = 0.01\n        return factor\n\n    if config.TRAIN_OPTIMIZER.lower() == 'adam':\n        optimizer = optim.Adam(\n            model.parameters(),\n            lr=config.learning_rate / config.batch,\n            betas=(0.9, 0.999),\n            eps=1e-08,\n        )\n    elif config.TRAIN_OPTIMIZER.lower() == 'sgd':\n        optimizer = optim.SGD(\n            params=model.parameters(),\n            lr=config.learning_rate / config.batch,\n            momentum=config.momentum,\n            weight_decay=config.decay,\n        )\n    scheduler = optim.lr_scheduler.LambdaLR(optimizer, burnin_schedule)\n\n    criterion = Yolo_loss(device=device, batch=config.batch // config.subdivisions, n_classes=config.classes)\n    # scheduler = ReduceLROnPlateau(optimizer, mode='max', verbose=True, patience=6, min_lr=1e-7)\n    # scheduler = CosineAnnealingWarmRestarts(optimizer, 0.001, 1e-6, 20)\n\n    save_prefix = 'Yolov4_epoch'\n    saved_models = deque()\n    model.train()\n    for epoch in range(epochs):\n        # model.train()\n        epoch_loss = 0\n        epoch_step = 0\n\n        with tqdm(total=n_train, desc=f'Epoch {epoch + 1}/{epochs}', unit='img', ncols=50) as pbar:\n            for i, batch in enumerate(train_loader):\n                global_step += 1\n                epoch_step += 1\n                images = batch[0]\n                bboxes = batch[1]\n\n                images = images.to(device=device, dtype=torch.float32)\n                bboxes = bboxes.to(device=device)\n\n                bboxes_pred = model(images)\n                loss, loss_xy, loss_wh, loss_obj, loss_cls, loss_l2 = criterion(bboxes_pred, bboxes)\n                # loss = loss / config.subdivisions\n                loss.backward()\n\n                epoch_loss += loss.item()\n\n                if global_step % config.subdivisions == 0:\n                    optimizer.step()\n                    scheduler.step()\n                    model.zero_grad()\n\n                if global_step % (log_step * config.subdivisions) == 0:\n                    writer.add_scalar('train/Loss', loss.item(), global_step)\n                    writer.add_scalar('train/loss_xy', loss_xy.item(), global_step)\n                    writer.add_scalar('train/loss_wh', loss_wh.item(), global_step)\n                    writer.add_scalar('train/loss_obj', loss_obj.item(), global_step)\n                    writer.add_scalar('train/loss_cls', loss_cls.item(), global_step)\n                    writer.add_scalar('train/loss_l2', loss_l2.item(), global_step)\n                    writer.add_scalar('lr', scheduler.get_lr()[0] * config.batch, global_step)\n                    pbar.set_postfix(**{'loss (batch)': loss.item(), 'loss_xy': loss_xy.item(),\n                                        'loss_wh': loss_wh.item(),\n                                        'loss_obj': loss_obj.item(),\n                                        'loss_cls': loss_cls.item(),\n                                        'loss_l2': loss_l2.item(),\n                                        'lr': scheduler.get_lr()[0] * config.batch\n                                        })\n                    logging.debug('Train step_{}: loss : {},loss xy : {},loss wh : {},'\n                                  'loss obj : {}，loss cls : {},loss l2 : {},lr : {}'\n                                  .format(global_step, loss.item(), loss_xy.item(),\n                                          loss_wh.item(), loss_obj.item(),\n                                          loss_cls.item(), loss_l2.item(),\n                                          scheduler.get_lr()[0] * config.batch))\n\n                pbar.update(images.shape[0])\n\n            if save_cp:\n                try:\n                    # os.mkdir(config.checkpoints)\n                    os.makedirs(config.checkpoints, exist_ok=True)\n                    logging.info('Created checkpoint directory')\n                except OSError:\n                    pass\n                save_path = os.path.join(config.checkpoints, f'{save_prefix}{epoch + 1}.pth')\n                torch.save(model.state_dict(), save_path)\n                logging.info(f'Checkpoint {epoch + 1} saved !')\n                saved_models.append(save_path)\n                if len(saved_models) > config.keep_checkpoint_max > 0:\n                    model_to_remove = saved_models.popleft()\n                    try:\n                        os.remove(model_to_remove)\n                    except:\n                        logging.info(f'failed to remove {model_to_remove}')\n\n    writer.close()","metadata":{"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"logging = init_logger(log_dir='log', stdout=False)\ncfg = Cfg\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = cfg.gpu\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nlogging.info(f'Using device {device}')\n\nmodel = Yolov4(cfg.pretrained, n_classes=cfg.classes)\nmodel.to(device=device)\n\ntrain(model=model,\n      config=cfg,\n      epochs=NUM_EPOCH,\n      device=device, )","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prediction","metadata":{}},{"cell_type":"code","source":"from models import Yolov4","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = Yolov4(yolov4conv137weight=None, n_classes=int(cfg.classes), inference=True)\npretrained_dict = torch.load(f\"checkpoints/Yolov4_epoch{NUM_EPOCH}.pth\", map_location=device)\nmodel.load_state_dict(pretrained_dict)\nmodel.to(device)\nmodel.eval()\n\"\"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tool.torch_utils import do_detect\nfrom tool import utils\nimport cv2","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Threashold to Predict","metadata":{}},{"cell_type":"code","source":"conf_thresh, nms_thresh = 0.4, 0.6\npred_name = [\"negative\", \"typical\", \"indeterminate\", \"atypical\"]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def post_processing(img, conf_thresh, nms_thresh, output):\n\n    # anchors = [12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401]\n    # num_anchors = 9\n    # anchor_masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]\n    # strides = [8, 16, 32]\n    # anchor_step = len(anchors) // num_anchors\n\n    # [batch, num, 1, 4]\n    box_array = output[0]\n    # [batch, num, num_classes]\n    confs = output[1]\n\n    t1 = time.time()\n\n    if type(box_array).__name__ != 'ndarray':\n        box_array = box_array.cpu().detach().numpy()\n        confs = confs.cpu().detach().numpy()\n\n    num_classes = confs.shape[2]\n\n    # [batch, num, 4]\n    box_array = box_array[:, :, 0]\n\n    # [batch, num, num_classes] --> [batch, num]\n    max_conf = np.max(confs, axis=2)\n    max_id = np.argmax(confs, axis=2)\n\n    t2 = time.time()\n\n    bboxes_batch = []\n    for i in range(box_array.shape[0]):\n       \n        argwhere = max_conf[i] > conf_thresh\n        l_box_array = box_array[i, argwhere, :]\n        l_max_conf = max_conf[i, argwhere]\n        l_max_id = max_id[i, argwhere]\n\n        bboxes = []\n        # nms for each class\n        for j in range(num_classes):\n\n            cls_argwhere = l_max_id == j\n            ll_box_array = l_box_array[cls_argwhere, :]\n            ll_max_conf = l_max_conf[cls_argwhere]\n            ll_max_id = l_max_id[cls_argwhere]\n\n            keep = utils.nms_cpu(ll_box_array, ll_max_conf, nms_thresh)\n            \n            if (keep.size > 0):\n                ll_box_array = ll_box_array[keep, :]\n                ll_max_conf = ll_max_conf[keep]\n                ll_max_id = ll_max_id[keep]\n\n                for k in range(ll_box_array.shape[0]):\n                    bboxes.append([ll_box_array[k, 0], ll_box_array[k, 1], ll_box_array[k, 2], ll_box_array[k, 3], ll_max_conf[k], ll_max_conf[k], ll_max_id[k]])\n        \n        bboxes_batch.append(bboxes)\n\n    t3 = time.time()\n\n    return bboxes_batch","metadata":{"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Make Submission","metadata":{}},{"cell_type":"code","source":"# make all id\nstudy_id = []\nimage_id = []\nstudy_id_file = []\nimage_id_file = []\nfor id in os.listdir(f'/kaggle/input/siim-covid19-detection/test'):\n    study_id.append(id)\n    sfile = None\n    fl = os.listdir(f'/kaggle/input/siim-covid19-detection/test/{id}')\n    for f in fl:\n        fn = os.listdir(f'/kaggle/input/siim-covid19-detection/test/{id}/{f}')\n        for d in fn:\n            image_id.append(d.split('.')[0])\n            image_id_file.append(f'/kaggle/input/siim-covid19-detection/test/{id}/{f}/{d}')\n            if sfile is None:\n                sfile = f'/kaggle/input/siim-covid19-detection/test/{id}/{f}/{d}'\n    study_id_file.append(sfile)\n    \npp = []\nfor sid, sfn in zip(study_id, study_id_file):\n    pp.append((f'{sid}_study', sfn))\nfor mid, mfn in zip(image_id, image_id_file):\n    pp.append((f'{mid}_image', mfn))\n\ns = pd.read_csv(\"/kaggle/input/siim-covid19-detection/sample_submission.csv\")\nids = s.id.values.tolist()\nassert set(ids)==set([p[0] for p in pp]), \"id miss\"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"result_id = []\nresult_pred = []\nchache_file = {}\nfor id, fn in tqdm(pp):\n    img = None\n    if fn in chache_file:\n        dst = chache_file[fn]\n    else:\n        try:\n            img, org_siz = dcm_to_pil(fn)\n        except:\n            dst = \"negative 1 0 0 1 1\"\n    \n    if img is not None:\n        img = np.array(img.convert(\"RGB\"))\n        img = torch.from_numpy(img.transpose(2, 0, 1)).float().div(255.0).unsqueeze(0)\n        output = model(img.to(device))\n        boxes = post_processing(img, conf_thresh, nms_thresh, output)\n        detect = [b for b in boxes[0] if b[6] < 4]\n        if len(detect) == 0:\n            dst = \"negative 1 0 0 1 1\"\n        else:\n            # Now, only One Result to Use\n            detect = sorted(detect, key=lambda x:x[5])[-1]\n            x1 = min(max(0,int(detect[0] * org_siz[0])), org_siz[0]-1)\n            y1 = min(max(0,int(detect[1] * org_siz[1])), org_siz[1]-1)\n            x2 = min(max(1,int(detect[2] * org_siz[0])), org_siz[0])\n            y2 = min(max(1,int(detect[3] * org_siz[1])), org_siz[1])\n            dst = f\"{pred_name[detect[6]]} {detect[5]} {x1} {y1} {x2} {y2}\"\n        chache_file[fn] = dst\n\n    result_id.append(id)\n    result_pred.append(dst)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.chdir('/kaggle/working')\npd.DataFrame({\"id\":result_id,\"PredictionString\":result_pred}).to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!rm -rf yolov4","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}