{"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":30747,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\n\n# Path to the CSV file\ncsv_path = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv\"\n\n# Load the CSV file into a DataFrame\ndf = pd.read_csv(csv_path)\n\n# Print each column name on a new line\nfor column in df.columns:\n    print(column)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-08-01T07:11:07.649855Z","iopub.execute_input":"2024-08-01T07:11:07.650241Z","iopub.status.idle":"2024-08-01T07:11:07.670596Z","shell.execute_reply.started":"2024-08-01T07:11:07.650211Z","shell.execute_reply":"2024-08-01T07:11:07.669623Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# List of columns to keep\ncolumns_to_keep = [\n    'study_id',\n    'spinal_canal_stenosis_l1_l2',\n    'spinal_canal_stenosis_l2_l3',\n    'spinal_canal_stenosis_l3_l4',\n    'spinal_canal_stenosis_l4_l5',\n    'spinal_canal_stenosis_l5_s1',\n]\n\n# Filter the DataFrame to keep only the specified columns\ndf = df[columns_to_keep]\ndf","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:11:09.452234Z","iopub.execute_input":"2024-08-01T07:11:09.452596Z","iopub.status.idle":"2024-08-01T07:11:09.469050Z","shell.execute_reply.started":"2024-08-01T07:11:09.452566Z","shell.execute_reply":"2024-08-01T07:11:09.468066Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.dropna()\nlen(df)","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:11:12.316898Z","iopub.execute_input":"2024-08-01T07:11:12.317548Z","iopub.status.idle":"2024-08-01T07:11:12.326617Z","shell.execute_reply.started":"2024-08-01T07:11:12.317518Z","shell.execute_reply":"2024-08-01T07:11:12.325710Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.preprocessing import OneHotEncoder\n\ntarget_columns = ['spinal_canal_stenosis_l1_l2', 'spinal_canal_stenosis_l2_l3', \n                   'spinal_canal_stenosis_l3_l4', 'spinal_canal_stenosis_l4_l5', \n                   'spinal_canal_stenosis_l5_s1']\n\n# Initialize the OneHotEncoder\nencoder = OneHotEncoder(sparse=False, handle_unknown='ignore', categories='auto')\n\n# List to store one-hot encoded DataFrames\none_hot_encoded_dfs = []\n\n# Apply OneHotEncoder to each target column\nfor col in target_columns:\n    # Fit and transform each column\n    one_hot_encoded = encoder.fit_transform(df[[col]].astype(str))\n    \n    # Convert to DataFrame with appropriate column names\n    one_hot_encoded_df = pd.DataFrame(one_hot_encoded, columns=encoder.get_feature_names_out([col]))\n    \n    # Append to the list\n    one_hot_encoded_dfs.append(one_hot_encoded_df)\n\n# Concatenate all one-hot encoded DataFrames\none_hot_encoded_df = pd.concat(one_hot_encoded_dfs, axis=1)\n\n# Drop the original target columns from the DataFrame\ndf = df.drop(columns=target_columns)\n\n# Concatenate the one-hot encoded columns with the original DataFrame\ndf = pd.concat([df, one_hot_encoded_df], axis=1)\n\nprint(df.head())","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:11:17.868374Z","iopub.execute_input":"2024-08-01T07:11:17.869054Z","iopub.status.idle":"2024-08-01T07:11:19.246640Z","shell.execute_reply.started":"2024-08-01T07:11:17.869021Z","shell.execute_reply":"2024-08-01T07:11:19.245300Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n\n# Path to the CSV file\ncsv_path_series = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_series_descriptions.csv\"\n\n# Load the CSV file into a DataFrame\ndf_series = pd.read_csv(csv_path_series)\n\n# Print the first few rows of the DataFrame\nprint(df_series.head())\n\n# Print the column names\nprint(df_series.columns.tolist())","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:10:08.921503Z","iopub.execute_input":"2024-08-01T07:10:08.921892Z","iopub.status.idle":"2024-08-01T07:10:08.935973Z","shell.execute_reply.started":"2024-08-01T07:10:08.921861Z","shell.execute_reply":"2024-08-01T07:10:08.935088Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport os\n\ndf_main = df\ndf_series = df_series\n\n# Define the base path for the images\nimages_base_path = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images\"\n\n# Create a dictionary to store the paths of middle images for Sagittal T1\nmiddle_image_paths = {}\n\n# Iterate through the series DataFrame to find Sagittal T1 series\nfor index, row in df_series.iterrows():\n    study_id = row['study_id']\n    series_description = row['series_description']\n    \n    if 'Sagittal T1' in series_description:\n        series_id = row['series_id']\n        series_path = os.path.join(images_base_path, str(study_id), str(series_id))\n        \n        if os.path.isdir(series_path):\n            images = [f for f in os.listdir(series_path) if f.endswith(\".dcm\")]\n            images.sort()  # Ensure the images are sorted correctly\n            \n            n_images = len(images)\n            if n_images > 0:\n                middle_index = n_images // 2\n                middle_image = images[middle_index]\n                middle_image_path = os.path.join(series_path, middle_image)\n                middle_image_paths[study_id] = middle_image_path\n\n# Add a new column to the main DataFrame with the middle image paths\ndf_main['Sagittal_T1_Middle_Image_Path'] = df_main['study_id'].map(middle_image_paths)\n\n# Print the first few rows of the updated DataFrame\nprint(df_main.head())\n\n# Optionally, save the updated DataFrame to a new CSV file\ndf_main.to_csv('/kaggle/working/updated_train_with_middle_images.csv', index=False)\ndf_main","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:11:24.081624Z","iopub.execute_input":"2024-08-01T07:11:24.082344Z","iopub.status.idle":"2024-08-01T07:11:27.560513Z","shell.execute_reply.started":"2024-08-01T07:11:24.082310Z","shell.execute_reply":"2024-08-01T07:11:27.559633Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(df_main))\ndf_main = df_main.dropna()\nprint(len(df_main))","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:11:58.553315Z","iopub.execute_input":"2024-08-01T07:11:58.554068Z","iopub.status.idle":"2024-08-01T07:11:58.562625Z","shell.execute_reply.started":"2024-08-01T07:11:58.554034Z","shell.execute_reply":"2024-08-01T07:11:58.561556Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#check if some patient might have more than one thingy\n#filtered_df = df_main[df_main[columns_to_keep[1:]].gt(0).sum(axis=1) >= 2]\n#filtered_df\n#they did around 300 ppl","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:14:54.722458Z","iopub.execute_input":"2024-08-01T07:14:54.723052Z","iopub.status.idle":"2024-08-01T07:14:54.726957Z","shell.execute_reply.started":"2024-08-01T07:14:54.723018Z","shell.execute_reply":"2024-08-01T07:14:54.726012Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Building the model","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport torch.nn as nn\nimport torchvision.models as models\nimport torch.optim as optim\nimport pydicom\nimport numpy as np\n\nclass StenosisDataset(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        img_path = self.df.iloc[idx]['Sagittal_T1_Middle_Image_Path']\n\n        # Read the DICOM image\n        dicom = pydicom.dcmread(img_path)\n        image = dicom.pixel_array\n\n        # Normalize the image to the range [0, 255] and convert to 8-bit\n        image = (image - np.min(image)) / (np.max(image) - np.min(image)) * 255\n        image = image.astype(np.uint8)\n\n        # Convert to RGB\n        if len(image.shape) == 2:  # if grayscale\n            image = np.stack([image] * 3, axis=-1)  # Convert grayscale to RGB\n\n        image = Image.fromarray(image).convert(\"RGB\")\n\n        # Apply transformations\n        if self.transform:\n            image = self.transform(image)\n\n        # Get targets\n        targets = self.df.iloc[idx][[\n            'spinal_canal_stenosis_l1_l2_Moderate',\n       'spinal_canal_stenosis_l1_l2_Normal/Mild',\n       'spinal_canal_stenosis_l1_l2_Severe',\n       'spinal_canal_stenosis_l2_l3_Moderate',\n       'spinal_canal_stenosis_l2_l3_Normal/Mild',\n       'spinal_canal_stenosis_l2_l3_Severe',\n       'spinal_canal_stenosis_l3_l4_Moderate',\n       'spinal_canal_stenosis_l3_l4_Normal/Mild',\n       'spinal_canal_stenosis_l3_l4_Severe',\n       'spinal_canal_stenosis_l4_l5_Moderate',\n       'spinal_canal_stenosis_l4_l5_Normal/Mild',\n       'spinal_canal_stenosis_l4_l5_Severe',\n       'spinal_canal_stenosis_l5_s1_Moderate',\n       'spinal_canal_stenosis_l5_s1_Normal/Mild',\n       'spinal_canal_stenosis_l5_s1_Severe'\n        ]].values.astype(float)\n\n        return image, torch.tensor(targets, dtype=torch.float)\n\n\n# Define transformations\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n])\n\n# Load the dataset\ndataset = StenosisDataset(df_main, transform=transform)\ndata_loader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4)","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:25:21.021006Z","iopub.execute_input":"2024-08-01T07:25:21.021368Z","iopub.status.idle":"2024-08-01T07:25:21.034738Z","shell.execute_reply.started":"2024-08-01T07:25:21.021339Z","shell.execute_reply":"2024-08-01T07:25:21.033717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\n\nclass ResNetWithSoftmax(nn.Module):\n    def __init__(self, num_classes_per_output, num_outputs):\n        super(ResNetWithSoftmax, self).__init__()\n        self.resnet = models.resnet18(pretrained=True)\n        self.resnet.fc = nn.Linear(self.resnet.fc.in_features, num_outputs * num_classes_per_output)\n        self.num_classes_per_output = num_classes_per_output\n        self.num_outputs = num_outputs\n\n    def forward(self, x):\n        x = self.resnet(x)\n        x = x.view(-1, self.num_outputs, self.num_classes_per_output)  # Reshape for multi-class outputs\n        x = torch.nn.functional.softmax(x, dim=2)  # Apply softmax to each output\n        return x\n","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:25:22.399079Z","iopub.execute_input":"2024-08-01T07:25:22.399449Z","iopub.status.idle":"2024-08-01T07:25:22.406876Z","shell.execute_reply.started":"2024-08-01T07:25:22.399416Z","shell.execute_reply":"2024-08-01T07:25:22.405949Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tqdm import tqdm\n\ndef train_model(model, dataloader, criterion, optimizer, num_epochs=10):\n    model.train()\n    \n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        \n        # Initialize tqdm for the dataloader\n        with tqdm(total=len(dataloader), desc=f'Epoch {epoch+1}/{num_epochs}', unit='batch') as pbar:\n            for inputs, targets in dataloader:\n                # Move data to the appropriate device\n                inputs = inputs.to(device)\n                targets = targets.to(device)\n                \n                optimizer.zero_grad()\n                outputs = model(inputs)\n                \n                # Calculate loss\n                losses = []\n                for i in range(outputs.size(1)):  # Iterate over the outputs\n                    loss = criterion(outputs[:, i, :], targets[:, i].long())\n                    losses.append(loss)\n                    \n                total_loss = sum(losses) / len(losses)  # Average loss\n                total_loss.backward()\n                optimizer.step()\n                \n                running_loss += total_loss.item() * inputs.size(0)\n                \n                # Update tqdm progress bar\n                pbar.set_postfix({'loss': running_loss / ((pbar.n + 1) * dataloader.batch_size)})\n                pbar.update(1)\n        \n        epoch_loss = running_loss / len(dataloader.dataset)\n        print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')\n        \n    print('Training complete')\n","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:25:26.580226Z","iopub.execute_input":"2024-08-01T07:25:26.581313Z","iopub.status.idle":"2024-08-01T07:25:26.591124Z","shell.execute_reply.started":"2024-08-01T07:25:26.581271Z","shell.execute_reply":"2024-08-01T07:25:26.590152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Parameters\nnum_classes_per_output = 3  # Number of classes for each output\nnum_outputs = 5  # Number of outputs\nbatch_size = 16\nlearning_rate = 0.001\nnum_epochs = 10\n\n# Initialize model, criterion, and optimizer\nmodel = ResNetWithSoftmax(num_classes_per_output=num_classes_per_output, num_outputs=num_outputs)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel.to(device)\n\ncriterion = nn.CrossEntropyLoss()  # Suitable for multi-class classification\noptimizer = optim.Adam(model.parameters(), lr=learning_rate)\n\n# Assuming data_loader is already defined and loaded with the StenosisDataset\ntrain_model(model, data_loader, criterion, optimizer, num_epochs=num_epochs)","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:25:29.456583Z","iopub.execute_input":"2024-08-01T07:25:29.457283Z","iopub.status.idle":"2024-08-01T07:28:42.312467Z","shell.execute_reply.started":"2024-08-01T07:25:29.457248Z","shell.execute_reply":"2024-08-01T07:28:42.311371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nimport torch.nn.functional as F\nfrom sklearn.metrics import confusion_matrix\nimport numpy as np\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Define your DataLoader for the test set\ntest_loader = data_loader\n\n# Function to collect predictions and ground truths\ndef collect_predictions_and_targets(model, dataloader):\n    model.eval()\n    all_preds = []\n    all_targets = []\n    \n    with torch.no_grad():\n        for inputs, targets in dataloader:\n            inputs = inputs.to(device)\n            targets = targets.to(device)\n            \n            outputs = model(inputs)\n            \n            preds = outputs.argmax(dim=2)  # Get the predicted class for each output\n            all_preds.append(preds.cpu().numpy())\n            all_targets.append(targets.cpu().numpy())\n    \n    all_preds = np.concatenate(all_preds, axis=0)\n    all_targets = np.concatenate(all_targets, axis=0)\n    \n    return all_preds, all_targets\n\n# Collect predictions and targets\npreds, targets = collect_predictions_and_targets(model, test_loader)\n\n# Calculate confusion matrices for each output\nconfusion_matrices = []\nfor i in range(preds.shape[1]):\n    cm = confusion_matrix(targets[:, i], preds[:, i], labels=[0, 1, 2])\n    confusion_matrices.append(cm)\n\n# Plot confusion matrices\nfor i, cm in enumerate(confusion_matrices):\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Blues\", xticklabels=[\"Normal/Mild\", \"Moderate\", \"Severe\"], yticklabels=[\"Normal/Mild\", \"Moderate\", \"Severe\"])\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"Actual\")\n    plt.title(f\"Confusion Matrix for Output {i+1}\")\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-08-01T07:43:45.293237Z","iopub.execute_input":"2024-08-01T07:43:45.294185Z","iopub.status.idle":"2024-08-01T07:44:05.368796Z","shell.execute_reply.started":"2024-08-01T07:43:45.294149Z","shell.execute_reply":"2024-08-01T07:44:05.367733Z"},"trusted":true},"execution_count":null,"outputs":[]}]}