{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **Welcome to *Artificial Alligator's* notebook for the RSNA Lumbar Spine 2024 competition!**\n","metadata":{"execution":{"iopub.status.busy":"2024-06-13T09:49:09.976335Z","iopub.execute_input":"2024-06-13T09:49:09.976751Z","iopub.status.idle":"2024-06-13T09:49:10.001487Z","shell.execute_reply.started":"2024-06-13T09:49:09.976719Z","shell.execute_reply":"2024-06-13T09:49:10.000275Z"}}},{"cell_type":"markdown","source":"## Table of Contents\n...","metadata":{}},{"cell_type":"markdown","source":"## Useful links\n- [Visual guide to Convolutional Neural Networks](https://www.pinecone.io/learn/series/image-search/cnn/)\n\n- [Youtube video accompaniment to the above guide](https://www.youtube.com/watch?v=ZBfpkepdZlw)\n\n- [Statquest guide to Convolutional Neural Networks](https://www.youtube.com/watch?v=HGwBXDKFk9I)\n\n- [Dicom data preprocessing for Convolutional Neural Networks](https://www.kaggle.com/code/gzuidhof/full-preprocessing-tutorial#Normalization)","metadata":{}},{"cell_type":"markdown","source":"# Library imports and data loading\n\nThis section imports the necessary libraries for the following analysis and loads in all the data available to us","metadata":{}},{"cell_type":"code","source":"import pandas as pd                    # Standard\nimport numpy as np                     # Standard\nimport matplotlib.pyplot as plt        # Visualisation\nimport seaborn as sns                  # Visualisation\nimport os                              # Directory navigation\nimport pydicom                         # For working with DICOM medical image data\nimport shutil\nimport warnings\nimport random\nfrom PIL import Image\nimport torch\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader, random_split\nimport torch.nn as nn\nimport torch.optim as optim\n\nwarnings.filterwarnings(\"ignore\")\npath = \"../input/rsna-2024-lumbar-spine-degenerative-classification/\"              # Base path for the competition\ntrain                     = pd.read_csv(path + \"train.csv\")                        # Each patient's disease severity and vertebral disk location\ntrain_series_descriptions = pd.read_csv(path + \"train_series_descriptions.csv\")    # Labelled images' MRI orientation\ntrain_label_coordinates   = pd.read_csv(path + \"train_label_coordinates.csv\")      # (X,Y) co-ordinates of the centre of the identifying location of the disease on each image\ntest_series_descriptions  = pd.read_csv(path + \"test_series_descriptions.csv\")     # Testing images' MRI orientation\nsample_submission         = pd.read_csv(path + \"sample_submission.csv\")            # Sample submission","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:38.481087Z","iopub.execute_input":"2024-08-28T20:35:38.481481Z","iopub.status.idle":"2024-08-28T20:35:38.592843Z","shell.execute_reply.started":"2024-08-28T20:35:38.481450Z","shell.execute_reply":"2024-08-28T20:35:38.591987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train Data Shape & Missing Values\n\nprint(f\"Shape of train.csv: {train.shape}\")\nprint(f\"No. missing values in train.csv: {train.isna().sum().sum()}\")\nprint(\"Train head:\")\ndisplay(train.head())\n\nprint(f\"Shape of train_label_coordinates.csv: {train_label_coordinates.shape}\")\nprint(f\"No. missing values in train_label_coordinates.csv: {train_label_coordinates.isna().sum().sum()}\")\nprint(\"Train label coordinates head:\")\ndisplay(train_label_coordinates.head())\n\nprint(f\"Shape of train_series_descriptions.csv: {train_series_descriptions.shape}\")\nprint(f\"No. missing values in train_series_descriptions.csv: {train_series_descriptions.isna().sum().sum()}\")\nprint(\"Train series descriptions head:\")\ndisplay(train_series_descriptions.head())","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:38.594648Z","iopub.execute_input":"2024-08-28T20:35:38.595261Z","iopub.status.idle":"2024-08-28T20:35:38.651388Z","shell.execute_reply.started":"2024-08-28T20:35:38.595224Z","shell.execute_reply":"2024-08-28T20:35:38.650486Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Where does each disease occur the most?\n\ndef heatmapper(df,disease,ax_no_x,ax_no_y):\n    columns = [x for x in df.columns if disease in x]\n    df = train.loc[:,columns]\n    df.columns = [x[-5:].replace('_','/').title() for x in df.columns]\n    df = df.apply(lambda x: x.value_counts()).loc[['Severe','Moderate']]\n    ax = sns.heatmap(df,ax=axs[ax_no_x,ax_no_y])\n    ax.set_title(disease.replace('_',' ').title())\n    ax.set_xlabel(\"Vertebral Disk\",fontweight='bold')\n    ax.set_ylabel(\"Severity\",fontweight='bold')\n    return ax\n\nfig, axs = plt.subplots(2,3,figsize=(16,11))\nleft_neural = heatmapper(train,'left_neural_foraminal_narrowing',0,0)\nright_neural = heatmapper(train,'right_neural_foraminal_narrowing',0,1)\nleft_subarticular = heatmapper(train,'left_subarticular_stenosis',0,2)\nright_subarticular = heatmapper(train,'right_subarticular_stenosis',1,0)\nspinal_canal_stenosis = heatmapper(train,'spinal_canal_stenosis',1,1)\naxs[1,2].axis('off')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:38.652737Z","iopub.execute_input":"2024-08-28T20:35:38.653515Z","iopub.status.idle":"2024-08-28T20:35:40.599778Z","shell.execute_reply.started":"2024-08-28T20:35:38.653480Z","shell.execute_reply":"2024-08-28T20:35:40.598931Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Dataset join prep, joining, and cleaning\n\nmelted_train = pd.melt(train,id_vars='study_id',var_name='condition/location',value_name='severity')\ntrain_label_coordinates['condition/location'] = train_label_coordinates['condition'].str.lower().str.replace(' ','_')  \\\n                                                + '_' + train_label_coordinates['level'].str.lower().str.replace('/','_')\ntrain_label_coordinates = train_label_coordinates.drop(['condition','level'],axis=1)\ntrain_complete = train_label_coordinates.merge(melted_train,how='left',on=['study_id','condition/location'])\ntrain_complete = train_complete.merge(train_series_descriptions,how='left',on=['study_id','series_id'])\ntrain_complete['location'] = train_complete['condition/location'].str[-5:].str.replace('_','/').str.title()\ntrain_complete['condition'] = train_complete['condition/location'].str[:-6].str.replace('_',' ').str.title()\ntrain_complete = train_complete.drop('condition/location',axis=1)\ntrain_complete = train_complete.reindex(['study_id','series_id','instance_number','x','y','series_description','condition','location','severity'],axis=1)\ntrain_complete.columns = ['study_id','series_id','instance_number','x','y','mri_orientation','condition','location','severity']\ntrain_complete['file_path'] = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/\" + train_complete['study_id'].astype('str') + '/' + train_complete['series_id'].astype('str') + '/' + train_complete['instance_number'].astype('str') + '.dcm'\ntrain_complete['mri_orientation'] = train_complete['mri_orientation'].str.replace('/','_')","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:40.602549Z","iopub.execute_input":"2024-08-28T20:35:40.603166Z","iopub.status.idle":"2024-08-28T20:35:40.999151Z","shell.execute_reply.started":"2024-08-28T20:35:40.603130Z","shell.execute_reply":"2024-08-28T20:35:40.998175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Which disease occurs the most?\n\nfig, axs = plt.subplots(1,2,figsize=(25,6))\nsns.countplot(data=train_complete.loc[(train_complete['severity'] == 'Moderate') | (train_complete['severity'] == 'Severe')],x='condition',ax=axs[0])\nx_labels = [x.get_text()[:x.get_text().find(' ',x.get_text().find(' ')+1)] + x.get_text()[x.get_text().find(' ',x.get_text().find(' ')+1):].replace(' ','\\n',1) for x in axs[0].get_xticklabels()]\naxs[0].set_xticklabels(labels=x_labels)\naxs[0].bar_label(axs[0].containers[0])\naxs[0].set_xlabel('Condition',fontweight='bold',fontsize=12)\naxs[0].set_ylabel('# of cases',fontweight='bold',fontsize=12)\naxs[0].set_title('Disease Count',fontsize=15)\nsns.countplot(data=train_complete.loc[(train_complete['severity'] == 'Moderate') | (train_complete['severity'] == 'Severe')],x='condition',hue='severity',ax=axs[1])\naxs[1].legend(title='Severity')\naxs[1].set_xticklabels(labels=x_labels)\naxs[1].bar_label(axs[1].containers[0])\naxs[1].bar_label(axs[1].containers[1])\naxs[1].set_xlabel('Condition',fontweight='bold',fontsize=12)\naxs[1].set_ylabel('# of cases',fontweight='bold',fontsize=12)\naxs[1].set_title('Disease Count (Moderate or Severe)',fontsize=15)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:41.000321Z","iopub.execute_input":"2024-08-28T20:35:41.000622Z","iopub.status.idle":"2024-08-28T20:35:41.660206Z","shell.execute_reply.started":"2024-08-28T20:35:41.000596Z","shell.execute_reply":"2024-08-28T20:35:41.659291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Setting baseline probabilities (frequency table) and submission\n'''\ntrain_complete['condition_location'] = train_complete['condition'].str.lower().str.replace(' ','_') + '_' + train_complete['location'].str.lower().str.replace('/','_')\nfreq_table = pd.crosstab(train_complete['condition_location'],train_complete['severity'],normalize='index')\nfreq_table = freq_table.reset_index()\nfreq_table = freq_table.reindex(['condition_location','Normal/Mild','Moderate','Severe'],axis=1)\nfreq_table.columns = ['condition_location','normal_mild','moderate','severe']\n\nsubmission = pd.DataFrame()\n\nfor x in os.listdir(\"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_images\"):\n    running_df = freq_table.copy()\n    running_df['condition_location'] = str(x) + '_' + running_df['condition_location']\n    running_df.columns = ['row_id','normal_mild','moderate','severe']\n    submission = pd.concat([submission,running_df],axis=0)\n\ntrain_complete = train_complete.drop('condition_location',axis=1)\n'''","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:41.661546Z","iopub.execute_input":"2024-08-28T20:35:41.662184Z","iopub.status.idle":"2024-08-28T20:35:41.669710Z","shell.execute_reply.started":"2024-08-28T20:35:41.662146Z","shell.execute_reply":"2024-08-28T20:35:41.668803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train[train['study_id'] != 3008676218]\n\norientations = ['Axial T2','Sagittal T1','Sagittal T2_STIR']\nst_ids = train['study_id'].unique().tolist()\n\nroot = r'/kaggle/working/training_images'\nif not os.path.exists(root):\n    os.mkdir(root)\nelse:\n    print('Directory already created')\n    \nfor study in st_ids:\n    study_path = os.path.join(root,str(study))\n    if not os.path.exists(study_path):\n        os.mkdir(study_path)\n        \n    for orient in orientations:\n        orient_path = os.path.join(study_path,orient)\n        if not os.path.exists(orient_path):\n            os.mkdir(orient_path)\n        source_paths = train_complete.loc[(train_complete['study_id'] == study) & (train_complete['mri_orientation'] == orient),'file_path'].unique().tolist()\n        for source in source_paths:\n            dest_filename = source[source.find(str(study))+len(str(study))+1:].replace('/','_')\n            dest = os.path.join(orient_path,dest_filename)\n            shutil.copy2(source,dest)","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:35:41.671048Z","iopub.execute_input":"2024-08-28T20:35:41.671512Z","iopub.status.idle":"2024-08-28T20:37:26.132235Z","shell.execute_reply.started":"2024-08-28T20:35:41.671477Z","shell.execute_reply":"2024-08-28T20:37:26.131388Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def pilify(path):\n    pixel_array = pydicom.dcmread(path).pixel_array\n    \n    if pixel_array.max() > 255:\n        normy_pixels = (pixel_array / pixel_array.max()) * 255\n    else:\n        normy_pixels = pixel_array\n    \n    normy_pixels = normy_pixels.astype(np.uint8)\n    pil = Image.fromarray(normy_pixels)\n    \n    if pil.mode != 'L':\n        pil = pil.convert('L')\n    \n    return pil\n\nclass MRIDataset(Dataset):\n    def __init__(self,df,transform=None):\n        self.df = df\n        self.transform = transform\n    \n    def __len__(self):\n        return len(self.df)\n        \n    def __getitem__(self,idx):\n        row = self.df.iloc[idx]\n        study_id = row[0]\n        labels = torch.tensor(row[1:].values,dtype=torch.float32)\n        study_directory = f'/kaggle/working/training_images/{study_id}'\n        \n        images = []\n        \n        for root, _, files in os.walk(study_directory):\n            for file in files:\n                pil_image = pilify(os.path.join(root,file))\n                if self.transform:\n                    pil_image = self.transform(pil_image)\n                images.append(pil_image)\n        \n        sampled_images = random.sample(images,6)\n        images_tensor = torch.stack(sampled_images)\n        images_tensor = images_tensor.squeeze(1)\n        \n        return images_tensor, labels","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:37:26.133495Z","iopub.execute_input":"2024-08-28T20:37:26.133797Z","iopub.status.idle":"2024-08-28T20:37:26.145070Z","shell.execute_reply.started":"2024-08-28T20:37:26.133768Z","shell.execute_reply":"2024-08-28T20:37:26.144059Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train.fillna(-100)\ntrain = train.replace({'Normal/Mild':0,'Moderate':1,'Severe':2})\n\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(degrees=45),\n    transforms.RandomCrop(size=(200,200)),\n    transforms.RandomPerspective(distortion_scale=0.5,p=0.5),\n    transforms.GaussianBlur(kernel_size=5, sigma=(0.1, 2.0)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=0.16767935454845428,std=0.18816058337688446)\n])\n\ndataset = MRIDataset(train,transform)\n\ntrain_size = int(0.8 * len(dataset))\nvalidation_size = len(dataset) - train_size\ntrain_dataset, val_dataset = random_split(dataset,[train_size,validation_size])\n\ntrain_loader = DataLoader(train_dataset,batch_size=64,shuffle=True,num_workers=4)\nval_loader = DataLoader(val_dataset,batch_size=64,shuffle=False,num_workers=4)","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:37:26.146155Z","iopub.execute_input":"2024-08-28T20:37:26.146472Z","iopub.status.idle":"2024-08-28T20:37:26.206292Z","shell.execute_reply.started":"2024-08-28T20:37:26.146421Z","shell.execute_reply":"2024-08-28T20:37:26.205463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ConvNeuralNet(nn.Module):\n    def __init__(self):\n        super(ConvNeuralNet, self).__init__()\n        self.conv_layer1 = nn.Conv2d(in_channels=6,out_channels=64,kernel_size=3,stride=1,padding=1)\n        self.relu1 = nn.ReLU()\n        self.max_pool1 = nn.MaxPool2d(kernel_size=2,stride=2)\n        \n        self.conv_layer2 = nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,stride=1,padding=1)\n        self.relu2 = nn.ReLU()\n        self.max_pool2 = nn.MaxPool2d(kernel_size=2,stride=2)\n        \n        self.conv_layer3 = nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,stride=1,padding=1)\n        self.relu3 = nn.ReLU()\n        self.max_pool3 = nn.MaxPool2d(kernel_size=2,stride=2)\n        \n        self.conv_layer4 = nn.Conv2d(in_channels=256,out_channels=512,kernel_size=3,stride=1,padding=1)\n        self.relu4 = nn.ReLU()\n        self.max_pool4 = nn.MaxPool2d(kernel_size=2,stride=2)\n        \n        self.conv_layer5 = nn.Conv2d(in_channels=512,out_channels=1024,kernel_size=3,stride=1,padding=1)\n        self.relu5 = nn.ReLU()\n        self.max_pool5 = nn.MaxPool2d(kernel_size=2,stride=2)\n        \n        self.dropout1 = nn.Dropout(p=0.5)\n        self.fc1 = nn.Linear(1024*6*6,512)   # no. channels = 1024, height and width are 6 after convolutional layers\n        self.relu6 = nn.ReLU()\n        \n        self.dropout2 = nn.Dropout(p=0.5)\n        self.fc2 = nn.Linear(512,256)\n        self.relu7 = nn.ReLU()\n        \n        self.dropout3 = nn.Dropout(p=0.5)\n        self.fc3 = nn.Linear(256,128)\n        self.relu8 = nn.ReLU()\n        \n        self.fc4 = nn.Linear(128,75)         # output 75 = 25 predictions * 3 possible classes (normal/mild, moderate, severe)\n        \n    def forward(self, x):\n        out = self.conv_layer1(x)\n        out = self.relu1(out)\n        out = self.max_pool1(out)\n        \n        out = self.conv_layer2(out)\n        out = self.relu2(out)\n        out = self.max_pool2(out)\n        \n        out = self.conv_layer3(out)\n        out = self.relu3(out)\n        out = self.max_pool3(out)\n        \n        out = self.conv_layer4(out)\n        out = self.relu4(out)\n        out = self.max_pool4(out)\n        \n        out = self.conv_layer5(out)\n        out = self.relu5(out)\n        out = self.max_pool5(out)\n        \n        out = out.view(out.size(0),-1)\n        \n        out = self.dropout1(out)\n        out = self.fc1(out)\n        out = self.relu6(out)\n        \n        out = self.dropout2(out)\n        out = self.fc2(out)\n        out = self.relu7(out)\n        \n        out = self.dropout3(out)\n        out = self.fc3(out)\n        out = self.relu8(out)\n        \n        out = self.fc4(out)\n        \n        out = out.view(out.size(0), 25, -1)\n        \n        return out","metadata":{"execution":{"iopub.status.busy":"2024-08-28T20:37:26.209427Z","iopub.execute_input":"2024-08-28T20:37:26.209756Z","iopub.status.idle":"2024-08-28T20:37:26.229270Z","shell.execute_reply.started":"2024-08-28T20:37:26.209730Z","shell.execute_reply":"2024-08-28T20:37:26.228396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nmodel = ConvNeuralNet().to(device)\nloss_func = nn.CrossEntropyLoss()\noptimiser = optim.Adam(model.parameters(),lr=0.001)\n\nnum_epoch = 50\nfor epoch in range(num_epoch):\n    model.train()\n    \n    for images, labels in train_loader:\n        images = images.to(device)\n        labels = labels.to(device).long()\n        \n        outputs = model(images)\n        outputs = outputs.view(-1, outputs.size(2))\n        labels = labels.view(-1)\n\n        loss = loss_func(outputs,labels)\n        \n        optimiser.zero_grad()\n        loss.backward()\n        optimiser.step()\n        \n    with torch.no_grad():\n        model.eval()\n        \n        correct = 0\n        total = 0\n        all_val_loss = []\n        \n        for images, labels in val_loader:\n            images = images.to(device)\n            labels = labels.to(device).long()\n            \n            outputs = model(images)\n            outputs_reshaped = outputs.view(-1, outputs.size(2))\n            labels_reshaped = labels.view(-1)\n            \n            loss = loss_func(outputs_reshaped,labels_reshaped)\n            all_val_loss.append(loss.item())\n            \n            predicted = torch.argmax(outputs,dim=2)\n            correct += (predicted == labels).sum().item()\n            \n            total += labels.size(0) * labels.size(1)\n            \n        mean_val_loss = sum(all_val_loss) / len(all_val_loss)\n        mean_val_acc = (correct / total) * 100\n        \n    print(f\"Epoch [{epoch}/{num_epoch}], Last Batch Loss: {loss.item():.4f}, Mean Validation Loss: {mean_val_loss:.4f}, Mean Validation Accuracy: {mean_val_acc:.1f}%\")","metadata":{"execution":{"iopub.status.busy":"2024-08-28T22:10:52.199874Z","iopub.execute_input":"2024-08-28T22:10:52.200292Z","iopub.status.idle":"2024-08-28T23:22:54.115133Z","shell.execute_reply.started":"2024-08-28T22:10:52.200259Z","shell.execute_reply":"2024-08-28T23:22:54.113699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}