{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"!cp ../input/gdcm-conda-install/gdcm.tar .\n!tar -xvzf gdcm.tar\n!conda install --offline ./gdcm/gdcm-2.8.9-py37h71b2a6d_0.tar.bz2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import sys\nsys.path.append('../input/efficientnet-pytorch/EfficientNet-PyTorch/EfficientNet-PyTorch-master')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport time\nimport random\n\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader, TensorDataset\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\nfrom  torch.cuda.amp import autocast, GradScaler # for training only, need nightly build pytorch\n\nimport pydicom\nfrom efficientnet_pytorch import EfficientNet\nfrom scipy.ndimage.interpolation import zoom\n\nfrom albumentations import Compose, HorizontalFlip, VerticalFlip, RandomRotate90\nfrom albumentations.pytorch import ToTensorV2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Configurations\nimg_inp = {'b0' : 224, \n            'b1' : 240, \n            'b2' : 260, \n            'b3' : 300, \n            'b4' : 380, \n            'b5' : 456, \n            'b6' : 528, \n            'b7' : 600}\n\n\npretrained_model = {\n    'efficientnet-b0': '../input/efficientnet-pytorch/efficientnet-b0-08094119.pth',\n    'efficientnet-b1': '../input/efficientnet-pytorch/efficientnet-b1-dbc7070a.pth', \n    'efficientnet-b2': '../input/efficientnet-pytorch/efficientnet-b2-27687264.pth',\n    'efficientnet-b3': '../input/efficientnet-pytorch/efficientnet-b3-c8376fa2.pth', \n    'efficientnet-b4': '../input/efficientnet-pytorch/efficientnet-b4-e116e8b3.pth',\n    'efficientnet-b5': '../input/efficientnet-pytorch/efficientnet-b5-586e6cc6.pth', \n    'efficientnet-b6': '../input/efficientnet-pytorch/efficientnet-b6-c76e70fd.pth',\n    'efficientnet-b7': '../input/efficientnet-pytorch/efficientnet-b7-dcc49843.pth', \n}\n\n\nCFG = {\n    'train': True,\n    \n    'train_img_path': '../input/rsna-str-pulmonary-embolism-detection/train',\n    'test_img_path': '../input/rsna-str-pulmonary-embolism-detection/test',\n    'cv_fold_path': '../input/samplersna/rsna_train_splits_fold_20.csv',\n    'train_path': '../input/rsna-str-pulmonary-embolism-detection/train.csv',\n    'test_path': '../input/rsna-str-pulmonary-embolism-detection/test.csv',\n    \n    'image_target_cols': [\n        'pe_present_on_image',\n         ],\n    \n    'exam_target_cols': [\n        'pe_present_on_image',\n        'negative_exam_for_pe', \n        'indeterminate', \n        'both_no', # Added new column\n        \n        'rv_lv_ratio_gte_1', \n        'rv_lv_ratio_lt_1', \n        \n        'chronic_pe', \n        'acute_and_chronic_pe',\n        'acute_pe',  # Added new column\n        \n        'leftsided_pe',\n        'central_pe', \n        'rightsided_pe',\n        \n        'qa_motion',\n        'qa_contrast',\n        'flow_artifact',\n        'true_filling_defect_not_pe'\n        ], \n   \n    \n    'lr': 0.0005,\n    'epochs': 1,\n    'device': 'cuda', # cuda, cpu\n    'train_bs': 64,\n    'valid_bs': 64,\n    'accum_iter': 1,\n    'verbose_step': 1,\n    'num_workers': 0,\n    'efbnet': 'efficientnet-b3',  # change here\n    'img_size': 300,              # change here\n    'effnet_fc': 128, \n    'metadata_feats': 26,  \n    \n    'train_folds': [\n                    # [1, 2, 3, 4], \n                    # [0, 2, 3, 4], \n                    # [0, 1, 3, 4], \n                    # [0, 1, 2, 4], \n                    [10, 11, 12, 13]\n                   ], \n    \n    'valid_folds': [\n                    # [0], \n                    # [1], \n                    # [2], \n                    # [3], \n                    [14]\n                   ], \n    \n    'stage_model_path': '../input/rsna-pre-models/',\n    'model_path': '../working/',\n    'tag': 'stage1'\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Seed\nSEED = 42\n\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# pre-process train df\ndef preprocess_DF(df):\n    both_no = lambda x: (1 - (x.negative_exam_for_pe + x.indeterminate))\n    acute_pe = lambda x: (1 - (x.chronic_pe + x.acute_and_chronic_pe))\n    \n    df['both_no'] = df.apply(both_no, axis=1)\n    df['acute_pe'] = df.apply(acute_pe, axis=1)\n    df['acute_pe'] = np.where(df['both_no']==0, 0, df['acute_pe'])\n    \n    return df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get image + pre-processing\n\ndef window_min_max(img, min_, max_, WL=50, WW=350):\n    upper, lower = WL+WW//2, WL-WW//2\n    X = np.clip(img.copy(), lower, upper)\n    X = X - np.min(X)\n    X = X / np.max(X)\n    return X\n\ndef get_img_min_max(path, min_, max_):\n    '''\n    # min_: patient level pixel min\n    # max_: patient level pixel max\n    \n    RED channel / LUNG window / level=-600, width=1500\n    GREEN channel / PE window / level=100, width=700\n    BLUE channel / MEDIASTINAL window / level=40, width=400\n    '''\n    d = pydicom.read_file(path)\n    \n    # Get image\n    img = (d.pixel_array * d.RescaleSlope) + d.RescaleIntercept\n    r = window_min_max(img, min_, max_, -600, 1500)\n    g = window_min_max(img, min_, max_, 100, 700)\n    b = window_min_max(img, min_, max_, 40, 400)\n    \n    res = np.concatenate([r[:, :, np.newaxis],\n                          g[:, :, np.newaxis],\n                          b[:, :, np.newaxis]], axis=-1)\n    \n    res = zoom(res, [CFG['img_size']/res.shape[0], CFG['img_size']/res.shape[1], 1.], prefilter=False, order=1) \n    \n    # Get numerical metadata\n    SliceThickness           = float(d.SliceThickness)\n    KVP                      = float(d.KVP)/100.0\n    TableHeight              = float(d.TableHeight)/100.0\n    XRayTubeCurrent          = float(d.XRayTubeCurrent)/100.0\n    Exposure                 = float(d.Exposure)/100.0\n    GantryDetectorTilt       = float(d.GantryDetectorTilt)\n\n    ImagePositionPatient     = [x/100.0 for x in list(d.ImagePositionPatient)]\n    ImageOrientationPatient  = list(d.ImageOrientationPatient)\n    \n    mt_num = np.array((SliceThickness, KVP, TableHeight, \n                XRayTubeCurrent, Exposure, \n                *ImagePositionPatient, *ImageOrientationPatient, \n                GantryDetectorTilt))\n\n    # Get categorical metadata\n    SpecificCharacterSet = d.SpecificCharacterSet\n    ImageType            = d.ImageType\n    ConvolutionKernel    = d.ConvolutionKernel\n    PatientPosition      = d.PatientPosition\n    \n    sps_100     = np.where(SpecificCharacterSet=='ISO_IR 100', 1, 0)\n    sps_other   = np.where(sps_100==0, 1, 0)\n\n    it_opa      = np.where(ImageType==\"['ORIGINAL', 'PRIMARY', 'AXIAL']\", 1, 0)\n    it_o        = np.where(ImageType==\"ORIGINAL\", 1, 0)\n    it_other    = np.where(it_opa+it_o > 0, 0, 1)\n\n    ck_std      = np.where(ConvolutionKernel==\"STANDARD\", 1, 0)\n    ck_b        = np.where(ConvolutionKernel==\"B\", 1, 0)\n    ck_other    = np.where(ck_std+ck_b > 0, 0, 1)\n\n    pp_ffs      = np.where(PatientPosition==\"FFS\", 1, 0)\n    pp_hfs      = np.where(PatientPosition==\"HFS\", 1, 0)\n    pp_other    = np.where(pp_ffs+pp_hfs > 0, 0, 1)\n    \n    mt_cat = np.array((sps_100, sps_other, it_opa, it_o, it_other, ck_std, ck_b, ck_other, pp_ffs, pp_hfs, pp_other))\n    \n    # Get Metadata\n    mt = np.concatenate((mt_num, mt_cat))\n    \n    return res, mt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Dataset\n\nclass RSNADataset(TensorDataset):\n    def __init__(\n        self, df, label_smoothing, data_root, \n        image_subsampling=True, transforms=None, output_label=True\n    ):\n        \n        super().__init__()\n        self.df = df.reset_index(drop=True).copy()        \n        self.label_smoothing = label_smoothing\n        self.transforms = transforms\n        self.data_root = data_root\n        self.output_label = output_label\n    \n    def __len__(self):\n        return self.df.shape[0]\n    \n    def __getitem__(self, index: int):\n        \n        # get labels\n        if self.output_label:\n            target = self.df[CFG['exam_target_cols']].iloc[index].values\n            target[1:-1] = target[0]*target[1:-1]\n          \n        path = \"{}/{}/{}/{}.dcm\".format(self.data_root, \n                                        self.df.iloc[index]['StudyInstanceUID'], \n                                        self.df.iloc[index]['SeriesInstanceUID'], \n                                        self.df.iloc[index]['SOPInstanceUID'])\n        \n        # Get image and metadata\n        img, mt  = get_img_min_max(path, 0, 0)\n        if self.transforms:\n            img = self.transforms(image=img)['image']\n        \n        # Get metadata and pre-process\n        # mt = mt[None, :]\n        \n        # do label smoothing\n        if self.output_label == True:\n            target = np.clip(target, self.label_smoothing, 1 - self.label_smoothing)\n            \n            return img, mt, target\n        else:\n            return img, mt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Image Transformation\n\ndef get_train_transforms():\n    return Compose([\n                    HorizontalFlip(p=0.5),\n                    VerticalFlip(p=0.5),\n                    RandomRotate90(p=0.5),\n                    ToTensorV2(p=1.0),\n                    ], p=1.)\n\n\ndef get_valid_transforms():\n    return Compose([\n                    ToTensorV2(p=1.0),\n                    ], p=1.)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Models\n\nclass FeatureExtractor(nn.Module):\n    def __init__(self):\n        super().__init__()\n        # self.cnn_model = EfficientNet.from_pretrained(CFG['efbnet'], in_channels=3)\n        self.cnn_model = EfficientNet.from_name(CFG['efbnet'])\n        self.cnn_model.load_state_dict(torch.load(pretrained_model[CFG['efbnet']]))\n        # self.model._fc = nn.Linear(self.cnn_model._fc.in_features, CFG['effnet_fc'], bias=True)\n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        \n    def get_dim(self):\n        return self.cnn_model._fc.in_features\n        \n    def forward(self, x):\n        feats = self.cnn_model.extract_features(x)\n        return self.pooling(feats).view(x.shape[0], -1)\n\n\n\nclass stg1_study_model(nn.Module):\n    def __init__(self):\n        super().__init__()\n        # For image\n        self.cnn_model = FeatureExtractor()\n        \n        # For metadata\n        self.fnn_fc1 = nn.Linear(in_features=CFG['metadata_feats'], out_features=32)\n        self.fnn_fc2 = nn.Linear(in_features=32, out_features=32)\n        self.fnn_fc3 = nn.Linear(in_features=32, out_features=16)\n        \n        # Final Fusion\n        self.final_fc = nn.Linear(in_features=self.cnn_model.get_dim()+16, out_features=len(CFG['exam_target_cols']))\n        \n    def forward(self, imgs, mts):\n        imgs_embdes = self.cnn_model(imgs) # bs * efb_feat_size\n        mt_embed = self.fnn_fc1(mts)\n        mt_embed = self.fnn_fc2(mt_embed)\n        mt_embed = self.fnn_fc3(mt_embed)\n        \n        embed = torch.cat([imgs_embdes, mt_embed],dim=1)\n        \n        image_preds = self.final_fc(embed)\n        \n        return image_preds","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Loss functions\ndef rsna_wloss_train(y_true_img, y_pred_img, device):\n    bce_func = torch.nn.BCEWithLogitsLoss(reduction='sum').to(device)\n    y_pred_img = y_pred_img.view(*y_true_img.shape)\n    image_loss = bce_func(y_pred_img, y_true_img)\n    correct_count = ((y_pred_img>0) == y_true_img).sum(axis=0)\n    counts = y_true_img.size()[0]\n    \n    return image_loss, correct_count, counts\n\ndef rsna_wloss_valid(y_true_img, y_pred_img, device):\n    return rsna_wloss_train(y_true_img, y_pred_img, device)\n\ndef rsna_wloss_inference(y_true_img, y_pred_img):\n    bce_func = torch.nn.BCELoss(reduction='sum')\n    image_loss = bce_func(y_pred_img, y_true_img)\n    correct_count = ((y_pred_img>0) == y_true_img).sum()\n    counts = y_pred_img.shape[0]\n    return image_loss, correct_count, counts","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# DataLoader\ndef prepare_train_dataloader(train, cv_df, train_fold, valid_fold):\n    from catalyst.data.sampler import BalanceClassSampler\n    \n    train_patients = cv_df.loc[cv_df.fold.isin(train_fold), 'StudyInstanceUID'].unique()\n    valid_patients = cv_df.loc[cv_df.fold.isin(valid_fold), 'StudyInstanceUID'].unique()\n\n    train_ = train.loc[train.StudyInstanceUID.isin(train_patients),:].reset_index(drop=True)\n    valid_ = train.loc[train.StudyInstanceUID.isin(valid_patients),:].reset_index(drop=True)\n\n    # train mode to do image-level subsampling\n    train_ds = RSNADataset(train_, 0.0, CFG['train_img_path'],  image_subsampling=False, transforms=get_train_transforms(), output_label=True) \n    valid_ds = RSNADataset(valid_, 0.0, CFG['train_img_path'],  image_subsampling=False, transforms=get_valid_transforms(), output_label=True)\n\n    train_loader = torch.utils.data.DataLoader(\n        train_ds,\n        batch_size=CFG['train_bs'],\n        pin_memory=False,\n        drop_last=False,\n        shuffle=True,        \n        num_workers=CFG['num_workers'],\n    )\n    \n    val_loader = torch.utils.data.DataLoader(\n        valid_ds, \n        batch_size=CFG['valid_bs'],\n        num_workers=CFG['num_workers'],\n        shuffle=False,\n        pin_memory=False,\n    )\n  \n    return train_loader, val_loader\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_one_epoch(epoch, model, device, scaler, optimizer, train_loader):\n    model.train()\n\n    t = time.time()\n    loss_sum = 0\n    acc_sum = None\n    loss_w_sum = 0\n    acc_record = []\n    loss_record = []\n    avg_cnt = 40\n    \n    for step, (imgs, mts, image_labels) in enumerate(train_loader):\n        imgs = imgs.to(device).float()\n        mts = mts.to(device).float()\n        image_labels = image_labels.to(device).float()\n\n        with autocast():\n            image_preds = model(imgs, mts)   #output = model(input)\n\n            image_loss, correct_count, counts = rsna_wloss_train(image_labels, image_preds, device)\n            \n            loss = image_loss/counts\n            scaler.scale(loss).backward()\n\n            loss_ = image_loss.detach().item()/counts\n            acc_ = correct_count.detach().cpu().numpy()/counts\n            \n            loss_record += [loss_]\n            acc_record += [acc_]\n            loss_record = loss_record[-avg_cnt:]\n            acc_record = acc_record[-avg_cnt:]\n            loss_sum = np.vstack(loss_record).mean(axis=0)\n            acc_sum = np.vstack(acc_record).mean(axis=0)\n            \n            #loss_w_sum += counts\n\n            if ((step + 1) %  CFG['accum_iter'] == 0) or ((step + 1) == len(train_loader)):\n                # may unscale_ here if desired (e.g., to allow clipping unscaled gradients)\n\n                scaler.step(optimizer)\n                scaler.update()\n                optimizer.zero_grad()                \n\n            acc_details = [\"{:.5}: {:.4f}\".format(f, float(acc_sum[i])) for i, f in enumerate(CFG['exam_target_cols'])]\n            acc_details = \", \".join(acc_details)\n            \n            if ((step + 1) % CFG['verbose_step'] == 0) or ((step + 1) == len(train_loader)):\n                print(\n                    f'epoch {epoch} train Step {step+1}/{len(train_loader)}, ' + \\\n                    f'loss: {loss_sum[0]:.3f}, ' + \\\n                    acc_details + ', ' + \\\n                    f'time: {(time.time() - t):.2f}', end='\\r' if (step + 1) != len(train_loader) else '\\n'\n                )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def valid_one_epoch(epoch, model, device, scheduler, val_loader, schd_loss_update=False):\n    model.eval()\n\n    t = time.time()\n    loss_sum = 0\n    acc_sum = None\n    loss_w_sum = 0\n\n    for step, (imgs, mts, image_labels) in enumerate(val_loader):\n        imgs = imgs.to(device).float()\n        mts = mts.to(device).float()\n        image_labels = image_labels.to(device).float()\n        \n        image_preds = model(imgs, mts)   #output = model(input)\n        #print(image_preds.shape, exam_pred.shape)\n\n        image_loss, correct_count, counts = rsna_wloss_valid(image_labels, image_preds, device)\n\n        loss = image_loss/counts\n        \n        loss_sum += image_loss.detach().item()\n        if acc_sum is None:\n            acc_sum = correct_count.detach().cpu().numpy()\n        else:\n            acc_sum += correct_count.detach().cpu().numpy()\n        loss_w_sum += counts     \n\n        acc_details = [\"{:.5}: {:.4f}\".format(f, acc_sum[i]/loss_w_sum) for i, f in enumerate(CFG['image_target_cols'])]\n        acc_details = \", \".join(acc_details)\n            \n        if ((step + 1) % CFG['verbose_step'] == 0) or ((step + 1) == len(val_loader)):\n            print(\n                f'epoch {epoch} valid Step {step+1}/{len(val_loader)}, ' + \\\n                f'loss: {loss_sum/loss_w_sum:.3f}, ' + \\\n                acc_details + ', ' + \\\n                f'time: {(time.time() - t):.2f}', end='\\r' if (step + 1) != len(val_loader) else '\\n'\n            )\n    \n    if schd_loss_update:\n        scheduler.step(loss_sum/loss_w_sum)\n    else:\n        scheduler.step()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## The Actual Run"},{"metadata":{"trusted":true},"cell_type":"code","source":"seed_everything(SEED)\n\ntrain_df = pd.read_csv(CFG['train_path'])\ncv_df = pd.read_csv(CFG['cv_fold_path'])\ntrain_df = preprocess_DF(train_df)\ncv_df = preprocess_DF(cv_df)\n\nprint(train_df.shape)\nprint(cv_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for fold, (train_fold, valid_fold) in enumerate(zip(CFG['train_folds'], CFG['valid_folds'])):\n            if fold < 0:\n                continue\n            \n            print(fold)   \n            train_loader, val_loader = prepare_train_dataloader(train_df, cv_df, train_fold, valid_fold)\n\n            device = torch.device(CFG['device'])\n            model = stg1_study_model().to(device)\n            model.load_state_dict(torch.load('{}/model_{}'.format(CFG['stage_model_path'], CFG['tag'])))\n            scaler = GradScaler()   \n            optimizer = torch.optim.Adam(model.parameters(), lr=CFG['lr'])\n            scheduler = torch.optim.lr_scheduler.StepLR(optimizer, gamma=0.1, step_size=1); schd_loss_update=False\n            \n            for epoch in range(CFG['epochs']):\n                train_one_epoch(epoch, model, device, scaler, optimizer, train_loader)\n                \n                torch.save(model.state_dict(),'{}/model_{}'.format(CFG['model_path'], CFG['tag']))\n                \n                with torch.no_grad():\n                    valid_one_epoch(epoch, model, device, scheduler, val_loader, schd_loss_update=schd_loss_update)\n            \n            torch.save(model.state_dict(),'{}/model_{}'.format(CFG['model_path'], CFG['tag']))\n            \n            del model, optimizer, train_loader, val_loader, scaler, scheduler\n            torch.cuda.empty_cache()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","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}