{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":133357,"sourceType":"modelInstanceVersion","modelInstanceId":112741,"modelId":136067}],"dockerImageVersionId":30786,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport csv\nfrom collections import defaultdict\nfrom tqdm import tqdm\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\n\nimport cv2\nimport pydicom\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torchvision import transforms\nimport torchvision.models as models\nimport timm\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Vertebrae names for display\nvertebra_names = ['L1/L2', 'L2/L3', 'L3/L4', 'L4/L5', 'L5/S1']","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-10-10T16:49:51.643978Z","iopub.execute_input":"2024-10-10T16:49:51.644473Z","iopub.status.idle":"2024-10-10T16:49:51.651401Z","shell.execute_reply.started":"2024-10-10T16:49:51.644433Z","shell.execute_reply":"2024-10-10T16:49:51.650323Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"# Spinal Canal","metadata":{}},{"cell_type":"markdown","source":"## Slice detection","metadata":{}},{"cell_type":"code","source":"class SliceIdentificationForSpinalCanal(nn.Module):\n    def __init__(self, num_classes=5):\n        super(SliceIdentificationForSpinalCanal, self).__init__()\n        self.model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=0)\n        num_features = self.model.num_features\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(num_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n    \n    def forward(self, x):\n        features = self.model(x)\n        return self.classifier(features)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.653574Z","iopub.execute_input":"2024-10-10T16:49:51.654118Z","iopub.status.idle":"2024-10-10T16:49:51.661651Z","shell.execute_reply.started":"2024-10-10T16:49:51.654070Z","shell.execute_reply":"2024-10-10T16:49:51.660613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Severity detection","metadata":{}},{"cell_type":"code","source":"class SeverityClassificationForSpinalCanal(nn.Module):\n    def __init__(self, num_classes=3, num_outputs=5):\n        super().__init__()\n        \n        # Use EfficientNet as the backbone\n        self.backbone = timm.create_model('efficientnet_b0', pretrained=True, in_chans=3, features_only=True)\n        self.backbone_channels = self.backbone.feature_info.channels()\n        \n        # Coordinate feature generator\n        self.coord_feature_gen = nn.Conv2d(10, 32, kernel_size=1)\n        \n        # Attention blocks for each level of the backbone\n        self.attention_blocks = nn.ModuleList([\n            SpinalCanalAttentionBlock(ch) for ch in self.backbone_channels\n        ])\n        \n        # Global average pooling\n        self.gap = nn.AdaptiveAvgPool2d(1)\n        \n        # Calculate the total number of features\n        total_features = sum(self.backbone_channels) + 32\n        \n        # Feature fusion\n        self.feature_fusion = nn.Sequential(\n            nn.Linear(total_features, 1280),\n            nn.ReLU(),\n            nn.Dropout(0.5)\n        )\n        \n        # Multi-output classifier\n        self.classifier = SpinalCanalMultiOutputClassifier(input_size=1280, num_classes=num_classes, num_outputs=num_outputs)\n\n    def forward(self, x, coordinates=None):\n       \n        # Generate coordinate features if provided\n        if coordinates is not None:\n            coord_features = self.create_coordinate_features(coordinates, x.shape[2], x.shape[3])\n          \n            coord_features = self.coord_feature_gen(coord_features)\n          \n        else:\n            coord_features = torch.zeros(x.size(0), 32, x.size(2), x.size(3), device=x.device)\n        \n        # Pass through backbone\n        features = self.backbone(x)\n \n        # Apply attention to each feature level\n        attended_features = [attention(feature) for feature, attention in zip(features, self.attention_blocks)]\n     \n        # Global average pooling\n        pooled_features = [self.gap(feature) for feature in attended_features]\n   \n        \n        # Flatten and concatenate features\n        flattened_features = [feature.view(x.size(0), -1) for feature in pooled_features]\n        flattened_coord_features = self.gap(coord_features).view(x.size(0), -1)\n        concat_features = torch.cat(flattened_features + [flattened_coord_features], dim=1)\n       \n        \n        # Feature fusion\n        fused_features = self.feature_fusion(concat_features)\n  \n        \n        # Multi-output classification\n        outputs = self.classifier(fused_features)\n      \n        \n        return outputs\n\n    def create_coordinate_features(self, coordinates, height, width):\n        batch_size = coordinates.shape[0]\n        coord_features = torch.zeros(batch_size, 10, height, width, device=coordinates.device)\n        for i in range(5):  # 5 disc levels\n            coord_features[:, 2*i] = self.gaussian_heatmap(coordinates[:, i, 0], height, width)\n            coord_features[:, 2*i+1] = self.gaussian_heatmap(coordinates[:, i, 1], height, width)\n        return coord_features\n\n    def gaussian_heatmap(self, coordinate, height, width, sigma=10):\n        x = torch.arange(width).repeat(height, 1).to(coordinate.device)\n        y = torch.arange(height).unsqueeze(1).repeat(1, width).to(coordinate.device)\n        coordinate = coordinate.unsqueeze(1).unsqueeze(2)\n        return torch.exp(-((x - coordinate)**2 + (y - coordinate)**2) / (2 * sigma**2))","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.662922Z","iopub.execute_input":"2024-10-10T16:49:51.663245Z","iopub.status.idle":"2024-10-10T16:49:51.681243Z","shell.execute_reply.started":"2024-10-10T16:49:51.663210Z","shell.execute_reply":"2024-10-10T16:49:51.680233Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SpinalCanalAttentionBlock(nn.Module):\n    def __init__(self, in_channels):\n        super().__init__()\n        self.attention = nn.Sequential(\n            nn.Conv2d(in_channels, in_channels // 8, kernel_size=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(in_channels // 8, in_channels, kernel_size=1),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return x * self.attention(x)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.683852Z","iopub.execute_input":"2024-10-10T16:49:51.684458Z","iopub.status.idle":"2024-10-10T16:49:51.692861Z","shell.execute_reply.started":"2024-10-10T16:49:51.684407Z","shell.execute_reply":"2024-10-10T16:49:51.692038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SpinalCanalMultiOutputClassifier(nn.Module):\n    def __init__(self, input_size, num_classes, num_outputs):\n        super().__init__()\n        self.shared_layers = nn.Sequential(\n            nn.Linear(input_size, 512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.5)\n        )\n        self.output_layers = nn.ModuleList([\n            nn.Linear(256, num_classes) for _ in range(num_outputs)\n        ])\n\n    def forward(self, x):\n        shared_features = self.shared_layers(x)\n        return [output_layer(shared_features) for output_layer in self.output_layers]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.694241Z","iopub.execute_input":"2024-10-10T16:49:51.694941Z","iopub.status.idle":"2024-10-10T16:49:51.704255Z","shell.execute_reply.started":"2024-10-10T16:49:51.694892Z","shell.execute_reply":"2024-10-10T16:49:51.703464Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import torch\n# from albumentations.pytorch import ToTensorV2\n# import os\n# from PIL import Image\n# import numpy as np\n# import matplotlib.pyplot as plt\n# from torchvision import transforms\n# from tqdm import tqdm\n# import pydicom\n# import torchvision.models as models\n# import cv2\n# import matplotlib.patches as patches\n# import torch\n# import os\n# from PIL import Image\n# import numpy as np\n# import matplotlib.pyplot as plt\n# from torchvision import transforms\n# from tqdm import tqdm\n# import pydicom\n# import albumentations as A\n# import cv2\n# import matplotlib.patches as patches\n# from collections import defaultdict\n# import os\n# import csv\n# import torch\n# import pydicom\n# import numpy as np\n# import pandas as pd\n# from collections import defaultdict\n# from tqdm import tqdm\n# import cv2\n# from PIL import Image\n# import torchvision.transforms as transforms\n# import torch.nn.functional as F\n# import timm\n# # Vertebrae names for display\n# vertebra_names = ['L1/L2', 'L2/L3', 'L3/L4', 'L4/L5', 'L5/S1']","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.707216Z","iopub.execute_input":"2024-10-10T16:49:51.707562Z","iopub.status.idle":"2024-10-10T16:49:51.713770Z","shell.execute_reply.started":"2024-10-10T16:49:51.707519Z","shell.execute_reply":"2024-10-10T16:49:51.712904Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Spinal canel pipeline","metadata":{}},{"cell_type":"code","source":"VERTEBRA_NAMES = ['L1/L2', 'L2/L3', 'L3/L4', 'L4/L5', 'L5/S1']\nSEVERITY_LABELS = ['normal_mild', 'moderate', 'severe']\n\ndef load_spinal_canal_stenosis_models(slice_model_path, severity_model_path, device):\n    slice_model = SliceIdentificationForSpinalCanal(num_classes=5).to(device)\n    slice_model.load_state_dict(torch.load(slice_model_path, map_location=device,weights_only=True))\n    slice_model.eval()\n\n    severity_model = SeverityClassificationForSpinalCanal(num_classes=3, num_outputs=5).to(device)\n    severity_model.load_state_dict(torch.load(severity_model_path, map_location=device,weights_only=True))\n    severity_model.eval()\n\n    return slice_model, severity_model\n\ndef spinal_process_dicom_image(dcm_path):\n    dicom_data = pydicom.dcmread(dcm_path)\n    image = dicom_data.pixel_array\n    image = (image - image.min()) / (image.max() - image.min() + 1e-6) * 255\n    image = image.astype(np.uint8)\n    image = cv2.resize(image, (512, 512), interpolation=cv2.INTER_CUBIC)\n    image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)  # Keep RGB (3 channels)\n    image = Image.fromarray(image)\n    \n    # Apply transformations\n    transform = transforms.Compose([\n        transforms.ToTensor(),  # Converts image to (C, H, W)\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # For 3-channel RGB\n    ])\n    \n    image = transform(image)  # Should result in shape (3, 512, 512)\n    \n    # Add debug statement\n#     print(f\"Processed image shape: {image.shape}\")\n    \n    return image\n\ndef predict_spinal_canal_slice(model, image, device):\n    # Add debug statement\n#     print(f\"Input image shape: {image.shape}\")\n    \n    with torch.no_grad():\n        outputs = model(image.to(device))\n        probabilities = torch.sigmoid(outputs).cpu().numpy()[0]\n    return probabilities\n\ndef predict_spinal_canal_severity(model, image, device):\n    # Add debug statement\n#     print(f\"Input image shape for severity prediction: {image.shape}\")\n    \n    with torch.no_grad():\n        outputs = model(image.to(device))\n    return [F.softmax(output, dim=1).squeeze().cpu().numpy() for output in outputs]\n\ndef predict_spinal_canal_severity(model, image, device):\n    with torch.no_grad():\n        # Image should already be 4D (1, 3, 512, 512) batch + channel + height + width\n        outputs = model(image.to(device))\n    return [F.softmax(output, dim=1).squeeze().cpu().numpy() for output in outputs]\n\ndef process_spinal_canal_stenosis_folder(folder_path, slice_model, severity_model, device):\n    results = {}\n    \n    for root, _, files in os.walk(folder_path):\n        for filename in tqdm(files):\n            if filename.endswith('.dcm'):\n                dcm_path = os.path.join(root, filename)\n                study_id = os.path.basename(os.path.dirname(os.path.dirname(dcm_path)))\n                image = spinal_process_dicom_image(dcm_path).unsqueeze(0)  # Add batch dimension\n                \n                # Predict slice probabilities\n                slice_probs = predict_spinal_canal_slice(slice_model, image, device)\n            \n                for i, prob in enumerate(slice_probs):\n                    level = VERTEBRA_NAMES[i]\n        \n                    \n                    # If this slice has a higher probability than what we've seen before\n                    if study_id not in results or level not in results[study_id] or prob > results[study_id][level]['prob']:\n                        # Predict severity for this specific image\n                        severity_probs = predict_spinal_canal_severity(severity_model, image, device)\n                        \n                        # Update results with new highest probability slice and its severity\n                        if study_id not in results:\n                            results[study_id] = {}\n                        results[study_id][level] = {\n                            'prob': prob,\n                            'path': dcm_path,\n                            'severity_prob': severity_probs[i]  # Only store severity for this specific level\n                        }\n                        \n    return results\n\n\ndef format_spinal_canal_stenosis_results(results):\n    formatted_results = []\n    \n    for study_id, levels in results.items():\n        for level, data in levels.items():\n            if data:\n                # Directly use the severity probabilities from the selected slice\n                severity_prob = data['severity_prob']\n                formatted_results.append({\n                    'row_id': f\"{study_id}_spinal_canal_stenosis_{level}\",\n                    'normal_mild': severity_prob[0],\n                    'moderate': severity_prob[1],\n                    'severe': severity_prob[2]\n                })\n    \n    return formatted_results\n\n\ndef run_spinal_canal_stenosis_pipeline(dicom_folder, device):\n    slice_model, severity_model = load_spinal_canal_stenosis_models('/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_spinal_level_detection.pth',\n                                                                    '/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_spinal_detection.pth', \n                                                                    device)\n    \n    results = process_spinal_canal_stenosis_folder(dicom_folder, slice_model, severity_model, device)\n    formatted_results = format_spinal_canal_stenosis_results(results)\n    \n    df = pd.DataFrame(formatted_results)\n    return df","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.717206Z","iopub.execute_input":"2024-10-10T16:49:51.717606Z","iopub.status.idle":"2024-10-10T16:49:51.737758Z","shell.execute_reply.started":"2024-10-10T16:49:51.717571Z","shell.execute_reply":"2024-10-10T16:49:51.736653Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Foraminal","metadata":{}},{"cell_type":"markdown","source":"## Foraminal Slice detection","metadata":{}},{"cell_type":"code","source":"class SliceIdentificationForForaminalStenosis(nn.Module):\n    def __init__(self, num_classes=7):  # 5 levels + left + right\n        super(SliceIdentificationForForaminalStenosis, self).__init__()\n        self.model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=0)\n        num_features = self.model.num_features\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(num_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n    \n    def forward(self, x):\n        features = self.model(x)\n        return self.classifier(features)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.740421Z","iopub.execute_input":"2024-10-10T16:49:51.741022Z","iopub.status.idle":"2024-10-10T16:49:51.749903Z","shell.execute_reply.started":"2024-10-10T16:49:51.740973Z","shell.execute_reply":"2024-10-10T16:49:51.748945Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Foraminal Severity detection","metadata":{}},{"cell_type":"code","source":"class SeverityClassificationForForaminalStenosis(nn.Module):\n    def __init__(self, num_classes=3, num_outputs=5):\n        super().__init__()\n        \n        # Use EfficientNet as the backbone\n        self.backbone = timm.create_model('efficientnet_b0', pretrained=True, in_chans=3, features_only=True)\n        self.backbone_channels = self.backbone.feature_info.channels()\n        \n        # Coordinate feature generator\n        self.coord_feature_gen = nn.Conv2d(10, 32, kernel_size=1)\n        \n        # Attention blocks for each level of the backbone\n        self.attention_blocks = nn.ModuleList([\n            ForaminalStenosisAttentionBlock(ch) for ch in self.backbone_channels\n        ])\n        \n        # Global average pooling\n        self.gap = nn.AdaptiveAvgPool2d(1)\n        \n        # Calculate the total number of features\n        total_features = sum(self.backbone_channels) + 32\n        \n        # Feature fusion\n        self.feature_fusion = nn.Sequential(\n            nn.Linear(total_features, 1280),\n            nn.ReLU(),\n            nn.Dropout(0.5)\n        )\n        \n        # Multi-output classifier\n        self.classifier = ForaminalStenosisMultiOutputClassifier(input_size=1280, num_classes=num_classes, num_outputs=num_outputs)\n\n    def forward(self, x, coordinates=None):\n       \n        # Generate coordinate features if provided\n        if coordinates is not None:\n            coord_features = self.create_coordinate_features(coordinates, x.shape[2], x.shape[3])\n          \n            coord_features = self.coord_feature_gen(coord_features)\n          \n        else:\n            coord_features = torch.zeros(x.size(0), 32, x.size(2), x.size(3), device=x.device)\n        \n        # Pass through backbone\n        features = self.backbone(x)\n \n        # Apply attention to each feature level\n        attended_features = [attention(feature) for feature, attention in zip(features, self.attention_blocks)]\n     \n        # Global average pooling\n        pooled_features = [self.gap(feature) for feature in attended_features]\n   \n        \n        # Flatten and concatenate features\n        flattened_features = [feature.view(x.size(0), -1) for feature in pooled_features]\n        flattened_coord_features = self.gap(coord_features).view(x.size(0), -1)\n        concat_features = torch.cat(flattened_features + [flattened_coord_features], dim=1)\n       \n        \n        # Feature fusion\n        fused_features = self.feature_fusion(concat_features)\n  \n        \n        # Multi-output classification\n        outputs = self.classifier(fused_features)\n      \n        \n        return outputs\n\n    def create_coordinate_features(self, coordinates, height, width):\n        batch_size = coordinates.shape[0]\n        coord_features = torch.zeros(batch_size, 10, height, width, device=coordinates.device)\n        for i in range(5):  # 5 disc levels\n            coord_features[:, 2*i] = self.gaussian_heatmap(coordinates[:, i, 0], height, width)\n            coord_features[:, 2*i+1] = self.gaussian_heatmap(coordinates[:, i, 1], height, width)\n        return coord_features\n\n    def gaussian_heatmap(self, coordinate, height, width, sigma=10):\n        x = torch.arange(width).repeat(height, 1).to(coordinate.device)\n        y = torch.arange(height).unsqueeze(1).repeat(1, width).to(coordinate.device)\n        coordinate = coordinate.unsqueeze(1).unsqueeze(2)\n        return torch.exp(-((x - coordinate)**2 + (y - coordinate)**2) / (2 * sigma**2))","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.751866Z","iopub.execute_input":"2024-10-10T16:49:51.752464Z","iopub.status.idle":"2024-10-10T16:49:51.769560Z","shell.execute_reply.started":"2024-10-10T16:49:51.752415Z","shell.execute_reply":"2024-10-10T16:49:51.768395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ForaminalStenosisAttentionBlock(nn.Module):\n    def __init__(self, in_channels):\n        super().__init__()\n        self.attention = nn.Sequential(\n            nn.Conv2d(in_channels, in_channels // 8, kernel_size=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(in_channels // 8, in_channels, kernel_size=1),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return x * self.attention(x)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.771035Z","iopub.execute_input":"2024-10-10T16:49:51.771425Z","iopub.status.idle":"2024-10-10T16:49:51.782115Z","shell.execute_reply.started":"2024-10-10T16:49:51.771380Z","shell.execute_reply":"2024-10-10T16:49:51.781049Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ForaminalStenosisMultiOutputClassifier(nn.Module):\n    def __init__(self, input_size, num_classes, num_outputs):\n        super().__init__()\n        self.shared_layers = nn.Sequential(\n            nn.Linear(input_size, 512),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.5)\n        )\n        self.output_layers = nn.ModuleList([\n            nn.Linear(256, num_classes) for _ in range(num_outputs)\n        ])\n\n    def forward(self, x):\n        shared_features = self.shared_layers(x)\n        return [output_layer(shared_features) for output_layer in self.output_layers]","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.784213Z","iopub.execute_input":"2024-10-10T16:49:51.784995Z","iopub.status.idle":"2024-10-10T16:49:51.792168Z","shell.execute_reply.started":"2024-10-10T16:49:51.784956Z","shell.execute_reply":"2024-10-10T16:49:51.791103Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Foraminal pipeline","metadata":{}},{"cell_type":"code","source":"VERTEBRA_NAMES = ['L1/L2', 'L2/L3', 'L3/L4', 'L4/L5', 'L5/S1']\nSEVERITY_LABELS = ['normal', 'moderate', 'severe']\n\ndef load_foraminal_stenosis_models(slice_model_path, severity_model_path, device):\n    slice_model = SliceIdentificationForForaminalStenosis(num_classes=7).to(device)\n    slice_model.load_state_dict(torch.load(slice_model_path, map_location=device,weights_only=True))\n    slice_model.eval()\n\n    severity_model = SeverityClassificationForForaminalStenosis().to(device)\n    severity_model.load_state_dict(torch.load(severity_model_path, map_location=device,weights_only=True))\n    severity_model.eval()\n\n    return slice_model, severity_model\n\ndef process_dicom_image(dcm_path):\n    dicom_data = pydicom.dcmread(dcm_path)\n    image = dicom_data.pixel_array\n    image = (image - image.min()) / (image.max() - image.min() + 1e-6) * 255\n    image = image.astype(np.uint8)\n    \n    transform = A.Compose([\n        A.Resize(512, 512),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n    \n    img_rgb = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)\n    transformed = transform(image=img_rgb)['image']\n    return transformed.unsqueeze(0)\n\ndef predict_foraminal_stenosis_slice(model, image, device):\n    with torch.no_grad():\n        outputs = model(image.to(device))\n        probabilities = torch.sigmoid(outputs).cpu().numpy()[0]\n    return probabilities\n\ndef predict_foraminal_stenosis_severity(model, image, device):\n    dummy_coordinates = torch.zeros((1, 5, 2), device=device)\n    with torch.no_grad():\n        outputs = model(image.to(device), dummy_coordinates)\n    return [F.softmax(output, dim=1).squeeze().cpu().numpy() for output in outputs]\n\ndef process_foraminal_stenosis_folder(folder_path, slice_model, severity_model, device):\n    results = {\n        'left': {vertebra: {'prob': 0, 'image': None, 'all_probs': None} for vertebra in VERTEBRA_NAMES},\n        'right': {vertebra: {'prob': 0, 'image': None, 'all_probs': None} for vertebra in VERTEBRA_NAMES}\n    }\n\n    for root, _, files in os.walk(folder_path):\n        for filename in tqdm(files):\n            if filename.endswith('.dcm'):\n                dcm_path = os.path.join(root, filename)\n                study_id = os.path.basename(os.path.dirname(os.path.dirname(dcm_path)))\n                image = process_dicom_image(dcm_path)\n                \n                slice_probs = predict_foraminal_stenosis_slice(slice_model, image, device)\n                side = 'left' if slice_probs[5] > slice_probs[6] else 'right'\n                \n                for i, vertebra in enumerate(VERTEBRA_NAMES):\n                    if slice_probs[i] > results[side][vertebra]['prob']:\n                        results[side][vertebra] = {\n                            'prob': slice_probs[i],\n                            'image': image,\n                            'file_path': dcm_path,\n                            'all_probs': slice_probs  # Store all probabilities\n                        }\n\n    final_results = []\n    for side in ['left', 'right']:\n        for vertebra, data in results[side].items():\n            if data['image'] is not None:\n                severity_probs = predict_foraminal_stenosis_severity(severity_model, data['image'], device)\n                final_results.append({\n                    'study_id': study_id,\n                    'side': side,\n                    'vertebra': vertebra,\n                    'file_path': data['file_path'],\n                    'vertebra_prob': data['prob'],\n                    'all_slice_probs': data['all_probs'],  # Include all slice probabilities\n                    'severity_probs': severity_probs[VERTEBRA_NAMES.index(vertebra)]\n                })\n\n    return final_results\n\ndef format_results_stenosis_pipeline(results):\n    formatted_results = []\n    \n    for result in results:\n        study_id = result['study_id']\n        side = result['side']\n        vertebra = result['vertebra']\n        \n        formatted_results.append({\n            'row_id': f\"{study_id}_{side}_neural_foraminal_narrowing_{vertebra.lower().replace('/', '_')}\",\n            'normal_mild': result['severity_probs'][0],\n            'moderate': result['severity_probs'][1],\n            'severe': result['severity_probs'][2]\n        })\n    \n    return formatted_results\n\ndef run_foraminal_stenosis_pipeline(dicom_folder,device):\n    slice_model, severity_model = load_foraminal_stenosis_models(\"/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_foraminal_side_level_detection.pth\", \n                                                                 \"/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_foraminal_detection.pth\", \n                                                                 device)\n    \n    results = process_foraminal_stenosis_folder(dicom_folder, slice_model, severity_model, device)\n    formatted_results = format_results_stenosis_pipeline(results)\n    \n    df = pd.DataFrame(formatted_results)\n\n    return df\n","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.793786Z","iopub.execute_input":"2024-10-10T16:49:51.794155Z","iopub.status.idle":"2024-10-10T16:49:51.816098Z","shell.execute_reply.started":"2024-10-10T16:49:51.794116Z","shell.execute_reply":"2024-10-10T16:49:51.815110Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Subarticular","metadata":{}},{"cell_type":"markdown","source":"## slice detection","metadata":{}},{"cell_type":"code","source":"class SliceIdentificationForSubarticularStenosis(nn.Module):\n    def __init__(self, num_classes=5):\n        super(SliceIdentificationForSubarticularStenosis, self).__init__()\n        self.model = timm.create_model('efficientnet_b0', pretrained=True, num_classes=0)\n        num_features = self.model.num_features\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(num_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n    \n    def forward(self, x):\n        features = self.model(x)\n        return self.classifier(features)","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.817309Z","iopub.execute_input":"2024-10-10T16:49:51.817698Z","iopub.status.idle":"2024-10-10T16:49:51.827954Z","shell.execute_reply.started":"2024-10-10T16:49:51.817653Z","shell.execute_reply":"2024-10-10T16:49:51.826900Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Severity detection","metadata":{}},{"cell_type":"code","source":"class SeverityClassificationForSubarticularStenosis(nn.Module):\n    def __init__(self):\n        super(SeverityClassificationForSubarticularStenosis, self).__init__()\n        \n        # Use a pre-trained ResNet50 as the backbone\n        resnet = models.resnet50(weights = models.ResNet50_Weights.DEFAULT )\n        self.backbone = nn.Sequential(*list(resnet.children())[:-2])\n        \n        # Modify the first convolutional layer to accept grayscale input\n        self.backbone[0] = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)\n        \n        # Add dense blocks for feature enhancement\n        self.dense_block1 = SubarticularStenosisDenseBlock(2048, growth_rate=32, num_layers=4)\n        self.dense_block2 = SubarticularStenosisDenseBlock(2048 + 4 * 32, growth_rate=32, num_layers=4)\n        \n        # Attention mechanism\n        self.attention = SubarticularStenosisAttentionBlock(2048 + 8 * 32)\n        \n        # Global average pooling\n        self.gap = nn.AdaptiveAvgPool2d(1)\n        \n        # Classifier\n        self.classifier = nn.Sequential(\n            nn.Linear(2048 + 8 * 32, 1024),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(1024, 512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 6)  # 6 outputs: 3 severities for left and right\n        )\n\n    def forward(self, x):\n        # Backbone features\n        x = self.backbone(x)\n        \n        # Dense blocks for feature enhancement\n        x = self.dense_block1(x)\n        x = self.dense_block2(x)\n        \n        # Apply attention\n        x = self.attention(x)\n        \n        # Global average pooling\n        x = self.gap(x)\n        x = x.view(x.size(0), -1)\n        \n        # Classification\n        x = self.classifier(x)\n        left_pred = x[:, :3]\n        right_pred = x[:, 3:]\n        return left_pred, right_pred","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.829236Z","iopub.execute_input":"2024-10-10T16:49:51.829894Z","iopub.status.idle":"2024-10-10T16:49:51.842118Z","shell.execute_reply.started":"2024-10-10T16:49:51.829843Z","shell.execute_reply":"2024-10-10T16:49:51.841066Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SubarticularStenosisAttentionBlock(nn.Module):\n    def __init__(self, in_channels):\n        super(SubarticularStenosisAttentionBlock, self).__init__()\n        self.conv = nn.Conv2d(in_channels, 1, kernel_size=1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        attention = self.sigmoid(self.conv(x))\n        return x * attention\n\nclass SubarticularStenosisDenseBlock(nn.Module):\n    def __init__(self, in_channels, growth_rate, num_layers):\n        super(SubarticularStenosisDenseBlock, self).__init__()\n        self.layers = nn.ModuleList()\n        for i in range(num_layers):\n            self.layers.append(nn.Sequential(\n                nn.BatchNorm2d(in_channels + i * growth_rate),\n                nn.ReLU(),\n                nn.Conv2d(in_channels + i * growth_rate, growth_rate, kernel_size=3, padding=1)\n            ))\n\n    def forward(self, x):\n        for layer in self.layers:\n            out = layer(x)\n            x = torch.cat([x, out], dim=1)\n        return x","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.843276Z","iopub.execute_input":"2024-10-10T16:49:51.843627Z","iopub.status.idle":"2024-10-10T16:49:51.855088Z","shell.execute_reply.started":"2024-10-10T16:49:51.843590Z","shell.execute_reply":"2024-10-10T16:49:51.854087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Subartivular pipeline","metadata":{}},{"cell_type":"code","source":"def load_subarticular_stenosis_slice_model(model_path, device):\n    model = SliceIdentificationForSubarticularStenosis(num_classes=5).to(device)\n    model.load_state_dict(torch.load(model_path, map_location=device,weights_only=True))\n    model.eval()\n    return model\n\ndef load_subarticular_stenosis_severity_model(model_path, device):\n    model = SeverityClassificationForSubarticularStenosis().to(device)\n    model.load_state_dict(torch.load(model_path, map_location=device,weights_only=True))\n    model.eval()\n    return model\ndef load_and_preprocess_dicom(dicom_path):\n    # Load DICOM file\n    dicom = pydicom.dcmread(dicom_path)\n    \n    # Convert to numpy array\n    image = dicom.pixel_array.astype(float)\n    \n    # Normalize to 0-255\n    image = ((image - image.min()) / (image.max() - image.min()) * 255.0).astype(np.uint8)\n    \n    # Convert to PIL Image\n    image = Image.fromarray(image).convert(\"L\")\n    \n    # Apply transforms (similar to your validation transforms)\n    transform = transforms.Compose([\n        transforms.Resize((256, 256)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485], std=[0.229])\n    ])\n    \n    return transform(image).unsqueeze(0) \ndef process_dicom_image(dcm_path):\n    dicom_data = pydicom.dcmread(dcm_path)\n    image = dicom_data.pixel_array\n    \n    # Normalize and resize the image to 512x512\n    image = (image - image.min()) / (image.max() - image.min() + 1e-6) * 255\n    image = image.astype(np.uint8)\n    \n    # Resize image to 512x512\n    resized_image = cv2.resize(image, (512, 512), interpolation=cv2.INTER_CUBIC)\n    \n    # Convert to RGB and apply transformations\n    img = Image.fromarray(resized_image).convert(\"RGB\")\n    transform = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    return transform(img).unsqueeze(0)\ndef predict_subarticular_stenosis_slice(model, image, device):\n    with torch.no_grad():\n        outputs = model(image.to(device))\n        probabilities = torch.sigmoid(outputs).cpu().numpy()[0]\n    return probabilities\n\ndef predict_severity(model ,image ,device):\n    with torch.no_grad():\n        left_pred, right_pred = model(image.to(device))\n        left_probs = torch.softmax(left_pred, dim=1).cpu().numpy()[0]\n        right_probs = torch.softmax(right_pred, dim=1).cpu().numpy()[0]\n    return left_probs, right_probs\n\ndef process_subarticular_stenosis_folder(folder_path, slice_model, device):\n    results = []\n    for root, _, files in os.walk(folder_path):\n        for filename in tqdm(files):\n            if filename.endswith('.dcm'):\n                dcm_path = os.path.join(root, filename)\n                image = process_dicom_image(dcm_path).to(device)\n                slice_probs = predict_subarticular_stenosis_slice(slice_model, image, device)\n                study_id = os.path.basename(os.path.dirname(os.path.dirname(dcm_path)))\n                results.append((dcm_path, study_id, slice_probs))\n    return results\n\ndef find_best_slices(results, vertebra_names):\n    best_slices = {vertebra: (None, -1, None) for vertebra in vertebra_names}\n    all_slices = {vertebra: [] for vertebra in vertebra_names}\n    \n    for dcm_path, study_id, probabilities in results:\n        for i, vertebra in enumerate(vertebra_names):\n            all_slices[vertebra].append((dcm_path, probabilities[i]))\n            if probabilities[i] > best_slices[vertebra][1]:\n                best_slices[vertebra] = (dcm_path, probabilities[i], study_id)\n    \n    return best_slices, all_slices\n\n\ndef predict_subarticular_stenosis_severity(best_slices, severity_model, device):\n    predictions = []\n    for vertebra, (dcm_path, _, study_id) in best_slices.items():\n        \n        image = load_and_preprocess_dicom(dcm_path)\n        \n        left_probs, right_probs = predict_severity(severity_model, image, device)\n        \n        predictions.extend([\n            (f\"{study_id}_left_subarticular_stenosis_{vertebra.lower().replace('/', '_')}\", left_probs),\n            (f\"{study_id}_right_subarticular_stenosis_{vertebra.lower().replace('/', '_')}\", right_probs)\n        ])\n    return predictions\n\ndef run_subarticular_stenosis_pipeline(folder_path, device):\n    vertebra_names = ['L1/L2', 'L2/L3', 'L3/L4', 'L4/L5', 'L5/S1']\n    \n    slice_model = load_subarticular_stenosis_slice_model(\"/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_subarticular_level_detection.pth\", device)\n    severity_model = load_subarticular_stenosis_severity_model(\"/kaggle/input/rsna-2024-lumbar-spine-classification-weights/pytorch/default/1/RSNA_2024_subarticular_detection.pth\", device)\n    \n    results = process_subarticular_stenosis_folder(folder_path, slice_model, device)\n    best_slices, all_slices = find_best_slices(results, vertebra_names)\n    predictions = predict_subarticular_stenosis_severity(best_slices, severity_model, device)\n    \n    df_data = []\n    for identifier, probs in predictions:\n        df_data.append({\n            'row_id': identifier,\n            'normal_mild': probs[0],\n            'moderate': probs[1],\n            'severe': probs[2]\n        })\n    \n    df = pd.DataFrame(df_data)\n    return df","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.857769Z","iopub.execute_input":"2024-10-10T16:49:51.858313Z","iopub.status.idle":"2024-10-10T16:49:51.880670Z","shell.execute_reply.started":"2024-10-10T16:49:51.858277Z","shell.execute_reply":"2024-10-10T16:49:51.879627Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def process_study(study_id, series_paths, models, device):\n    results = []\n    \n\n    spinal_results = run_spinal_canal_stenosis_pipeline(series_paths['spinal'],  device)\n    results.append(spinal_results)\n\n    foraminal_results = run_foraminal_stenosis_pipeline(series_paths['foraminal'], device)\n    results.append(foraminal_results)\n  \n    subarticular_results = run_subarticular_stenosis_pipeline(series_paths['subarticular'],  device)\n    results.append(subarticular_results)\n    \n    combined_results = pd.concat(results, ignore_index=True)\n    float_columns = ['normal_mild', 'moderate', 'severe']\n    combined_results[float_columns] = combined_results[float_columns].round(3)\n    \n    return combined_results","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.881668Z","iopub.execute_input":"2024-10-10T16:49:51.882005Z","iopub.status.idle":"2024-10-10T16:49:51.893567Z","shell.execute_reply.started":"2024-10-10T16:49:51.881970Z","shell.execute_reply":"2024-10-10T16:49:51.892698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def process_all_studies(dataset_path, image_root_path):\n    df = pd.read_csv(dataset_path)\n    grouped = df.groupby('study_id')\n    \n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    all_results = []\n    i =0\n    for study_id, group in tqdm(grouped, desc=\"Processing studies\"):\n        series_paths = {\n            'spinal': os.path.join(image_root_path, str(study_id), str(group[group['series_description'] == 'Sagittal T2/STIR']['series_id'].iloc[0])),\n            'foraminal': os.path.join(image_root_path, str(study_id), str(group[group['series_description'] == 'Sagittal T1']['series_id'].iloc[0])),\n            'subarticular': os.path.join(image_root_path, str(study_id), str(group[group['series_description'] == 'Axial T2']['series_id'].iloc[0]))\n        }\n        \n        study_results = process_study(study_id, series_paths, models, device)\n        all_results.append(study_results)\n        if i ==2:\n            break\n        i=i+1\n    \n    final_results = pd.concat(all_results, ignore_index=True)\n\n    return final_results","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.894867Z","iopub.execute_input":"2024-10-10T16:49:51.895193Z","iopub.status.idle":"2024-10-10T16:49:51.904291Z","shell.execute_reply.started":"2024-10-10T16:49:51.895159Z","shell.execute_reply":"2024-10-10T16:49:51.903322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# image_root_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_images'\n# dataset_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_series_descriptions.csv'\n# results = process_all_studies(dataset_path, image_root_path)\n# results.to_csv('submission.csv', index=False)\n# results","metadata":{"execution":{"iopub.status.busy":"2024-10-10T16:49:51.905535Z","iopub.execute_input":"2024-10-10T16:49:51.905844Z","iopub.status.idle":"2024-10-10T16:50:25.564401Z","shell.execute_reply.started":"2024-10-10T16:49:51.905811Z","shell.execute_reply":"2024-10-10T16:50:25.563312Z"},"trusted":true},"execution_count":null,"outputs":[]}]}