{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport os\nimport math\nimport torch\nimport torch.nn as nn\nfrom tqdm import tqdm\nimport torchvision.transforms.v2 as v2\nimport pydicom as dicom\nimport matplotlib.pylab as plt\n\nfrom torchvision.models import resnet18\nfrom torch.utils.data import Dataset, DataLoader, random_split","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:52.823557Z","iopub.execute_input":"2025-05-21T16:48:52.823841Z","iopub.status.idle":"2025-05-21T16:48:52.828317Z","shell.execute_reply.started":"2025-05-21T16:48:52.823823Z","shell.execute_reply":"2025-05-21T16:48:52.827610Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# specify your image path\nimage_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/100206310/1012284084/10.dcm'\nds = dicom.dcmread(image_path)\n\nplt.imshow(ds.pixel_array, cmap='gray')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:53.131466Z","iopub.execute_input":"2025-05-21T16:48:53.131670Z","iopub.status.idle":"2025-05-21T16:48:53.454592Z","shell.execute_reply.started":"2025-05-21T16:48:53.131655Z","shell.execute_reply":"2025-05-21T16:48:53.453756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# specify your image path\n# image_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/100206310/1012284084/8.dcm'\n# image_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/100206310/1792451510/8.dcm'\n# image_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/100206310/2092806862/8.dcm'\nimage_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/100206310/1792451510/1.dcm'\nds = dicom.dcmread(image_path)\n\nplt.imshow(ds.pixel_array, cmap='gray')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:53.455984Z","iopub.execute_input":"2025-05-21T16:48:53.456272Z","iopub.status.idle":"2025-05-21T16:48:53.655112Z","shell.execute_reply.started":"2025-05-21T16:48:53.456249Z","shell.execute_reply":"2025-05-21T16:48:53.654362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1 = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:56.978892Z","iopub.execute_input":"2025-05-21T16:48:56.979463Z","iopub.status.idle":"2025-05-21T16:48:57.006082Z","shell.execute_reply.started":"2025-05-21T16:48:56.979441Z","shell.execute_reply":"2025-05-21T16:48:57.005588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df1.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:57.243628Z","iopub.execute_input":"2025-05-21T16:48:57.243819Z","iopub.status.idle":"2025-05-21T16:48:57.274209Z","shell.execute_reply.started":"2025-05-21T16:48:57.243805Z","shell.execute_reply":"2025-05-21T16:48:57.273558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df2 = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_label_coordinates.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:57.583009Z","iopub.execute_input":"2025-05-21T16:48:57.583214Z","iopub.status.idle":"2025-05-21T16:48:57.686944Z","shell.execute_reply.started":"2025-05-21T16:48:57.583199Z","shell.execute_reply":"2025-05-21T16:48:57.686420Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df2.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:48:58.925882Z","iopub.execute_input":"2025-05-21T16:48:58.926137Z","iopub.status.idle":"2025-05-21T16:48:58.936959Z","shell.execute_reply.started":"2025-05-21T16:48:58.926117Z","shell.execute_reply":"2025-05-21T16:48:58.936217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"patients = {}\n\nstudy_ids = df1['study_id'].unique()\nfor study_id in study_ids:\n    patients[study_id] = {}\n    for row_id in range(len(df2[df2['study_id']==study_id]['condition'])):\n        condition = df2[df2['study_id']==study_id]['condition'].iloc[row_id].lower().replace(' ', '_')\n        level = df2[df2['study_id']==study_id]['level'].iloc[row_id].lower().replace('/','_')\n        col_id = condition + \"_\" + level\n        status = df1[df1['study_id']==study_id][col_id].values[0]\n\n        if type(status) is not str and math.isnan(status):\n            continue\n        \n        if status.lower() == 'Normal/Mild'.lower():\n            label = 0\n        elif status.lower() == 'Moderate'.lower():\n            label = 1\n        if status.lower() == 'Severe'.lower():\n            label = 2\n\n        serie_id = df2[df2['study_id']==study_id]['series_id'].iloc[row_id]\n        instance_number = df2[df2['study_id']==study_id]['instance_number'].iloc[row_id]\n\n        if serie_id in patients[study_id].keys():\n            if instance_number in patients[study_id][serie_id].keys():\n                if patients[study_id][serie_id][instance_number] == 0 and label != 0:\n                    patients[study_id][serie_id][instance_number] = label\n                elif patients[study_id][serie_id][instance_number] == 1 and label == 2:\n                    patients[study_id][serie_id][instance_number] = label\n            else:\n                patients[study_id][serie_id][instance_number] = label\n        else:\n            patients[study_id][serie_id] = {}\n            patients[study_id][serie_id][instance_number] = label\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T15:49:41.463788Z","iopub.execute_input":"2025-05-21T15:49:41.464383Z","iopub.status.idle":"2025-05-21T15:49:48.031078Z","shell.execute_reply.started":"2025-05-21T15:49:41.464358Z","shell.execute_reply":"2025-05-21T15:49:48.030031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"root_path = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/\"\nfor patient_id in patients.keys():\n    for serie_id in patients[patient_id].keys():\n        for img_id in patients[patient_id][serie_id].keys():\n            print(\"img_path is: \", os.path.join(root_path, str(patient_id), str(serie_id), str(img_id) + \".dcm\"), \"Label is: \", patients[patient_id][serie_id][img_id])\n    break","metadata":{"trusted":true,"execution":{"execution_failed":"2025-05-18T17:03:42.994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class lumbar_dataset(Dataset):\n    def __init__(self, root_path, transform):\n        patients = {}\n        study_ids = df1['study_id'].unique()\n        for study_id in study_ids:\n            patients[study_id] = {}\n            for row_id in range(len(df2[df2['study_id']==study_id]['condition'])):\n                condition = df2[df2['study_id']==study_id]['condition'].iloc[row_id].lower().replace(' ', '_')\n                level = df2[df2['study_id']==study_id]['level'].iloc[row_id].lower().replace('/','_')\n                col_id = condition + \"_\" + level\n                status = df1[df1['study_id']==study_id][col_id].values[0]\n        \n                if type(status) is not str and math.isnan(status):\n                    continue\n                \n                if status.lower() == 'Normal/Mild'.lower():\n                    label = 0\n                elif status.lower() == 'Moderate'.lower():\n                    label = 1\n                if status.lower() == 'Severe'.lower():\n                    label = 2\n        \n                serie_id = df2[df2['study_id']==study_id]['series_id'].iloc[row_id]\n                instance_number = df2[df2['study_id']==study_id]['instance_number'].iloc[row_id]\n        \n                if serie_id in patients[study_id].keys():\n                    if instance_number in patients[study_id][serie_id].keys():\n                        if patients[study_id][serie_id][instance_number] == 0 and label != 0:\n                            patients[study_id][serie_id][instance_number] = label\n                        elif patients[study_id][serie_id][instance_number] == 1 and label == 2:\n                            patients[study_id][serie_id][instance_number] = label\n                    else:\n                        patients[study_id][serie_id][instance_number] = label\n                else:\n                    patients[study_id][serie_id] = {}\n                    patients[study_id][serie_id][instance_number] = label\n        \n        \n        self.root_path = root_path\n        self.imgs_path = []\n        self.labels = []\n        self.transform = transform\n        \n        for patient_id in patients.keys():\n            for serie_id in patients[patient_id].keys():\n                for img_id in patients[patient_id][serie_id].keys():\n                    self.imgs_path.append(os.path.join(root_path, str(patient_id), str(serie_id), str(img_id) + \".dcm\"))\n                    self.labels.append(patients[patient_id][serie_id][img_id])\n\n    def __len__(self):\n        return len(self.imgs_path)\n\n    def __getitem__(self, idx):\n        img_path = self.imgs_path[idx]\n        label = self.labels[idx]\n        dcm = dicom.dcmread(img_path)\n        img = dcm.pixel_array\n        # img.expand_dims(axis=2)\n        img = self.transform(np.array(img).astype(np.float32))\n\n        return img, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:49:02.562170Z","iopub.execute_input":"2025-05-21T16:49:02.562530Z","iopub.status.idle":"2025-05-21T16:49:02.574429Z","shell.execute_reply.started":"2025-05-21T16:49:02.562495Z","shell.execute_reply":"2025-05-21T16:49:02.573573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_path = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/\"\n\ntransform = v2.Compose([\n    v2.ToPILImage(),\n    v2.Resize(230),\n    v2.CenterCrop(224),\n    v2.RandomPerspective(distortion_scale=0.05, p=0.3),\n    v2.RandomChoice([\n        v2.GaussianBlur(kernel_size=(5, 9), sigma=(0.05, 1.)),\n        v2.RandomRotation(degrees=(-10, 10)),\n    ], p=[0.3, 0.3]),\n    v2.ToTensor(),\n    v2.Normalize(mean=[0.5, 0.5, 0.5], std=[0.2, 0.2, 0.2])\n])\n\nfull_dataset = lumbar_dataset(train_path, transform)\n\n# Define split sizes\ntrain_size = int(0.8 * len(full_dataset))\nval_size = len(full_dataset) - train_size\ntrain_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:49:05.873970Z","iopub.execute_input":"2025-05-21T16:49:05.874231Z","iopub.status.idle":"2025-05-21T16:50:17.803658Z","shell.execute_reply.started":"2025-05-21T16:49:05.874214Z","shell.execute_reply":"2025-05-21T16:50:17.802850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, pin_memory=True)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:50:17.804843Z","iopub.execute_input":"2025-05-21T16:50:17.805120Z","iopub.status.idle":"2025-05-21T16:50:17.809053Z","shell.execute_reply.started":"2025-05-21T16:50:17.805103Z","shell.execute_reply":"2025-05-21T16:50:17.808362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score\n\ndef train(loader, model, criterion, optimizer, device):\n    model.train()\n    loss_per_epoch = []\n    acc_per_epoch = []\n    for data, label in tqdm(loader):\n        optimizer.zero_grad()\n\n        data = data.to(device)\n        label = label.to(device)\n\n        pred = model(data)\n        loss = criterion(pred, label)\n        loss.backward()\n        optimizer.step()\n\n        acc = accuracy_score(label.detach().cpu(), pred.argmax(dim=1).detach().cpu())\n        acc_per_epoch.append(acc)\n        loss_per_epoch.append(loss.item())\n\n    return torch.mean(torch.tensor(loss_per_epoch)), torch.mean(torch.tensor(acc_per_epoch))\n\ndef validation(loader, model, criterion, device):\n    model.eval()\n    loss_per_epoch = []\n    acc_per_epoch = []\n    with torch.no_grad():\n        for data, label in tqdm(loader):\n\n            data = data.to(device)\n            label = label.to(device)\n\n            pred = model(data)\n            loss = criterion(pred, label)\n            acc = accuracy_score(label.detach().cpu(), pred.argmax(dim=1).detach().cpu())\n            acc_per_epoch.append(acc)\n            loss_per_epoch.append(loss.item())\n\n    return torch.mean(torch.tensor(loss_per_epoch)), torch.mean(torch.tensor(acc_per_epoch))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:50:17.809858Z","iopub.execute_input":"2025-05-21T16:50:17.810092Z","iopub.status.idle":"2025-05-21T16:50:19.158901Z","shell.execute_reply.started":"2025-05-21T16:50:17.810072Z","shell.execute_reply":"2025-05-21T16:50:19.158334Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"####################################################################\n######## This cell is for multiple node (server) execution #########\n####################################################################\n# from torch.utils.data.distributed import DistributedSampler\n# from torch.nn.parallel import DistributedDataParallel as DDP\n# from torch.distributed import init_process_group, destroy_process_group\n\n# def ddp_setup(rank: int, world_size: int):\n#    \"\"\"\n#    Args:\n#        rank: Unique identifier of each process\n#       world_size: Total number of processes\n#    \"\"\"\n#    os.environ[\"MASTER_ADDR\"] = \"localhost\"\n#    os.environ[\"MASTER_PORT\"] = \"12355\"\n#    torch.cuda.set_device(rank)\n#    init_process_group(backend=\"nccl\", rank=rank, world_size=world_size)\n\n# model = DDP(model, device_ids=[\"0\", \"1\"])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\"\n# device = \"cpu\"\n\n# model = resnet18(weights=\"IMAGENET1K_V1\")\nmodel = resnet18(weights=None)\nmodel.fc = nn.Linear(in_features=model.fc.in_features, out_features=3)\n# model.requires_grad = False\n# model.fc.requires_grad = True\n\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\ncriterion = torch.nn.CrossEntropyLoss()\nmodel = model.to(device)\ncriterion = criterion.to(device)\n\nmodel = nn.DataParallel(model)\n\ntrain_loss_per_epoch = []\ntrain_acc_per_epoch = []\nval_loss_per_epoch = []\nval_acc_per_epoch = []\nfor epoch in range(50):\n    train_loss, train_acc = train(train_loader, model, criterion, optimizer, device)\n    val_loss, val_acc = validation(val_loader, model, criterion, device)\n    train_loss_per_epoch.append(train_loss)\n    train_acc_per_epoch.append(train_acc)\n    val_loss_per_epoch.append(val_loss)\n    val_acc_per_epoch.append(val_acc)\n    print(f\"Epoch {epoch+1}/10: Train loss: {train_loss}, Train acc: {train_acc}, Val loss: {val_loss}, Val acc: {val_acc}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T17:02:32.855304Z","iopub.execute_input":"2025-05-21T17:02:32.855623Z","iopub.status.idle":"2025-05-21T17:09:11.922180Z","shell.execute_reply.started":"2025-05-21T17:02:32.855601Z","shell.execute_reply":"2025-05-21T17:09:11.921180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model, \"./model_epoch_50.pt\")\nmodel = torch.load(\"./model_epoch_50.pt\")\n##################################################################\n##################################################################\ntorch.save(model.state_dict(), \"./model_epoch_50.pt\") # Recomended!\nmodel.load_state_dict(torch.load(\"./model_epoch_50.pt\"))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Fine-Tuning","metadata":{}},{"cell_type":"code","source":"model = resnet18(weights=\"IMAGENET1K_V1\")\nmodel.fc = nn.Linear(in_features=model.fc.in_features, out_features=3)\n\n# model.load_state_dict(torch.load(\"./model_epoch_50.pt\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:42:41.986168Z","iopub.execute_input":"2025-05-21T16:42:41.986558Z","iopub.status.idle":"2025-05-21T16:42:43.450404Z","shell.execute_reply.started":"2025-05-21T16:42:41.986539Z","shell.execute_reply":"2025-05-21T16:42:43.449615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.layer1.required_grad = False\nmodel.layer2.required_grad = False\nmodel.layer3.required_grad = True\nmodel.layer4.required_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:42:43.451653Z","iopub.execute_input":"2025-05-21T16:42:43.451954Z","iopub.status.idle":"2025-05-21T16:42:43.457467Z","shell.execute_reply.started":"2025-05-21T16:42:43.451936Z","shell.execute_reply":"2025-05-21T16:42:43.456768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i, layer in enumerate(model.children()):\n    print(layer)\n    layer.required_grad = False\n    if i > 10:\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T16:46:03.240497Z","iopub.execute_input":"2025-05-21T16:46:03.241261Z","iopub.status.idle":"2025-05-21T16:46:03.245809Z","shell.execute_reply.started":"2025-05-21T16:46:03.241235Z","shell.execute_reply":"2025-05-21T16:46:03.245095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.requires_grad = True\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-5)\ncriterion = torch.nn.CrossEntropyLoss()\nmodel = model.to(device)\ncriterion = criterion.to(device)\n\nmodel = nn.DataParallel(model)\n\ntrain_loss_per_epoch = []\ntrain_acc_per_epoch = []\nval_loss_per_epoch = []\nval_acc_per_epoch = []\nfor epoch in range(50):\n    train_loss, train_acc = train(train_loader, model, criterion, optimizer, device)\n    val_loss, val_acc = validation(val_loader, model, criterion, device)\n    train_loss_per_epoch.append(train_loss)\n    train_acc_per_epoch.append(train_acc)\n    val_loss_per_epoch.append(val_loss)\n    val_acc_per_epoch.append(val_acc)\n    print(f\"Epoch {epoch+1}/10: Train loss: {train_loss}, Train acc: {train_acc}, Val loss: {val_loss}, Val acc: {val_acc}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}