{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"!cp ../input/gdcm-conda-install/gdcm.tar .\n!tar  -xzf gdcm.tar\n!conda install -q --offline ./gdcm/gdcm-2.8.9-py37h71b2a6d_0.tar.bz2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# !pip install -qq ../input/lungmask/lungmask-master/lungmask-master/","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"from fastai.vision.all import *\nfrom fastai.medical.imaging import *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"datapath = Path(\"/kaggle/input/rsna-str-pulmonary-embolism-detection/\")\ncnnmodelpath = Path(\"/kaggle/input/rsnastrpecnnmodel/\")\nseqmodelpath = Path(\"/kaggle/input/rsnastrpeseqmodel/\")\ntest_df = pd.read_csv(datapath/'test.csv')\nsub_df = pd.read_csv(datapath/'sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"[o for o in sub_df['id'].values if \"df06fad17bc3\" in o]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Predict Study"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_study_dirnames = [datapath/'test'/o for o in test_df['StudyInstanceUID'].unique()]\nstudy_dirname = test_study_dirnames[0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# RGB windows\nlung_window = (1500, -600)\npe_window = (700, 100)\nmediastinal_window = (400, 40)\nwindows = (lung_window, pe_window, mediastinal_window)\n\ndef read_dcm_img(dcm, windows=windows):\n    \"Read single slice in RGB\"\n    return torch.stack([dcm.windowed(*w) for w in windows])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### CNN Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load CNN model\ndef get_dls(tensors, size=256, bs=128):\n    \"Get study dataloader\"\n    tfms = [[RandomResizedCropGPU(size, min_scale=0.9)], []]\n\n    dsets = Datasets(tensors, tfms=tfms, splits=([0,1], [2,3]))\n\n    batch_tfms = [Normalize.from_stats(*imagenet_stats)]\n    dls = dsets.dataloaders(bs=bs, after_batch=batch_tfms, num_workers=2)\n    return dls\n\ndls = get_dls(torch.zeros(4, 3, 224, 224), bs=32)\ndls.c = 2\nlearn = cnn_learner(dls, xresnet34, pretrained=False, loss_func=nn.CrossEntropyLoss())\nlearn.path = Path(\"/kaggle/input/rsnastrpecnnmodel/\")\nlearn.load('xresnet34-256_3');","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Sequence Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"input_pad_idx = None","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class MultiHeadedSequenceClassifier(Module):\n    \"dim: input sequence feature dim\"\n    def __init__(self, input_pad_idx=input_pad_idx, dim=1024):\n        \n        store_attr('input_pad_idx')\n        self.lstm1 = nn.LSTM(dim+5, dim//16, bidirectional=True)\n        \n        # image level preds\n        self.seq_cls_head = nn.Linear(dim//8, 1)\n    \n        \n        # positive, negative, indeterminate\n        self.pe_head = nn.Linear(dim//4, 3) # softmax\n        # rv / lv >=,  < 1 or neither\n        self.rv_lv_head = nn.Linear(dim//4, 3) # softmax\n        # l,r,c pe\n        self.pe_position_head = nn.Linear(dim//4, 3) # sigmoid\n        # chronic, ac-chr or neither\n        self.chronic_pe_head = nn.Linear(dim//4, 3) # softmax\n        \n    \n    def forward(self, x):\n        \n        # get mask from non-pad idxs and then features\n        mask = x != self.input_pad_idx\n        x = torch.cat([embs[x], meta_embs[x]], dim=-1).to(device)\n        \n        # sequence outs\n        x, _ = self.lstm1(x) \n#         x, _ = self.lstm2(x)\n        seq_cls_out = self.seq_cls_head(x).squeeze(-1)\n        \n        \n        #masked concat pool\n        pooled_x = []\n        for i in range(x.size(0)):\n            xi = x[i, mask[i], :]\n            pooled_x.append(torch.cat([xi.mean(0), xi.max(0).values]).unsqueeze(0))\n        pooled_x = torch.cat(pooled_x)\n        \n\n        # 'POSITIVE','negative_exam_for_pe','indeterminate'\n        out1 = self.pe_head(pooled_x)\n\n        # 'rv_lv_ratio_gte_1','rv_lv_ratio_lt_1', 'NEITHER'\n        out2 = self.rv_lv_head(pooled_x)\n\n        # 'leftsided_pe','rightsided_pe','central_pe',\n        out3 = self.pe_position_head(pooled_x)\n\n        # 'chronic_pe','acute_and_chronic_pe','NEITHER'\n        out4 = self.chronic_pe_head(pooled_x)\n\n        return (seq_cls_out, out1, out2, out3, out4)\n    \n    def predict(self, x):\n        \n        # sequence outs\n        x, _ = self.lstm1(x) \n        seq_cls_out = self.seq_cls_head(x).squeeze(-1)\n        \n        pooled_x = torch.cat([x.mean(1), x.max(1).values], dim=1)\n        \n\n        # 'POSITIVE','negative_exam_for_pe','indeterminate'\n        out1 = self.pe_head(pooled_x)\n\n        # 'rv_lv_ratio_gte_1','rv_lv_ratio_lt_1', 'NEITHER'\n        out2 = self.rv_lv_head(pooled_x)\n\n        # 'leftsided_pe','rightsided_pe','central_pe',\n        out3 = self.pe_position_head(pooled_x)\n\n        # 'chronic_pe','acute_and_chronic_pe','NEITHER'\n        out4 = self.chronic_pe_head(pooled_x)\n\n        return (seq_cls_out, out1, out2, out3, out4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load Sequence Model\nseqmodel = MultiHeadedSequenceClassifier()\nseqmodel.load_state_dict(torch.load(\"/kaggle/input/rsnastrpeseqmodel/models/best_seqmodel.pth\"));\ndevice = default_device()\nseqmodel.eval()\nseqmodel.to(device);","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Metadata"},{"metadata":{"trusted":true},"cell_type":"code","source":"meta_mean_std = {'img_min': (-1409.7525910396214, 920.6624071834135),\n                 'img_max': (2997.565154356599, 1375.5195189199717),\n                 'img_mean': (159.1868599739921, 280.4988584140103),\n                 'img_std': (916.7543430215497, 378.53540952883),\n                 'scaled_position': (0.5078721739409208, 0.29139548181397373)}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Predict"},{"metadata":{"trusted":true},"cell_type":"code","source":"class EmbeddingHook:\n    def __init__(self, m):\n        self.embeddings, self.m = tensor([]).to(device), m\n        if len(m._forward_hooks) > 0: self.reset()\n        self.hook = Hook(m, self.hook_fn, cpu=False)\n       \n    def hook_fn(self, m, inp, out): \n        \"Stack and save computed embeddings\"\n        self.embeddings = torch.cat([self.embeddings, out])\n    \n    def reset(self): \n        self.m._forward_hooks = OrderedDict()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"meta_feats = ['img_min', 'img_max', 'img_mean', 'img_std', 'scaled_position']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def minmax_scaler(o): return (o - min(o))/(max(o) - min(o))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# from time import time\n# study_dirname = np.random.choice(test_study_dirnames)\n# s = time()\n# # get metadata\n# dcmfiles = get_dicom_files(study_dirname)\n# dcm_metadf = (pd.DataFrame.from_dicoms(dcmfiles, window=pe_window)\n#                           .sort_values(['ImagePositionPatient2'])\n#                           .reset_index(drop=True))\n# study_fnames = dcm_metadf['fname'].values\n# sop_ids = [Path(o).stem for o in study_fnames]\n# e = time()\n# print(e-s)\n\n# s = time()\n# # get ordered imgs\n# dcm_ds = [Path(o).dcmread() for o in study_fnames]\n# imgs = torch.stack([read_dcm_img(o) for o in dcm_ds])\n# e = time()\n# print(e-s)\n\n# imgs.shape\n\n# s = time()\n# hook = EmbeddingHook(learn.model[1][1])\n# test_dl = dls.test_dl(imgs.numpy(), bs=32)\n# cnn_preds,_ = learn.get_preds(dl=test_dl)\n# features = hook.embeddings.unsqueeze(0)\n# e = time()\n# print(e-s)\n\n# s = time()\n# dcm_metadf['scaled_position'] = (dcm_metadf.groupby('StudyInstanceUID')['ImagePositionPatient2']\n#                                            .apply(minmax_scaler))\n# for f in meta_feats: dcm_metadf[f] = (dcm_metadf[f] - meta_mean_std[f][0]) / meta_mean_std[f][1]\n# meta_features = tensor(dcm_metadf[meta_feats].to_numpy()).to(device)\n# multi_preds = to_detach(seqmodel.predict(torch.cat([features, meta_features[None, ...]], dim=-1)))\n# e = time()\n# print(e-s)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict_study(study_dirname):\n    # get metadata\n    dcmfiles = get_dicom_files(study_dirname)\n    dcm_metadf = (pd.DataFrame.from_dicoms(dcmfiles, window=pe_window)\n                              .sort_values(['ImagePositionPatient2'])\n                              .reset_index(drop=True))\n    study_fnames = dcm_metadf['fname'].values\n    sop_ids = [Path(o).stem for o in study_fnames]\n\n    # get ordered imgs\n    dcm_ds = [Path(o).dcmread() for o in study_fnames]\n    imgs = torch.stack([read_dcm_img(o) for o in dcm_ds])\n\n    # get predictions\n    with torch.no_grad():\n        hook = EmbeddingHook(learn.model[1][1])\n        test_dl = dls.test_dl(imgs.numpy(), bs=32)\n        cnn_preds,_ = learn.get_preds(dl=test_dl)\n        features = hook.embeddings.unsqueeze(0)\n        dcm_metadf['scaled_position'] = (dcm_metadf.groupby('StudyInstanceUID')['ImagePositionPatient2']\n                                                   .apply(minmax_scaler))\n        for f in meta_feats: dcm_metadf[f] = (dcm_metadf[f] - meta_mean_std[f][0]) / meta_mean_std[f][1]\n        meta_features = tensor(dcm_metadf[meta_feats].to_numpy()).to(device)\n        multi_preds = to_detach(seqmodel.predict(torch.cat([features, meta_features[None, ...]], dim=-1)))\n    \n    return (multi_preds, sop_ids, cnn_preds)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_study_probas(sid, seq_cls_out, out1, out2, out3, out4):\n    sub_res = []\n    # image probas\n    for sopid, p in zip(sop_ids, to_np(seq_cls_out.sigmoid()[0])):\n        sub_res.append((sopid, p))\n\n    # exam probas\n    pos_pe_proba, neg_pe_proba, ind_pe_proba = to_np(out1[0].softmax(0))\n    rvlv_gte, rvlv_lt, rvlv_none = to_np(out2[0].softmax(0))\n    left_pe, right_pe, central_pe = to_np(torch.sigmoid(out3[0]))\n    chronic, acute_chronic, chronic_none = to_np(out4[0].softmax(0))\n    sub_res += [(f\"{sid}_negative_exam_for_pe\", neg_pe_proba),\n                (f\"{sid}_indeterminate\", ind_pe_proba),\n                (f\"{sid}_rv_lv_ratio_gte_1\", rvlv_gte),\n                (f\"{sid}_rv_lv_ratio_lt_1\", rvlv_lt),\n                (f\"{sid}_leftsided_pe\", left_pe),\n                (f\"{sid}_rightsided_pe\", right_pe),\n                (f\"{sid}_central_pe\", central_pe),\n                (f\"{sid}_chronic_pe\", chronic),\n                (f\"{sid}_acute_and_chronic_pe\", acute_chronic)]\n    return sub_res\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"do_full = False\nn = 20\n\nif Path('../input/rsna-str-pulmonary-embolism-detection/train').exists() and not do_full: \n    test_study_dirnames = [datapath/'test'/o for o in test_df['StudyInstanceUID'].unique()]\n    test_study_dirnames = np.random.choice(test_study_dirnames, n, replace=False)\n\nsub_res = []\nfor study_dirname in test_study_dirnames:\n    (seq_cls_out, out1, out2, out3, out4), sop_ids, cls_preds = predict_study(study_dirname)\n    study_res = get_study_probas(study_dirname.stem, seq_cls_out, out1, out2, out3, out4)\n    sub_res += study_res\n\nfinal_sub_df = pd.DataFrame(sub_res, columns=['id', 'label'])\nfinal_sub_df.to_csv(\"submission.csv\", index=False)","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}