{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\n!pip install pydicom\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#for dirname, _, filenames in os.walk('/kaggle/input'):\n    #for filename in filenames:\n        #print(os.path.join(dirname, filename))\n\n# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"#Load the csv files\ntrain_csv_path = \"/kaggle/input/rsna-str-pulmonary-embolism-detection/train.csv\"\ntest_csv_path = \"/kaggle/input/rsna-str-pulmonary-embolism-detection/test.csv\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Create a dataframe object from csv file\ntrain_csv = pd.read_csv(train_csv_path)\ntest_csv = pd.read_csv(test_csv_path)\ntrain_csv[\"image_path\"] = train_csv[\"StudyInstanceUID\"] + \"/\" + train_csv[\"SeriesInstanceUID\"] + \"/\" + train_csv[\"SOPInstanceUID\"]\ntrain_csv","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Initially I will load a subset of images to test the model as it will be quicker.\n    #Study Instance ID: 6897fa9de148\n    #Series Instance ID: 2bfbb7fd2e8b\n    \nStudyInstance = \"6897fa9de148\"\nSeriesInstance = \"2bfbb7fd2e8b\"\n\nImage_path = \"/kaggle/input/rsna-str-pulmonary-embolism-detection/train/\"\n\n#List the file path for each image in one directory.\n#for image in os.listdir(Image_path):\n #   print(os.path.join(Image_path + image))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Display one image from the directory to check code is working properly.\nimport matplotlib.pyplot as plt #To view the .dcm files\nimport pydicom #To read the .dcm (dicom) data\n\n#filename = \"/kaggle/input/rsna-str-pulmonary-embolism-detection/train/6897fa9de148/2bfbb7fd2e8b/894706f0aa3e.dcm\"\n#slide = pydicom.dcmread(filename)\n#plt.imshow(slide.pixel_array, cmap=plt.cm.bone)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The test data does not give us any text data about the slides, like we get in the train data, so there's not much point in creating an ANN without the images. Therefore we will create a simple CNN to start."},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\n\n#Set device to cuda/GPU as the GPU is considerably faster than the GPU at image tasks.\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data Loading\nI have included extra comments for the DataLoader portion taken from: https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel\n\nCredit to this notebook too for explaining what we are actually predicting. I found the competition overview uninsightful: https://www.kaggle.com/iammanpreet/pe-detection-using-pytorch"},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch.utils.data import DataLoader, Dataset    #Create an efficient dataloader set to feed images to the model\nfrom torch.utils.data.sampler import SequentialSampler\nfrom torchvision import transforms\n\nimport albumentations as A #Package of transformations\nfrom albumentations.pytorch.transforms import ToTensorV2\n\ndef get_train_transforms():\n    return A.Compose([\n        ToTensorV2(p=1.0) #Convert image and target to a tensor. Our CNN will only work with Tensors, not numpy arrays.\n    ])\n\ndef get_valid_transforms():\n    return A.Compose([\n            A.Resize(height=1024, width=1024, p=1.0),\n            ToTensorV2(p=1.0), #Convert image and target to a tensor. Our CNN will only work with Tensors, not numpy arrays.\n        ], p=1.0)\n\nclass TrainData(Dataset):\n\n    def __init__(self, dataframe, image_dir, transforms):\n        super().__init__()\n        \n        self.df = dataframe\n        self.image_ids = dataframe['image_path'].unique()\n        self.image_dir = image_dir\n        self.transforms = transforms\n\n    def __getitem__(self, idx: int):\n        #Generates one sample of the data\n        image_id = self.image_ids[idx]\n        image = pydicom.dcmread(f'{self.image_dir}{image_id}.dcm') #Read .dcm file into the loop\n        image = image.reshape((512,512,1)).astype('float')\n\n        labels =self.df[['negative_exam_for_pe', 'rv_lv_ratio_gte_1', 'rv_lv_ratio_lt_1',\n                     'leftsided_pe', 'chronic_pe', 'rightsided_pe',\n                     'acute_and_chronic_pe', 'central_pe', 'indeterminate']].loc[idx].values\n        \n        if self.transforms:\n            image = {\"image\" : image,} #Create a dictionary of the image values. We can only force the kwargs through transform if they are in dict type\n            image = self.transforms(**image)\n            \n        return image, labels #Return the transformed image, and the associated labels.\n    \n    def __len__(self) -> int:\n        #The loader needs to know how many items we have in our dataset. Therefore we use the __len__ function as an upper-bound.\n        return len(self.image_ids)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n#Split the training data into train and validate sets. We do this so we can assess the accuracy of our model.\ntrain_meta, valid_meta = train_test_split(train_csv,test_size=0.2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#print(train_meta.shape)\n#print(valid_meta.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = TrainData(train_meta, Image_path, transforms = get_train_transforms())\nvalid_dataset = TrainData(valid_meta, Image_path, transforms = get_valid_transforms())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nDataLoader takes in the arguements:\n\n    batch_size, which denotes the number of samples contained in each generated batch.\n    shuffle. If set to True, we will get a new order of exploration at each pass.\n        Shuffling the order in which examples are fed to the classifier is helpful so that batches between epochs do not look alike.\n        Doing so will eventually make our model more robust.\n    num_workers, which denotes the number of processes that generate batches in parallel.\n        A high enough number of workers assures that CPU computations are efficiently managed, i.e. that the bottleneck is indeed the neural network's forward and backward operations on the GPU (and not data generation).\n\n\"\"\"\n\ntrain_loader = DataLoader(train_dataset, batch_size = 32, shuffle = True, num_workers = 0)\nvalid_loader = DataLoader(valid_dataset, batch_size = 32, shuffle = True, num_workers = 0)\n\n\"\"\"\nWhat is an AxesImage:\nAn image attached to axes. Rather than just a normal image without. Often used in medical situations.\n\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch import nn\nfrom torch.nn import functional as F\nimport torchvision.models as models\nfrom torch import optim\nfrom torchvision import datasets, models\n\nmodel = models.resnet18(pretrained=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for param in model.parameters():\n    param.requires_grad = False\n    \nmodel.fc = nn.Sequential(nn.Linear(2048, 512),\n                                 nn.ReLU(),\n                                 nn.Dropout(0.2),\n                                 nn.Linear(512, 10), #Compact the 512 incoming nodes into 10 output nodes\n                                 nn.LogSoftmax(dim=1)) #Use softmax to classify the output into 1 of these 10 output nodes.\ncriterion = nn.NLLLoss()\noptimizer = optim.Adam(model.fc.parameters(), lr=0.003)\nmodel.to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Install and import dependencies\n#!conda install -c conda-forge gdcm -y\n#import gdcm","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"epochs = 1\nsteps = 0\nrunning_loss = 0\nprint_every = 10\ntrain_losses, test_losses = [], []\nfor epoch in range(epochs):\n    for image, label in train_loader:\n        steps += 1\n        image, label = image.to(device), label.to(device) #Send image and label to the device, cuda if enabled, otherwise gpu\n        optimizer.zero_grad()                             #zero out the gradient\n        logps = model.forward(image)                      #Do a forward pass of the model using the current batch of images and targets\n        loss = criterion(logps, label)                    #Calculate the loss\n        loss.backward()                                   #Backpropegate the loss\n        optimizer.step()                                  #Take a step in the direction of the optimum, calculated by the optimiser - in this case Adam.\n        running_loss += loss.item()\n        \n        if steps % print_every == 0:\n            test_loss = 0\n            accuracy = 0\n            model.eval()\n            with torch.no_grad():\n                for images, category in valid_loader:\n                    images, category = images.to(device), category.to(device)\n                    logps = model.forward(images)\n                    batch_loss = criterion(logps, category)\n                    test_loss += batch_loss.item()\n                    \n                    ps = torch.exp(logps)\n                    top_p, top_class = ps.topk(1, dim=1)\n                    equals = top_class == category.view(*top_class.shape)\n                    accuracy += torch.mean(equals.type(torch.FloatTensor)).item()\n            train_losses.append(running_loss/len(train_loader))\n            test_losses.append(test_loss/len(valid_loader))                    \n            print(f\"Epoch {epoch+1}/{epochs}.. \"\n                  f\"Train loss: {running_loss/print_every:.3f}.. \"\n                  f\"Test loss: {test_loss/len(valid_loader):.3f}.. \"\n                  f\"Test accuracy: {accuracy/len(valid_loader):.3f}\")\n            running_loss = 0\n            model.train()\ntorch.save(model, 'RSNAResNet50.pth')","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}