{"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":"from torch import nn\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport os\nimport cv2\nfrom glob import glob\nimport numpy as np\nimport pandas as pd\nimport re\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nimport random\nfrom torch import optim\nimport time\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\n\n\ndef getAugmentationParams(rot=[-10,10],brig=[-30,30],con=[-30,50]):\n    result={'rotation':0,'flipImage':None,'brightness':0 ,'contrast':0}\n    opt1=np.random.randint(0, 2)\n    opt2=np.random.randint(0, 2)\n    opt3=np.random.randint(0, 2)\n    opt4=np.random.randint(0, 2)\n    if opt1==1:\n        result['rotation']=np.random.randint(rot[0],rot[1])\n    if opt2==1:\n        result['flipImage']=np.random.randint(-1,2)\n    if opt3==1:\n        result['brightness']=np.random.randint(brig[0],brig[1])\n    if opt4==1:\n        result['contrast']=np.random.randint(con[0],con[1])\n    \n    return result\n\ndef rotate_image(image, angle):\n    image_center = tuple(np.array(image.shape[1::-1]) / 2)\n    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)\n    result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)\n    return result\n\ndef flipImage(img,code):\n    '''\n    code = 0: flip vertically\n    code = 1: flip horizontally\n    code = -1: flip vertically and horizontally\n    '''\n    if code is None:\n        return img\n    img_flip_ud = cv2.flip(img, code)\n    return img_flip_ud\n\n\ndef apply_brightness_contrast(input_img, brightness = 0, contrast = 0):\n    '''\n    input img : cv2 image \n    brightness INT : number \n    contrast INT : number \n    '''\n    \n    if brightness != 0:\n        if brightness > 0:\n            shadow = brightness\n            highlight = 255\n        else:\n            shadow = 0\n            highlight = 255 + brightness\n        alpha_b = (highlight - shadow)/255\n        gamma_b = shadow\n        \n        buf = cv2.addWeighted(input_img, alpha_b, input_img, 0, gamma_b)\n    else:\n        buf = input_img.copy()\n    \n    if contrast != 0:\n        f = 131*(contrast + 127)/(127*(131-contrast))\n        alpha_c = f\n        gamma_c = 127*(1-f)\n        \n        buf = cv2.addWeighted(buf, alpha_c, buf, 0, gamma_c)\n\n    return buf\n\n\ndef max_pool(img_list):\n    img_list = np.array(img_list)\n    max_img = img_list.max(axis=0)\n    return max_img\n\n\ndef filter_images(img_list, img_size, threshold=0.9):\n    final = []\n    for img in img_list:\n        if (np.count_nonzero(img == 0) / img_size**2) < threshold:\n            final.append(img)\n        elif (np.count_nonzero(img == 0) / img_size**2) == 1:\n            final.append(img)\n            \n    return np.array(final)\n\n\ndef trim_empty_spaces(img_list, img_size):\n    trimmed_list = []\n    for img in img_list:\n        try:\n            trimmed = img[~np.all(img == 0, axis=1)]\n            trimmed = trimmed.T[~np.all(trimmed.T == 0, axis=1)]\n            trimmed = trimmed.T\n\n            trimmed = cv2.resize(trimmed, (img_size, img_size))\n            trimmed_list.append(trimmed)\n        except:\n            pass\n        \n    return np.array(trimmed_list)\n\n\ndef padding(img_list, num_imgs):\n    temp = []\n    for img in img_list:\n        for _ in range(num_imgs//len(img_list)+1):\n            temp.append(img)\n            \n    return np.array(temp)\n\n\ndef chunkIt(seq, num):\n    avg = len(seq) / float(num)\n    out = []\n    last = 0.0\n\n    while last < len(seq):\n        out.append(seq[int(last):int(last + avg)])\n        last += avg\n    \n    if len(out) > num:\n        out = out[:num]\n        \n    return out\n\n\ndef load_slices_3d(path_to_scan_dir, num_imgs=64, img_size=224,augmentations=None):\n    \"\"\"\n        path_to_scan_dir: the string to the scan location along with the mri type\n        num_imgs: (integer) the number of the slices to get from the scan returns 3d tensor of shape (num_imgs, img_size, img_size)        \n    \"\"\"\n    path_to_scan_dir = os.path.join(path_to_scan_dir, \"*\")\n    slices_path = sorted(glob(path_to_scan_dir), key=lambda var: [int(x) if x.isdigit() else x for x in re.findall(r'[^0-9]|[0-9]+', var)])\n    slices = [cv2.imread(f, cv2.IMREAD_GRAYSCALE) for f in slices_path]\n    \n    if augmentations is not None:\n        slices = [ flipImage(x,augmentations['flipImage']) for x in slices ]        \n        slices = [ apply_brightness_contrast(x,augmentations['brightness'],augmentations['contrast']) for x in slices ]\n        slices = [ rotate_image(x,augmentations['rotation']) for x in slices ]\n        \n\n    slices = [cv2.resize(img, (img_size, img_size)) for img in slices]\n    slices = filter_images(slices, img_size)\n    slices = trim_empty_spaces(slices, img_size)\n\n    if len(slices) < num_imgs:\n        slices = padding(slices, num_imgs)\n\n    slices = chunkIt(slices, num_imgs)\n    averaged_slices = []\n    for chunk in slices:\n        averaged_slices.append(max_pool(chunk))\n    tensor = np.array(averaged_slices)\n    tensor = np.reshape(averaged_slices, (1, tensor.shape[0], tensor.shape[1], tensor.shape[2]))\n    return torch.from_numpy(tensor).float()\n\n\nclass BrainTumorDataSet(Dataset):\n    def __init__(self, scan_paths, labels_df, img_size=224, num_images=64,augmentations=False,rot=[-10,10],brig=[-30,30],con=[-30,50]):\n        \"\"\"\n        Args:\n            scan_paths: (list of strings) a list containing paths to the each scan. each training example.\n            labels_df: dictionary[scan_id -> target]\n            labels_df: (pandas dataframe) a pandas dataframe containing the scan_id and labels. with the index column set to the scan_ids\n        \"\"\"\n        self.scan_paths = scan_paths\n        self.labels_df = labels_df\n        self.img_size = img_size\n        self.num_images = num_images\n        self.augmentations=augmentations\n        self.rot=rot\n        self.brig=brig\n        self.con=con\n        self.targets=labels_df\n\n    def __len__(self, ):\n        return len(self.scan_paths)\n\n    def __getitem__(self, idx):\n        self.label = np.eye(2)[self.targets[int(self.scan_paths[idx].split(os.sep)[-2])]]\n        if self.augmentations:\n            augment_params=getAugmentationParams(self.rot,self.brig,self.con)\n        else:\n            augment_params=None\n        self.tensor = load_slices_3d(\n            path_to_scan_dir=self.scan_paths[idx],\n            num_imgs=self.num_images,\n            img_size=self.img_size,\n        )\n        return (self.tensor, self.label)\n    \n    \ndef plot_image_grid(images, ncols=None, cmap='gray'):\n    '''Plot a grid of images'''\n    if not ncols:\n        factors = [i for i in range(1, len(images)+1) if len(images) % i == 0]\n        ncols = factors[len(factors) //\n                        2] if len(factors) else len(images) // 4 + 1\n    nrows = int(len(images) / ncols) + int(len(images) % ncols)\n    imgs = [images[i] if len(\n        images) > i else None for i in range(nrows * ncols)]\n    f, axes = plt.subplots(nrows, ncols, figsize=(3*ncols, 2*nrows))\n    axes = axes.flatten()[:len(imgs)]\n    for img, ax in zip(imgs, axes.flatten()):\n        if np.any(img):\n            if len(img.shape) > 2 and img.shape[2] == 1:\n                img = img.squeeze()\n            ax.imshow(img, cmap=cmap)\n            \nimgs = np.array(load_slices_3d(\"../input/rsna-miccai-png/test/00287/T1wCE\", 64)[0])\nplot_image_grid(imgs)","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:19:07.099784Z","iopub.execute_input":"2021-09-03T03:19:07.100160Z","iopub.status.idle":"2021-09-03T03:19:16.713915Z","shell.execute_reply.started":"2021-09-03T03:19:07.100080Z","shell.execute_reply":"2021-09-03T03:19:16.712844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install git+https://github.com/shijianjian/EfficientNet-PyTorch-3D\nfrom efficientnet_pytorch_3d import EfficientNet3D\n\nclass Model(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.net = EfficientNet3D.from_name(\"efficientnet-b0\", override_params={'num_classes': 1}, in_channels=1)\n        n_features = self.net._fc.in_features\n        self.net._fc = nn.Linear(in_features=n_features, out_features=2, bias=True)\n    \n    def forward(self, x):\n        out = self.net(x)\n        return out\n    ","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:19:16.715360Z","iopub.execute_input":"2021-09-03T03:19:16.715720Z","iopub.status.idle":"2021-09-03T03:19:27.615997Z","shell.execute_reply.started":"2021-09-03T03:19:16.715685Z","shell.execute_reply":"2021-09-03T03:19:27.615082Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Trainer:\n    def __init__(self, model, criterion, optimizer, device, metric='auc'):\n        self.model = model.to(device=device)\n        self.criterion = criterion\n        self.optimizer = optimizer\n        self.device = device\n        if metric == 'auc':\n            self.metric = roc_auc_score\n        else:\n            self.metric == metric\n        self.best_val_loss = np.inf\n        self.patience = 0\n    \n    @staticmethod\n    def logger(message, *args, end=\"\\n\"):\n        print(message.format(*args), end=end)    \n    \n    \n    def fit(self, trainLoader, validLoader, save_path, epochs=10, patience=3):\n        self.logger(\"Training Started at: {}\", time.ctime())\n        \n        for epoch in range(1, epochs+1):\n            train_loss, train_time = self.train_epoch(trainLoader)\n            self.logger(\"Epoch {} Train  ||  loss: {:.4f}  ||  Time taken {:.1f}s\", epoch, train_loss, train_time)            \n            val_loss, val_accuracy, val_time = self.valid_epoch(validLoader)\n            self.logger(\"Epoch {} Valid  ||  loss: {:.4f}  ||  accuracy: {:.4f}  ||  Time taken {:.1f}s\", epoch, val_loss, val_accuracy, val_time)\n            \n            \n            if self.best_val_loss > val_loss:\n                self.save_model(epoch, save_path, val_loss, val_accuracy)\n                self.logger(\"Validation Loss imporoved from {:.4f} to {:.4f}. Saving the model\", self.best_val_loss, val_loss)\n                self.best_val_loss = val_loss\n                self.patience = 0\n            else:\n                self.patience += 1\n            if self.patience >= patience:\n                self.logger(\"\\nValid auc didn't improve last {} epochs. Early Stopping\", patience)\n                break\n            self.logger(\"--------------------------------\")\n    \n    \n    def train_epoch(self, trainLoader):\n        tick = time.time()\n        sum_loss = 0\n        allOutputs, allTargets = [], []\n        self.model.train()\n\n        for idx, (X, targets) in enumerate(trainLoader):\n            X = X.to(device=self.device)\n            targets = targets.to(device=self.device).float()\n\n            self.optimizer.zero_grad()\n            \n            outputs = self.model(X).squeeze(1)\n            loss = self.criterion(outputs, targets)\n\n            loss.backward()\n            sum_loss += loss.detach().item()\n            self.optimizer.step()\n            \n            self.logger(\"Training Batch {}/{}  ||  loss: {:.4f} \", idx+1, len(trainLoader), loss, end='\\r')\n        \n        return sum_loss/len(trainLoader), time.time()-tick\n        \n        \n    def valid_epoch(self, validLoader):\n        self.model.eval()\n        tick = time.time()\n        sum_loss = 0\n        allOutputs, allTargets = [], []\n\n        for idx, (X, targets) in enumerate(validLoader):\n            with torch.no_grad():\n                X = X.to(device=self.device)\n                targets = targets.to(device=self.device).float()\n                \n                outputs = model(X).squeeze(1)\n                loss = self.criterion(outputs, targets)\n                \n                sum_loss += loss.detach().item()\n                allTargets.extend(targets.tolist())\n                allOutputs.extend(torch.sigmoid(outputs).tolist())\n                \n            self.logger(\"Validating Batch {}/{}  ||  loss: {:.4f} \", idx+1, len(validLoader), loss, end='\\r')\n\n#         allTargets = [max(x) for x in allTargets]\n#         auc = self.metric(allTargets, allOutputs)\n        auc = 0.0001\n        return sum_loss/len(validLoader), auc, time.time()-tick\n    \n    \n    def save_model(self, n_epoch, save_path, loss, auc):\n        self.lastmodel = f\"RSNA-{save_path}-e{n_epoch}-loss{loss:.3f}-auc{auc:.3f}.pth\"\n        \n        torch.save(\n            {\n                \"model_state_dict\": self.model.state_dict(),\n                \"optimizer_state_dict\": self.optimizer.state_dict(),\n                \"best_valid_score\": self.best_val_loss,\n                \"n_epoch\": n_epoch,\n            },\n            self.lastmodel,\n        )\n\n","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:19:27.619562Z","iopub.execute_input":"2021-09-03T03:19:27.619829Z","iopub.status.idle":"2021-09-03T03:19:27.835861Z","shell.execute_reply.started":"2021-09-03T03:19:27.619800Z","shell.execute_reply":"2021-09-03T03:19:27.835018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nBATCH_SIZE = 4\nEPOCHS = 5\nIMG_SIZE = 256\nNUM_IMGS = 64\nPATIENCE = 5\nLEARNING_RATE = 0.00008\nMRI_TYPE = \"FLAIR\"\n\nmodel = Model()\n\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\ndata_directory=\"../input/rsna-miccai-brain-tumor-radiogenomic-classification\"\nlabels_df= pd.read_csv(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train_labels.csv\")\n\ntrain_x,test_x,trainy,test_y=  train_test_split(labels_df,labels_df['MGMT_value'],test_size =0.2)\nfile_train_list= train_x.BraTS21ID.astype(str).str.zfill(5)\nfile_valid_list= test_x.BraTS21ID.astype(str).str.zfill(5)\nvalidation_file_list = [ x  for x in glob('../input/rsna-miccai-png/train/*/{}'.format(MRI_TYPE)) if x.split(os.sep)[-2] in file_valid_list.values  ]\ntrain_file_list = [ x  for x in glob('../input/rsna-miccai-png/train/*/{}'.format(MRI_TYPE)) if x.split(os.sep)[-2] in file_train_list.values  ]\n\n\ntrainDataSet= BrainTumorDataSet(\ntrain_file_list, labels_df = {x[1]:x[2] for x in  labels_df.itertuples()}, img_size=IMG_SIZE, num_images=NUM_IMGS,\n    augmentations=True\n)\n\ntestDataSet= BrainTumorDataSet(\nvalidation_file_list, labels_df = {x[1]:x[2] for x in  labels_df.itertuples()}, img_size=IMG_SIZE, num_images=NUM_IMGS,\n    augmentations=False\n)\n\ntrainLoader = DataLoader(trainDataSet, batch_size=BATCH_SIZE, prefetch_factor=2)\ntestLoader = DataLoader(testDataSet, batch_size=BATCH_SIZE, prefetch_factor=2)","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:39:09.434924Z","iopub.execute_input":"2021-09-03T03:39:09.435239Z","iopub.status.idle":"2021-09-03T03:39:09.951637Z","shell.execute_reply.started":"2021-09-03T03:39:09.435209Z","shell.execute_reply":"2021-09-03T03:39:09.950758Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainer = Trainer(model, criterion, optimizer, device)\ntrainer.fit(trainLoader, testLoader, \"FLAIR\", EPOCHS, PATIENCE)","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:39:09.983619Z","iopub.execute_input":"2021-09-03T03:39:09.983881Z","iopub.status.idle":"2021-09-03T04:02:47.761691Z","shell.execute_reply.started":"2021-09-03T03:39:09.983855Z","shell.execute_reply":"2021-09-03T04:02:47.759682Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"execution":{"iopub.status.busy":"2021-09-03T03:18:29.154175Z","iopub.status.idle":"2021-09-03T03:18:29.154982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}