{"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":"gpu","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":8722001,"sourceType":"datasetVersion","datasetId":5233879}],"dockerImageVersionId":30716,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Inference\n\nTraining code: [rsna-3channel](https://www.kaggle.com/code/samu2505/rsna-3channeldataset?scriptVersionId=182471948)\n\nLoading Model weights and Cross-validation: [wandb&cross-validation](https://www.kaggle.com/code/samu2505/rsna-wandbmodelweights-crossvalidation/notebook)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"# Import libraries","metadata":{}},{"cell_type":"code","source":"import os, gc, sys, copy, pickle\nfrom pathlib import Path\nimport glob\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nimport math\nimport random\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nfrom joblib import Parallel, delayed\nimport multiprocessing as mp\n\nimport albumentations as A\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.cuda.amp as amp\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.transforms as transforms\n\nimport pytorch_lightning as pl\nfrom pytorch_lightning.loggers import WandbLogger\nfrom pytorch_lightning.callbacks import LearningRateMonitor, ModelCheckpoint\nfrom pytorch_lightning.callbacks.early_stopping import EarlyStopping\n\nfrom torch.utils.data import WeightedRandomSampler\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport timm\n\nimport cv2\ncv2.setNumThreads(0)\nimport PIL\nimport pydicom\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:47:51.796360Z","iopub.execute_input":"2024-06-19T10:47:51.796791Z","iopub.status.idle":"2024-06-19T10:48:01.905059Z","shell.execute_reply.started":"2024-06-19T10:47:51.796751Z","shell.execute_reply":"2024-06-19T10:48:01.904168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def seeding(SEED):\n    np.random.seed(SEED)\n    random.seed(SEED)\n    os.environ['PYTHONHASHSEED'] = str(SEED)\n    torch.manual_seed(SEED)\n    if torch.cuda.is_available(): \n        torch.cuda.manual_seed(SEED)\n        torch.cuda.manual_seed_all(SEED)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n    print('seeding done!!!')\n    \ndef flush():\n    gc.collect()\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n        torch.cuda.reset_peak_memory_stats()","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:01.906886Z","iopub.execute_input":"2024-06-19T10:48:01.907253Z","iopub.status.idle":"2024-06-19T10:48:01.913962Z","shell.execute_reply.started":"2024-06-19T10:48:01.907220Z","shell.execute_reply":"2024-06-19T10:48:01.913150Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configuration","metadata":{}},{"cell_type":"code","source":"CONFIG = dict(\n    project_name = \"PL-RSNA-2024-Lumbar-Spine-Classification\",\n    artifact_name = \"rsnaEffNetModel\",\n    load_kernel = None,\n    load_last = True,\n    n_folds = 5,\n    backbone = \"efficientnet_b0.ra_in1k\", # tf_efficientnetv2_s_in21ft1k\n    img_size = 384,\n    n_slice_per_c = 16,\n    in_chans = 1,\n\n    drop_rate = 0.,\n    drop_rate_last = 0.3,\n    drop_path_rate = 0.,\n    p_mixup = 0.5,\n    p_rand_order_v1 = 0.2,\n    lr = 1e-3,\n\n    out_dim = 3,\n    epochs = 15,\n    batch_size = 8,\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    seed = 2024\n)\n\nseeding(CONFIG['seed'])","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:01.915464Z","iopub.execute_input":"2024-06-19T10:48:01.915978Z","iopub.status.idle":"2024-06-19T10:48:01.963225Z","shell.execute_reply.started":"2024-06-19T10:48:01.915945Z","shell.execute_reply":"2024-06-19T10:48:01.962419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"DATA_PATH = Path(\"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification\")\nos.listdir(DATA_PATH)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:01.965518Z","iopub.execute_input":"2024-06-19T10:48:01.965843Z","iopub.status.idle":"2024-06-19T10:48:01.973530Z","shell.execute_reply.started":"2024-06-19T10:48:01.965816Z","shell.execute_reply":"2024-06-19T10:48:01.972529Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_df = pd.read_csv(DATA_PATH/\"sample_submission.csv\")\ntest_desc = pd.read_csv(DATA_PATH/\"test_series_descriptions.csv\")\ntrain_desc = pd.read_csv(DATA_PATH/\"train_series_descriptions.csv\")\ntrain_main = pd.read_csv(DATA_PATH/\"train.csv\")","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:01.974554Z","iopub.execute_input":"2024-06-19T10:48:01.974816Z","iopub.status.idle":"2024-06-19T10:48:02.040799Z","shell.execute_reply.started":"2024-06-19T10:48:01.974794Z","shell.execute_reply":"2024-06-19T10:48:02.039949Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"FAKE_TEST = len(sample_df) <= 25","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:02.042038Z","iopub.execute_input":"2024-06-19T10:48:02.042287Z","iopub.status.idle":"2024-06-19T10:48:02.046433Z","shell.execute_reply.started":"2024-06-19T10:48:02.042263Z","shell.execute_reply":"2024-06-19T10:48:02.045346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create test dataset\n\nAdopted from [shubhamcodez](https://www.kaggle.com/code/shubhamcodez/rsna-resnet-starter-notebook)","metadata":{}},{"cell_type":"code","source":"# define the base path for test images\nbase_path = f\"{str(DATA_PATH)}/test_images\"\n\n# function to get image paths for a series\ndef get_image_paths(row):\n    series_path = os.path.join(base_path, str(row['study_id']), str(row['series_id']))\n    if os.path.exists(series_path):\n        return [\n            os.path.join(series_path, f) for f in os.listdir(series_path) if os.path.isfile(os.path.join(series_path, f))\n        ]\n    return []\n\n# Mapping of series_description to conditions\ncondition_mapping = {\n    'Sagittal T1': {'left': 'left_neural_foraminal_narrowing', 'right': 'right_neural_foraminal_narrowing'},\n    'Axial T2': {'left': 'left_subarticular_stenosis', 'right': 'right_subarticular_stenosis'},\n    'Sagittal T2/STIR': 'spinal_canal_stenosis'\n}\n\n# Create a list to store the expanded rows\nexpanded_rows = []\n\n# Expand the dataframe by adding new rows for each file path\nfor index, row in test_desc.iterrows():\n    image_paths = get_image_paths(row)\n    conditions = condition_mapping.get(row['series_description'], {})\n    if isinstance(conditions, str):  # Single condition\n        conditions = {'left': conditions, 'right': conditions}\n    for side, condition in conditions.items():\n        for image_path in image_paths:\n            expanded_rows.append({\n                'study_id': row['study_id'],\n                'series_id': row['series_id'],\n                'series_description': row['series_description'],\n                'image_path': image_path,\n                'condition': condition,\n                'row_id': f\"{row['study_id']}_{condition}\"\n            })\n\n# Create a new dataframe from the expanded rows\nexpanded_test_desc = pd.DataFrame(expanded_rows)\n\ntest_data = expanded_test_desc.copy()\ntest_data['target'] = 0\ntest_data.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:02.047841Z","iopub.execute_input":"2024-06-19T10:48:02.048386Z","iopub.status.idle":"2024-06-19T10:48:02.104967Z","shell.execute_reply.started":"2024-06-19T10:48:02.048361Z","shell.execute_reply":"2024-06-19T10:48:02.104166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if FAKE_TEST:\n    # define the base path for test images\n    base_path = f\"{str(DATA_PATH)}/train_images\"\n\n    # function to get image paths for a series\n    def get_image_paths(row):\n        series_path = os.path.join(base_path, str(row['study_id']), str(row['series_id']))\n        if os.path.exists(series_path):\n            return [\n                os.path.join(series_path, f) for f in os.listdir(series_path) if os.path.isfile(os.path.join(series_path, f))\n            ]\n        return []\n\n    # Mapping of series_description to conditions\n    condition_mapping = {\n        'Sagittal T1': {'left': 'left_neural_foraminal_narrowing', 'right': 'right_neural_foraminal_narrowing'},\n        'Axial T2': {'left': 'left_subarticular_stenosis', 'right': 'right_subarticular_stenosis'},\n        'Sagittal T2/STIR': 'spinal_canal_stenosis'\n    }\n\n    # Create a list to store the expanded rows\n    expanded_rows = []\n\n    # Expand the dataframe by adding new rows for each file path\n    for index, row in tqdm(train_desc.iterrows(), total=len(train_desc)):\n        image_paths = get_image_paths(row)\n        conditions = condition_mapping.get(row['series_description'], {})\n        if isinstance(conditions, str):  # Single condition\n            conditions = {'left': conditions, 'right': conditions}\n        for side, condition in conditions.items():\n            for image_path in image_paths:\n                expanded_rows.append({\n                    'study_id': row['study_id'],\n                    'series_id': row['series_id'],\n                    'series_description': row['series_description'],\n                    'image_path': image_path,\n                    'condition': condition,\n                    'row_id': f\"{row['study_id']}_{condition}\"\n                })\n\n    # Create a new dataframe from the expanded rows\n    expanded_test_desc = pd.DataFrame(expanded_rows)\n\n    test_data = expanded_test_desc.copy()\n    test_data['target'] = 0\n    display(test_data.head())","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:02.106268Z","iopub.execute_input":"2024-06-19T10:48:02.106780Z","iopub.status.idle":"2024-06-19T10:48:49.786701Z","shell.execute_reply.started":"2024-06-19T10:48:02.106757Z","shell.execute_reply":"2024-06-19T10:48:49.785773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label2id = {\"Normal/Mild\": 0, \"Moderate\": 1, \"Severe\": 2}\nid2label = {v:k for k,v in label2id.items()}","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.787773Z","iopub.execute_input":"2024-06-19T10:48:49.788050Z","iopub.status.idle":"2024-06-19T10:48:49.792884Z","shell.execute_reply.started":"2024-06-19T10:48:49.788025Z","shell.execute_reply":"2024-06-19T10:48:49.791908Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_dicom(path):\n    dicom = pydicom.read_file(path)\n    data = dicom.pixel_array\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    return data","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.796966Z","iopub.execute_input":"2024-06-19T10:48:49.797342Z","iopub.status.idle":"2024-06-19T10:48:49.806745Z","shell.execute_reply.started":"2024-06-19T10:48:49.797311Z","shell.execute_reply":"2024-06-19T10:48:49.805875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomDataset(Dataset):\n    def __init__(self, dataframe, transform=None, label_name='target'):\n        self.dataframe = dataframe\n        self.transform = transform\n        self.label = dataframe.loc[:, label_name]\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n        image_path = self.dataframe['image_path'][index]\n        image = load_dicom(image_path)  # Define this function to load your DICOM images\n        target = self.dataframe['target'][index]\n        \n        if self.transform:\n            image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)\n            image = self.transform(image=image)['image']\n            image = image.transpose(2, 0, 1).astype(np.float32) / 255.\n\n        return image, torch.tensor(target).float()\n    \n    def get_labels(self):\n        return self.label","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.808026Z","iopub.execute_input":"2024-06-19T10:48:49.808872Z","iopub.status.idle":"2024-06-19T10:48:49.817645Z","shell.execute_reply.started":"2024-06-19T10:48:49.808840Z","shell.execute_reply":"2024-06-19T10:48:49.816902Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_transforms(height, width):\n    train_tsfm = A.Compose([\n        # Geometric augmentations\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.Rotate(-30, 30, p=0.5),\n        \n        A.Resize(height=height, width=width),\n    ])\n    \n    valid_tsfm = A.Compose([\n        A.Resize(height=height, width=width, interpolation=cv2.INTER_CUBIC, p=1.0), # also INTER_LANCZOS4\n        A.CenterCrop(height=height, width=width, p=1.0),\n    ])\n    return {\"train\": train_tsfm, \"eval\": valid_tsfm}\n\n\ndef get_dataloaders(data, cfg, split=\"train\"):\n    img_size = cfg['img_size']\n    height, width = img_size, img_size\n    tsfm = get_transforms(height=height, width=width)\n    if split == 'train':\n        tr_tsfm = tsfm['train']\n        ds = CustomDataset(data, transform=tr_tsfm)\n        labels = ds.get_labels()\n        class_weights = torch.tensor([1, 2, 4])\n        samples_weights = class_weights[labels]\n        sampler = WeightedRandomSampler(weights=samples_weights, \n                                        num_samples=len(samples_weights), \n                                        replacement=True)\n\n        dls = DataLoader(ds, \n                         batch_size=cfg['batch_size'], \n                         sampler=sampler, \n                         num_workers=os.cpu_count(), \n                         drop_last=True, \n                         pin_memory=True)\n        \n    elif split == 'valid' or split == 'test':\n        eval_tsfm = tsfm['eval']\n        ds = CustomDataset(data, transform=eval_tsfm)\n        dls = DataLoader(ds, \n                         batch_size=2*cfg['batch_size'], \n                         shuffle=False, \n                         num_workers=os.cpu_count(), \n                         drop_last=False, \n                         pin_memory=True)\n    else:\n        raise Exception(\"Split should be 'train' or 'valid' or 'test'!!!\")\n    return dls","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.819071Z","iopub.execute_input":"2024-06-19T10:48:49.819303Z","iopub.status.idle":"2024-06-19T10:48:49.830686Z","shell.execute_reply.started":"2024-06-19T10:48:49.819283Z","shell.execute_reply":"2024-06-19T10:48:49.829854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"class TimmModel(nn.Module):\n    def __init__(self, backbone, pretrained=False):\n        super(TimmModel, self).__init__()\n\n        self.encoder = timm.create_model(\n            backbone,\n            num_classes=CONFIG[\"out_dim\"],\n            features_only=False,\n            drop_rate=CONFIG[\"drop_rate\"],\n            drop_path_rate=CONFIG[\"drop_path_rate\"],\n            pretrained=pretrained\n        )\n\n        if 'efficient' in backbone:\n            hdim = self.encoder.conv_head.out_channels\n            self.encoder.classifier = nn.Identity()\n        elif 'convnext' in backbone:\n            hdim = self.encoder.head.fc.in_features\n            self.encoder.head.fc = nn.Identity()\n\n\n        self.lstm = nn.LSTM(hdim, 256, num_layers=2, dropout=CONFIG[\"drop_rate\"], bidirectional=True, batch_first=True)\n        self.head = nn.Sequential(\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.Dropout(CONFIG[\"drop_rate_last\"]),\n            nn.LeakyReLU(0.1),\n            nn.Linear(256, CONFIG[\"out_dim\"]),\n        )\n\n    def forward(self, x):\n        feat = self.encoder(x)\n        feat, _ = self.lstm(feat)\n        feat = self.head(feat)\n        return feat","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.831801Z","iopub.execute_input":"2024-06-19T10:48:49.832114Z","iopub.status.idle":"2024-06-19T10:48:49.842318Z","shell.execute_reply.started":"2024-06-19T10:48:49.832088Z","shell.execute_reply":"2024-06-19T10:48:49.841427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predictions\n\nHere we have code for prediction with and without test time augmentation\n\nTest time augmentation (TTA) is useful for test images that the model is pretty unsure. It takes approximately 2 hours as compared to the normal prediction which takes roughly 30 minutes","metadata":{}},{"cell_type":"code","source":"FLIPS = [None, [-1], [-2], [-2, -1]]\n\ndef inference_loop(model, loader):\n    model.to(CONFIG[\"device\"])\n    model.eval()\n    preds = np.empty((0, 3))\n    with torch.no_grad():\n        for batch in tqdm(loader):\n            images, labels = batch\n            images = images.to(CONFIG[\"device\"], non_blocking=True)\n            with torch.autocast(device_type=\"cuda\", dtype=torch.float16):\n                logits = model(images)\n                logits = logits.softmax(dim=-1)\n                preds = np.concatenate([preds, logits.detach().cpu().numpy()])\n    np.save('preds.npy', preds)\n    \n    \ndef tta_inference_loop(model, loader):\n    model.to(CONFIG[\"device\"])\n    model.eval()\n    preds = np.empty((0, 3))\n    with torch.no_grad():\n        for batch in tqdm(loader):\n            images, labels = batch\n            images = images.to(CONFIG[\"device\"], non_blocking=True)\n            pred_tta = []\n            with torch.autocast(device_type=\"cuda\", dtype=torch.float16):\n                for f in FLIPS:\n                    logits = model(torch.flip(images, f) if f is not None else images)\n                    logits = logits.softmax(dim=-1)\n                    pred_tta.append(logits.detach().cpu().numpy())\n#                     del logits\n                preds = np.concatenate([preds, np.mean(pred_tta, 0)])\n#             del images, labels, pred_tta\n#             flush()\n    np.save('preds.npy', preds)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.843244Z","iopub.execute_input":"2024-06-19T10:48:49.843807Z","iopub.status.idle":"2024-06-19T10:48:49.855984Z","shell.execute_reply.started":"2024-06-19T10:48:49.843782Z","shell.execute_reply":"2024-06-19T10:48:49.855176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load model weights\n\nThe model is trained for 1 fold using pytorch lightning [rsna-pytorchlightning](https://www.kaggle.com/code/samu2505/rsna-pytorchlightning-train?scriptVersionId=182471948) and the model weights are stored using Weights and Biases artifacts and you can find them here [rsna-wandbmodelweights-crossvalidation](https://www.kaggle.com/code/samu2505/rsna-wandbmodelweights-crossvalidation/notebook)","metadata":{}},{"cell_type":"code","source":"weights_path = \"/kaggle/input/rsna-pytorchmodels/rsna_2024_lumbar_spine_fold_1_epoch_9.pth\"\nweights = torch.load(weights_path, map_location=torch.device(\"cpu\"))\nmodel = TimmModel(backbone=CONFIG[\"backbone\"], pretrained=False)\nmodel.load_state_dict(weights)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:49.856988Z","iopub.execute_input":"2024-06-19T10:48:49.857277Z","iopub.status.idle":"2024-06-19T10:48:50.456377Z","shell.execute_reply.started":"2024-06-19T10:48:49.857254Z","shell.execute_reply":"2024-06-19T10:48:50.455453Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dls = get_dataloaders(test_data, CONFIG, split=\"test\")\ntta_inference_loop(model, dls)\n\npreds = np.load('preds.npy')","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:48:50.457645Z","iopub.execute_input":"2024-06-19T10:48:50.458256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"levels = ['l1_l2', 'l2_l3', 'l3_l4', 'l4_l5', 'l5_s1']\n\n# Function to update row_id with levels\ndef update_row_id(row, levels):\n    level = levels[row.name % len(levels)]\n    return f\"{row['study_id']}_{row['condition']}_{level}\"\n\n# Update row_id in expanded_test_desc to include levels\nexpanded_test_desc['row_id'] = expanded_test_desc.apply(lambda row: update_row_id(row, levels), axis=1)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"target_cols = [\"normal_mild\",\"moderate\",\"severe\"]\n\nexpanded_test_desc[[\"normal_mild\",\"moderate\",\"severe\"]] = preds\n\nfinal_df = expanded_test_desc[[\"row_id\", *target_cols]]\n\nfinal_df = final_df.groupby('row_id').sum().reset_index()\n\n# normalize the columns\nfinal_df[target_cols] = final_df[target_cols].div(final_df[target_cols].sum(axis=1), axis=0)\n\nfinal_df[[\"row_id\", *target_cols]].to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!head -n 25 submission.csv","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport pandas.api.types\nimport sklearn.metrics\n\n\nclass ParticipantVisibleError(Exception):\n    pass\n\n\ndef get_condition(full_location: str) -> str:\n    # Given an input like spinal_canal_stenosis_l1_l2 extracts 'spinal'\n    for injury_condition in ['spinal', 'foraminal', 'subarticular']:\n        if injury_condition in full_location:\n            return injury_condition\n    raise ValueError(f'condition not found in {full_location}')\n\n\ndef score(\n        solution: pd.DataFrame,\n        submission: pd.DataFrame,\n        row_id_column_name: str,\n        any_severe_scalar: float\n    ) -> float:\n    '''\n    Pseudocode:\n    1. Calculate the sample weighted log loss for each medical condition:\n    2. Derive a new any_severe label.\n    3. Calculate the sample weighted log loss for the new any_severe label.\n    4. Return the average of all of the label group log losses as the final score, normalized for the number of columns in each group.\n       This mitigates the impact of spinal stenosis having only half as many columns as the other two conditions.\n    '''\n\n    target_levels = ['normal_mild', 'moderate', 'severe']\n\n    # Run basic QC checks on the inputs\n    if not pandas.api.types.is_numeric_dtype(submission[target_levels].values):\n        raise ParticipantVisibleError('All submission values must be numeric')\n\n    if not np.isfinite(submission[target_levels].values).all():\n        raise ParticipantVisibleError('All submission values must be finite')\n\n    if solution[target_levels].min().min() < 0:\n        raise ParticipantVisibleError('All labels must be at least zero')\n    if submission[target_levels].min().min() < 0:\n        raise ParticipantVisibleError('All predictions must be at least zero')\n\n    solution['study_id'] = solution['row_id'].apply(lambda x: x.split('_')[0])\n    solution['location'] = solution['row_id'].apply(lambda x: '_'.join(x.split('_')[1:]))\n    solution['condition'] = solution['row_id'].apply(get_condition)\n\n    del solution[row_id_column_name]\n    del submission[row_id_column_name]\n    assert sorted(submission.columns) == sorted(target_levels)\n\n    submission['study_id'] = solution['study_id']\n    submission['location'] = solution['location']\n    submission['condition'] = solution['condition']\n\n    condition_losses = []\n    condition_weights = []\n    for condition in ['spinal', 'foraminal', 'subarticular']:\n        condition_indices = solution.loc[solution['condition'] == condition].index.values\n        condition_loss = sklearn.metrics.log_loss(\n            y_true=solution.loc[condition_indices, target_levels].values,\n            y_pred=submission.loc[condition_indices, target_levels].values,\n            sample_weight=solution.loc[condition_indices, 'sample_weight'].values\n        )\n        condition_losses.append(condition_loss)\n        condition_weights.append(1)\n\n    any_severe_spinal_labels = pd.Series(solution.loc[solution['condition'] == 'spinal'].groupby('study_id')['severe'].max())\n    any_severe_spinal_weights = pd.Series(solution.loc[solution['condition'] == 'spinal'].groupby('study_id')['sample_weight'].max())\n    any_severe_spinal_predictions = pd.Series(submission.loc[submission['condition'] == 'spinal'].groupby('study_id')['severe'].max())\n    any_severe_spinal_loss = sklearn.metrics.log_loss(\n        y_true=any_severe_spinal_labels,\n        y_pred=any_severe_spinal_predictions,\n        sample_weight=any_severe_spinal_weights\n    )\n    condition_losses.append(any_severe_spinal_loss)\n    condition_weights.append(any_severe_scalar)\n    return np.average(condition_losses, weights=condition_weights)\n\n\ndef score_from_train(\n    submission: pd.DataFrame,  # Pass submission.csv as a DataFrame\n    train_df: pd.DataFrame,  # Pass train.csv as a DataFrame\n    row_id_column_name=\"row_id\",\n    any_severe_scalar=1.0,\n    sample_weights: dict[str, int]={\"normal_mild\": 1, \"moderate\": 2, \"severe\": 4},\n):\n    target_cols = list(sample_weights.keys())\n    pred = submission.copy() # Copy to prevent changes in original\n    # Normalize values to have a sum of 1.0\n    pred[target_cols] = pred[target_cols].div(pred[target_cols].sum(axis=1), axis=0)\n\n    # Index the study_id in train_df\n    indexed_train_df = train_df.set_index(\"study_id\", verify_integrity=True)\n\n    row_ids = pred[row_id_column_name]\n    study_ids = row_ids.apply(lambda x: x.split('_')[0])\n    locations = row_ids.apply(lambda x: '_'.join(x.split('_')[1:]))\n\n    solution_data = np.zeros_like(pred[target_cols].values)\n    sample_weight_list = []\n    nan_row_ids = set()\n    for idx, (row, study_id, location) in enumerate(zip(row_ids, study_ids, locations)):\n        severity = str(indexed_train_df.at[int(study_id), location]).replace(\"/\", \"_\").lower()\n        if severity in sample_weights:\n            solution_data[idx, target_cols.index(severity)] = 1.0\n            sample_weight_list.append(sample_weights[severity])\n        else:\n            solution_data[idx] = np.nan\n            nan_row_ids.add(row)\n            sample_weight_list.append(np.nan)\n\n    solution = pd.DataFrame({\n        row_id_column_name: pred[row_id_column_name],\n        \"sample_weight\": sample_weight_list\n    })\n    solution[target_cols] = solution_data\n\n    # Change row_ids in nan_row_ids to np.nan\n    pred.loc[pred[row_id_column_name].isin(nan_row_ids), target_cols] = np.nan\n    # Remove nan rows and pass copy to score function\n    # score from https://www.kaggle.com/code/metric/rsna-lumbar-metric-71549?scriptVersionId=181722791 (Version 10)\n    return score(solution.dropna().copy(), pred.dropna().copy(), row_id_column_name, any_severe_scalar)\n\n\nif FAKE_TEST:\n    print(score_from_train(pd.read_csv(\"submission.csv\"), pd.read_csv(DATA_PATH / \"train.csv\")))","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}