{"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":"code","source":"# https://www.kaggle.com/code/samuelcortinhas/rsna-fracture-detection-in-depth-eda/data\n# https://www.kaggle.com/competitions/rsna-breast-cancer-detection/discussion/369259\n#https://www.kaggle.com/competitions/rsna-breast-cancer-detection/discussion/369531\ntry:\n    import pylibjpeg\nexcept:\n   !pip install /kaggle/input/rsna-2022-whl/{pylibjpeg-1.4.0-py3-none-any.whl,python_gdcm-3.0.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl}\n\nprint('install pylibjpeg, python_gdcm ok!')\n#----------------\n\nimport sys, os\nsys.path.append('/kaggle/input/rsna-breast-mammography-00')\n\n\nimport pandas as pd\nimport numpy as np\nimport pydicom\nimport cv2\n\nfrom timeit import default_timer as timer\n\nfrom tqdm.notebook import tqdm\nfrom joblib import Parallel, delayed\nfrom glob import glob\nfrom sklearn import metrics\n\nimport torch\nfrom torch.utils.data.dataset import Dataset\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data.sampler import *\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.cuda.amp as amp\n#print( 'torch.cuda.device_count() = %d'%torch.cuda.device_count())\n#print( 'torch.cuda.get_device_properties() = %s' % str(torch.cuda.get_device_properties(0))[21:])\n\nimport timm\nprint('timm',timm.__version__)\n#print(timm.__file__)\nfrom timm.models.resnet import *\nfrom timm.models.efficientnet import *\n\n\ndef time_to_str(t, mode='min'):\n    if mode=='min':\n        t  = int(t)/60\n        hr = t//60\n        min = t%60\n        return '%2d hr %02d min'%(hr,min)\n\n    elif mode=='sec':\n        t   = int(t)\n        min = t//60\n        sec = t%60\n        return '%2d min %02d sec'%(min,sec)\n\n    else:\n        raise NotImplementedError\n\n\nprint('import ok!')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-12-03T15:13:58.789629Z","iopub.execute_input":"2022-12-03T15:13:58.790675Z","iopub.status.idle":"2022-12-03T15:13:58.807208Z","shell.execute_reply.started":"2022-12-03T15:13:58.790617Z","shell.execute_reply":"2022-12-03T15:13:58.806178Z"}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_size = 512\n\nmode = 'submit-dicom'   #'local-dicom' #local-image  submit-dicom\n\n\nif 'local' in mode:\n    csv_file = '/kaggle/input/rsna-breast-mammography-00/valid_df.fold0.csv'\n    dcm_dir  = '/kaggle/input/rsna-breast-cancer-detection/train_images'\n\nif 'submit' in mode:\n    csv_file = '/kaggle/input/rsna-breast-cancer-detection/test.csv'\n    dcm_dir  = '/kaggle/input/rsna-breast-cancer-detection/test_images'\n    \nif 'dicom' in mode:\n    image_dir = '/kaggle/tmp/~png'\n    os.makedirs(image_dir, exist_ok=True)\n\n\n\ntest_df = pd.read_csv(csv_file)\ntest_df.loc[:, 'i'] = np.arange(len(test_df))\nif 'local' in mode:\n    test_df.loc[:, 'prediction_id'] = test_df.patient_id.astype(str) + '_' + test_df.laterality\n    if 'dicom' in mode:\n        test_id = [\n            826, 1703, 1759, 2346, 2989, 3021, 3542, 4340, 4824, 5059,\n            5769, 6654, 6658, 7053, 7493, 7780, 9014, 11094, 11937, 14292,\n            30, 36, 65, 90, 111, 122, 127, 152, 158, 204,\n            272, 282, 289, 299, 308, 399, 425, 454, 477, 505,\n        ]\n        test_df = test_df[test_df.patient_id.isin(test_id)].reset_index(drop=True)\n\nprint('test_df', test_df.shape)\nprint(test_df)\n\n\n#-------------------------------------------------------------------------------\n\ndef dicom_to_png(dcm_file, image_size=image_size, image_dir=''):\n    patient_id = dcm_file.split('/')[-2]\n    image_id   = dcm_file.split('/')[-1][:-4]\n\n    dicom = pydicom.dcmread(dcm_file)\n    img = dicom.pixel_array\n    img = (img - img.min()) / (img.max() - img.min())\n    if dicom.PhotometricInterpretation == 'MONOCHROME1':\n        img = 1 - img\n\n    img = cv2.resize(img, (image_size, image_size), interpolation=cv2.INTER_LINEAR)\n    img = (img * 255).astype(np.uint8)\n    cv2.imwrite(image_dir +'/'+ f'{patient_id}_{image_id}.png', img)\n\n\nif 'dicom' in mode:\n    dcm_file = dcm_dir + '/' + test_df.patient_id.astype(str) + '/'  + test_df.image_id.astype(str) + '.dcm'\n    #dcm_file = glob(f'{dcm_dir}/*/*.dcm')\n    #print(dcm_file)\n    Parallel(n_jobs=2)(\n        delayed(dicom_to_png)(f, image_size=image_size, image_dir=image_dir)\n        for f in tqdm(dcm_file)\n    )\n\n\n##------------------------\n\ndef read_data(df):\n    image = []\n    for t, d in df.iterrows():\n        m = cv2.imread(f'{image_dir}/{d.patient_id}_{d.image_id}.png', cv2.IMREAD_GRAYSCALE)\n        image.append(m)\n\n    image = np.stack(image)\n    return image\n\n\nclass RsnaDataset(Dataset):\n    def __init__(self, df):\n\n        patient_id =  sorted(df.patient_id.unique())\n        self.patient_id = patient_id\n        self.length = len(patient_id)\n        self.df = df\n\n    def __len__(self):\n        return self.length\n\n    def __getitem__(self, index):\n        patient_id = self.patient_id[index]\n        df = self.df[self.df.patient_id == patient_id].reset_index(drop=True)\n        image = read_data(df)\n        image = image.astype(np.float32)/255\n\n        r = {}\n        r['index'] = index\n        r['patient_id'] = patient_id\n        r['df'] = df\n        r['num'] = len(df)\n        r['image'] = torch.from_numpy(image).float()\n        return r\n\ntensor_key = ['image']\ndef null_collate(batch):\n    d = {}\n    key = batch[0].keys()\n    for k in key:\n        d[k] = [b[k] for b in batch]\n\n    d['image'] = torch.cat(d['image'],0).unsqueeze(1)\n    return d\n\n\n","metadata":{"execution":{"iopub.status.busy":"2022-12-03T15:13:58.809161Z","iopub.execute_input":"2022-12-03T15:13:58.809892Z","iopub.status.idle":"2022-12-03T15:14:02.500521Z","shell.execute_reply.started":"2022-12-03T15:13:58.809852Z","shell.execute_reply":"2022-12-03T15:14:02.499225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nclass RGB(nn.Module):\n    IMAGE_RGB_MEAN = [0.5, 0.5, 0.5] #[0.485, 0.456, 0.406]\n    IMAGE_RGB_STD  = [0.5, 0.5, 0.5] #[0.229, 0.224, 0.225]\n\n    def __init__(self, ):\n        super(RGB, self).__init__()\n        self.register_buffer('mean', torch.zeros(1, 3, 1, 1))\n        self.register_buffer('std', torch.ones(1, 3, 1, 1))\n        self.mean.data = torch.FloatTensor(self.IMAGE_RGB_MEAN).view(self.mean.shape)\n        self.std.data = torch.FloatTensor(self.IMAGE_RGB_STD).view(self.std.shape)\n\n    def forward(self, x):\n        x = (x - self.mean) / self.std\n        return x\n\nclass ResNet(nn.Module):\n    def __init__(self,):\n        super(ResNet, self).__init__()\n        self.rgb = RGB()\n        self.encoder = seresnext50_32x4d(pretrained=False)\n        self.cancer  = nn.Linear(2048,1)\n\n    def forward(self, batch):\n        x = batch['image']\n        x = x.expand(-1,3,-1,-1)\n        x = self.rgb(x)\n\n        #------\n        e = self.encoder\n        x = e.forward_features(x)\n        x = F.adaptive_avg_pool2d(x,1)\n        x = torch.flatten(x,1,3)\n        cancer = self.cancer(x)\n\n        cancer = cancer.reshape(-1)\n        cancer = torch.sigmoid(cancer)\n        cancer = torch.nan_to_num(cancer)\n        return cancer\n\n\nclass EffNet(nn.Module):\n    def __init__(self,):\n        super(EffNet, self).__init__()\n        self.rgb = RGB()\n        self.encoder = efficientnet_b4(pretrained=False)\n        self.cancer  = nn.Linear(1792,1)\n\n    def forward(self, batch):\n        x = batch['image']\n        x = x.expand(-1,3,-1,-1)\n        x = self.rgb(x)\n\n        #------\n        e = self.encoder\n        x = e.forward_features(x)\n        x = F.adaptive_avg_pool2d(x,1)\n        x = torch.flatten(x,1,3)\n        cancer = self.cancer(x)\n\n        cancer = cancer.reshape(-1)\n        cancer = torch.sigmoid(cancer)\n        cancer = torch.nan_to_num(cancer)\n        return cancer\n\n\n","metadata":{"execution":{"iopub.status.busy":"2022-12-03T15:14:02.503257Z","iopub.execute_input":"2022-12-03T15:14:02.503563Z","iopub.status.idle":"2022-12-03T15:14:02.520642Z","shell.execute_reply.started":"2022-12-03T15:14:02.503533Z","shell.execute_reply":"2022-12-03T15:14:02.519718Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ndef run_submit():\n    model = [\n        [ResNet,'/kaggle/input/rsna-breast-mammography-weight-01/seresnext50_32x4d-512-fold0-00009366.model.pth'],\n        [ResNet,'/kaggle/input/rsna-breast-mammography-weight-01/seresnext50_32x4d-512-fold1-00016056.model.pth'],\n        [EffNet,'/kaggle/input/rsna-breast-mammography-weight-01/efficientnet_b4-512-fold0-00012042.model.pth'],\n        [EffNet,'/kaggle/input/rsna-breast-mammography-weight-01/efficientnet_b4-512-fold1-00012042.model.pth'],\n    ]\n    num_net = len(model)\n\n    net = []\n    for i in range(num_net):\n        Net, checkpoint = model[i]\n        n = Net()\n        f = torch.load(checkpoint, map_location=lambda storage, loc: storage)\n        n.load_state_dict(f['state_dict'], strict=True)  # True\n        n.cuda()\n        n.eval()\n        net.append(n)\n\n    #----\n    test_dataset = RsnaDataset(test_df)\n    test_loader = DataLoader(\n        test_dataset,\n        sampler = SequentialSampler(test_dataset),\n        batch_size  = 8,\n        drop_last   = False,\n        num_workers = 2,\n        pin_memory  = False,\n        collate_fn = null_collate,\n    )\n\n    #----\n    if 1:\n        result = {\n            'i':[],\n            'probability':[],\n        }\n        test_num = 0\n\n        start_timer = timer()\n        for t, batch in enumerate(test_loader):\n            batch_size = len(batch['index'])\n            batch['image'] = batch['image'].cuda()\n\n            p = 0\n            count = 0\n            with torch.no_grad():\n                with amp.autocast(enabled=True):\n                    for i in range(num_net):\n                        p += net[i](batch)  # net(input)#\n                        count += 1\n\n                        # TTA\n                        if 1:\n                            batch['image'] = torch.flip(batch['image'], dims=[3, ])\n                            p += net[i](batch)\n                            count += 1\n\n            p = p / count\n            result['probability'].append(p.float().data.cpu().numpy())\n            result['i'].append(pd.concat(batch['df'])['i'].values)\n            test_num += batch_size\n            print('\\r %8d / %d  %s' % (test_num, len(test_dataset), time_to_str(timer() - start_timer, 'sec')), end='', flush=True)\n        print('')\n\n        #---\n        probability = np.concatenate(result['probability'])\n        i = np.concatenate(result['i'])\n        argsort = np.argsort(i)\n        i = i[argsort]\n        probability = probability[argsort]\n        np.save('probability.npy',probability)\n        #exit(0)\n\n    #----\n    probability = np.load('probability.npy')\n    print('probability', probability.shape)\n    print('')\n\n    submit_df = pd.DataFrame({'prediction_id':test_df.prediction_id})\n    submit_df.loc[:, 'cancer'] = probability\n    submit_df = submit_df.groupby('prediction_id').max() #.mean() #\n    #submit_df = (submit_df.groupby('prediction_id').mean()  + submit_df.groupby('prediction_id').max())/2\n    submit_df = submit_df.sort_index()\n    submit_df.loc[:, 'cancer'] = (submit_df.cancer.values>0.23).astype(np.float32)\n    \n    submit_df.to_csv('submission.csv',index=True)\n    print('submit_df',submit_df)\n    print(submit_df.values.mean())\n    print('')\n\n    if 'local' in mode: \n\n        # https://www.kaggle.com/code/sohier/probabilistic-f-score\n        def pfbeta(labels, predictions, beta=1):\n            y_true_count = 0\n            ctp = 0\n            cfp = 0\n\n            for idx in range(len(labels)):\n                prediction = min(max(predictions[idx], 0), 1)\n                if (labels[idx]):\n                    y_true_count += 1\n                    ctp += prediction\n                else:\n                    cfp += prediction\n\n            beta_squared = beta * beta\n            c_precision = ctp / (ctp + cfp)\n            c_recall = ctp / y_true_count\n            if (c_precision > 0 and c_recall > 0):\n                result = (1 + beta_squared) * (c_precision * c_recall) / (beta_squared * c_precision + c_recall)\n                return result\n            else:\n                return 0\n\n        truth_df = test_df[['prediction_id', 'cancer']].groupby('prediction_id').mean()\n        truth_df = truth_df.sort_index()\n        print('truth_df', truth_df)\n        print(truth_df.values.mean())\n        print('')\n\n        truth = truth_df.cancer.values\n        predict = submit_df.cancer.values\n\n        lb_score = pfbeta(truth, predict)\n        print('lb_score', lb_score)\n\n        auc = metrics.roc_auc_score(truth, predict)\n        print('auc', auc)\n\nrun_submit()","metadata":{"execution":{"iopub.status.busy":"2022-12-03T15:14:02.522468Z","iopub.execute_input":"2022-12-03T15:14:02.523038Z","iopub.status.idle":"2022-12-03T15:14:05.571369Z","shell.execute_reply.started":"2022-12-03T15:14:02.523002Z","shell.execute_reply":"2022-12-03T15:14:05.570401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"the training code can be found at:\nhttps://www.kaggle.com/datasets/hengck23/for-tpu-efficientb4-debug\n\nto train refer to:\nrun_train.py.\n\nIf you have problem in reproducing the training results in kaggle notebook, please the followings as reference:\n![https://i.ibb.co/yYL37p7/Selection-999-2086.png](https://i.ibb.co/yYL37p7/Selection-999-2086.png)\n\n\nWe can observed the followings:\n1. modern transformer is better than traditional CNN achieving better accuracy performance in cross validation CV (0.64 vs 0.24) and leaderboard LB (0.54 vs 0.51).\n\n2. large input image resolution 2048 is crucial to accuracy. This is becuase cancel lesion is really small in large X-ray breast images\n\n3. It is somtimes difficult to use kaggle notebook for training image classification model with large image resolution:\n  - long training time (kaggle free cloud gpu is limited to 30 hrs per week)\n  - limited VRAM. Kaggle VRAM is limited to 16GB for P100 Nvidi GPU, which may be be sufficient for training vision transformer at 2048 resolution. \n  - difficult augment in training becuase there is only 2 worker threads. It takes quite some time to process large image for CPU.\n  \nA local PC like Z8 HP workstation will have advanatgesin this case, giving better results with less training hours. ","metadata":{}}]}