{"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":30733,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"## DEBUG: Why are you pip installing when internet is off?\n# !pip install pandas==2.2.2\n# !pip install scikit-learn==1.2.2\n# !pip install numpy==1.26.4\n# !pip install torch==2.1.2\n# !pip install opencv-python==4.10.0.82\n# !pip install albumentations==1.4.0\n\n# Also all the above pakages should be installed\n# %pip list | grep pandas  # pandas -> 2.2.1 \n# %pip list | grep scikit  # scikit-learn -> 1.2.2 \n# %pip list | grep numpy  # numpy -> 1.26.4 \n# %pip list | grep torch  # torch -> 2.1.2 \n# %pip list | grep albumentations  # albumentations -> 1.4.0 ","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-07-19T11:47:09.984283Z","iopub.execute_input":"2024-07-19T11:47:09.985140Z","iopub.status.idle":"2024-07-19T11:47:09.992377Z","shell.execute_reply.started":"2024-07-19T11:47:09.985095Z","shell.execute_reply":"2024-07-19T11:47:09.991202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import glob\nimport os\nimport random\nimport string\nimport sys\nfrom pathlib import Path\n\nimport albumentations as A\nimport cv2 as cv\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport torch\nfrom albumentations import DualTransform\nfrom albumentations.pytorch import ToTensorV2\nfrom scipy.ndimage import zoom\nfrom torch import nn\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.utils.data.dataset import T_co\nfrom tqdm import tqdm","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:09.998270Z","iopub.execute_input":"2024-07-19T11:47:09.998575Z","iopub.status.idle":"2024-07-19T11:47:13.789861Z","shell.execute_reply.started":"2024-07-19T11:47:09.998550Z","shell.execute_reply":"2024-07-19T11:47:13.789046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"INPUT_DIR = Path(\"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification\")\nCONFIG = dict(\n    num_classes = [15,30],\n    num_workers = 8,\n    batch_size = 64,\n    test_data_desc = INPUT_DIR / \"test_series_descriptions.csv\",\n    test_data_path = str(INPUT_DIR / \"test_images\"),\n)\nFAKE_TEST = False","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:13.791624Z","iopub.execute_input":"2024-07-19T11:47:13.792081Z","iopub.status.idle":"2024-07-19T11:47:13.797452Z","shell.execute_reply.started":"2024-07-19T11:47:13.792052Z","shell.execute_reply":"2024-07-19T11:47:13.796424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_train_labels():\n    df_train_main = pd.read_csv(INPUT_DIR / 'train.csv')\n    unpivoted = df_train_main.melt(\n        id_vars=\"study_id\", var_name=\"condition_level\", value_name=\"severity\"\n    )\n\n    unpivoted[\"values\"] = 1.0\n    unpivoted[\"row_id\"] = (\n        unpivoted[\"study_id\"].astype(str) + \"_\" + unpivoted[\"condition_level\"]\n    )\n    labels = unpivoted.pivot(index=[\"row_id\"], columns=\"severity\", values=\"values\")\n\n    labels = labels.rename(\n        columns={\n            np.nan: \"weights\",\n            \"Normal/Mild\": \"normal_mild\",\n            \"Moderate\": \"moderate\",\n            \"Severe\": \"severe\",\n        }\n    )\n    labels = labels.fillna(0.0)\n    labels.weights = 1.0 - labels.weights\n    labels.columns.set_names(None, inplace=True)\n    \n    return labels.reset_index()","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:13.798679Z","iopub.execute_input":"2024-07-19T11:47:13.798998Z","iopub.status.idle":"2024-07-19T11:47:13.807128Z","shell.execute_reply.started":"2024-07-19T11:47:13.798970Z","shell.execute_reply":"2024-07-19T11:47:13.806308Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ss_df = pd.read_csv(INPUT_DIR / \"sample_submission.csv\")\n\nif FAKE_TEST and len(ss_df) <= 25:\n    print(\"Enter testing mode...\")\n    CONFIG[\"test_data_desc\"] = INPUT_DIR / \"train_series_descriptions.csv\"\n    CONFIG[\"test_data_path\"] = str(INPUT_DIR / \"train_images\")\n    \n    ss_df = get_train_labels()\n    print(f\"Total row_id counts: {len(ss_df):,}\", end=\" | \")\n    # Ignore NaN portions of train_labels\n    ss_df = ss_df.loc[ss_df.weights == 1.0, ['row_id', 'normal_mild', 'moderate', 'severe']]\n    print(f\"Usable: {len(ss_df):,}\")\nelse:\n    FAKE_TEST = False","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:13.809163Z","iopub.execute_input":"2024-07-19T11:47:13.809435Z","iopub.status.idle":"2024-07-19T11:47:13.821516Z","shell.execute_reply.started":"2024-07-19T11:47:13.809411Z","shell.execute_reply":"2024-07-19T11:47:13.820628Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DepthwiseSeparableConv3D(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):\n        super(DepthwiseSeparableConv3D, self).__init__()\n        self.depthwise = nn.Conv3d(in_channels, in_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=in_channels)\n        self.pointwise = nn.Conv3d(in_channels, out_channels, kernel_size=1)\n\n    def forward(self, x):\n        out = self.depthwise(x)\n        out = self.pointwise(out)\n        return out\n\nclass MobileNetV2_3D(nn.Module):\n    def __init__(self, num_classes):\n        super(MobileNetV2_3D, self).__init__()\n        self.features = nn.Sequential(\n            nn.Conv3d(1, 8, kernel_size=3, stride=1, padding=1),\n            nn.BatchNorm3d(8),\n            nn.ReLU(inplace=True),\n            DepthwiseSeparableConv3D(8, 16, kernel_size=3, stride=2, padding=1),\n            nn.BatchNorm3d(16),\n            nn.ReLU(inplace=True),\n            DepthwiseSeparableConv3D(16, 32, kernel_size=3, stride=2, padding=1),\n            nn.BatchNorm3d(32),\n            nn.ReLU(inplace=True),\n            DepthwiseSeparableConv3D(32, 64, kernel_size=3, stride=2, padding=1),\n            nn.BatchNorm3d(64),\n            nn.ReLU(inplace=True),\n            DepthwiseSeparableConv3D(64, 128, kernel_size=3, stride=2, padding=1),\n            nn.BatchNorm3d(128),\n            nn.ReLU(inplace=True),\n            nn.AdaptiveAvgPool3d((1, 1, 1))\n        )\n        self.classifier = nn.Sequential(\n            nn.Dropout(0.2),\n            nn.Linear(128, num_classes)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = x.view(x.size(0), -1)\n        x = self.classifier(x)\n        return x\n\ndef mobilenetv2_3d(num_classes):\n    return MobileNetV2_3D(num_classes)","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:13.822701Z","iopub.execute_input":"2024-07-19T11:47:13.823648Z","iopub.status.idle":"2024-07-19T11:47:13.835418Z","shell.execute_reply.started":"2024-07-19T11:47:13.823622Z","shell.execute_reply":"2024-07-19T11:47:13.834417Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nsagittal_t2stir_model = mobilenetv2_3d(num_classes=CONFIG[\"num_classes\"][0]).to(device)\nsagittal_t1_model = mobilenetv2_3d(num_classes=CONFIG[\"num_classes\"][1]).to(device)\naxial_t2_model = mobilenetv2_3d(num_classes=CONFIG[\"num_classes\"][1]).to(device)\n## DEBUG: Not doing this step as it is private\n# state1 = torch.load(\"/kaggle/input/models-1/T2_STIR.pt\")\n# state2 = torch.load(\"/kaggle/input/models-1/T1.pt\")\n# state3 = torch.load(\"/kaggle/input/models-1/T2.pt\")\n# sagittal_t2stir_model.load_state_dict(state1['state_dict'], strict=False)\n# sagittal_t1_model.load_state_dict(state2['state_dict'],strict=False)\n# axial_t2_model.load_state_dict(state3['state_dict'],strict=False)","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:13.836572Z","iopub.execute_input":"2024-07-19T11:47:13.836887Z","iopub.status.idle":"2024-07-19T11:47:14.051627Z","shell.execute_reply.started":"2024-07-19T11:47:13.836855Z","shell.execute_reply":"2024-07-19T11:47:14.050630Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dl = pd.read_csv(CONFIG[\"test_data_desc\"])\ndl['path'] = (\n    CONFIG['test_data_path'] + '/' +\n    dl['study_id'].astype(str) + '/' +\n    dl['series_id'].astype(str)\n)","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.052901Z","iopub.execute_input":"2024-07-19T11:47:14.053212Z","iopub.status.idle":"2024-07-19T11:47:14.064317Z","shell.execute_reply.started":"2024-07-19T11:47:14.053180Z","shell.execute_reply":"2024-07-19T11:47:14.063574Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def process_image(img, min_side=416):\n    '''\n    :param img:\n    :param min_side:\n    :return:\n    '''\n    size = img.shape\n    h, w = size[0], size[1]\n    scale = max(w, h) / float(min_side)\n    new_w, new_h = int(w / scale), int(h / scale)\n    resize_img = cv.resize(img, (new_w, new_h))\n    if new_w % 2 != 0 and new_h % 2 == 0:\n        top, bottom, left, right = (min_side - new_h) / 2, (min_side - new_h) / 2, (min_side - new_w) / 2 + 1, (\n                min_side - new_w) / 2\n    elif new_h % 2 != 0 and new_w % 2 == 0:\n        top, bottom, left, right = (min_side - new_h) / 2 + 1, (min_side - new_h) / 2, (min_side - new_w) / 2, (\n                min_side - new_w) / 2\n    elif new_h % 2 == 0 and new_w % 2 == 0:\n        top, bottom, left, right = (min_side - new_h) / 2, (min_side - new_h) / 2, (min_side - new_w) / 2, (\n                min_side - new_w) / 2\n    else:\n        top, bottom, left, right = (min_side - new_h) / 2 + 1, (min_side - new_h) / 2, (min_side - new_w) / 2 + 1, (\n                min_side - new_w) / 2\n    pad_img = cv.copyMakeBorder(resize_img, int(top), int(bottom), int(left), int(right), cv.BORDER_CONSTANT,\n                                value=[0, 0, 0])\n    return pad_img\n\ndef hisEqul(img, k=7, clip_limit=1.0):\n    ycrcb = cv.cvtColor(img, cv.COLOR_RGB2YCR_CB)\n    channels = cv.split(ycrcb)\n    clahe = cv.createCLAHE(clipLimit=clip_limit, tileGridSize=(k, k))\n    clahe.apply(channels[0], channels[0])\n    cv.merge(channels, ycrcb)\n    cv.cvtColor(ycrcb, cv.COLOR_YCR_CB2RGB, img)\n    return img\n\n# 自适应直方图均衡化\nclass HisEqul(DualTransform):\n    def __init__(self, k=7, clip_limit=1.0, always_apply: bool = False, p: float = 0.5):\n        super(HisEqul, self).__init__(always_apply=always_apply, p=p)\n        self.k = k\n        self.clip_limit = clip_limit\n\n    def apply(self, img, **params) -> np.ndarray:\n        return hisEqul(img, self.k, self.clip_limit)\n\ndef resample_volume_to_fixed_slices(volume, target_num_slices):\n    # 获取原始卷的深度（层数）\n    original_num_slices = volume.shape[1]\n\n    # 计算缩放因子\n    zoom_factor = target_num_slices / original_num_slices\n\n    # 使用缩放因子在z轴方向进行重采样\n    zoom_factors = [1,zoom_factor, 1, 1]  # 只在z轴方向进行缩放\n    volume_resampled = zoom(volume, zoom_factors, order=1)\n\n    return volume_resampled\n","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.065681Z","iopub.execute_input":"2024-07-19T11:47:14.065997Z","iopub.status.idle":"2024-07-19T11:47:14.083241Z","shell.execute_reply.started":"2024-07-19T11:47:14.065956Z","shell.execute_reply":"2024-07-19T11:47:14.082402Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class datasets(Dataset):\n    def __init__(self,df, mode=\"train\"):\n        self.mode = mode\n        self.df = df\n        self.transform_test = A.Compose([\n            A.Resize(224, 224),\n            A.Normalize(mean=[0.485,],\n                        std=[0.229,]),\n            ToTensorV2(),\n        ])\n\n    def __getitem__(self, index) -> T_co:\n        image_path = self.df[\"path\"][index]\n        type = self.df[\"series_description\"][index]\n        # 获取所有DICOM文件路径\n        dicom_files = glob.glob(os.path.join(image_path, '*.dcm'))\n\n        # 读取所有DICOM文件\n        dicom_datasets = [pydicom.dcmread(dicom_file) for dicom_file in dicom_files]\n        # 提取每个DICOM文件的像素数据和位置\n        slices = []\n        for dataset in dicom_datasets:\n            # 提取像素数据\n            pixel_array = dataset.pixel_array\n            pixel_array = pixel_array - np.min(pixel_array)\n            if np.max(pixel_array) != 0:\n                pixel_array = pixel_array / np.max(pixel_array)\n            pixel_array = (pixel_array * 255).astype(np.uint8)\n            pixel_array = self.transform_test(image=pixel_array)['image']\n\n            # 提取图像位置（患者）信息\n            image_position = dataset.ImagePositionPatient\n            if type == 'Sagittal T2/STIR' or type == 'Sagittal T1':\n                slices.append((image_position[0], pixel_array))\n            elif type == 'Axial T2':\n                slices.append((image_position[2], pixel_array))\n\n        # 根据切片位置排序\n        slices.sort(key=lambda x: x[0])\n        # 提取排序后的像素数据\n        sorted_pixel_arrays = [slice[1] for slice in slices]\n        # 堆叠成一个3D数组\n        img = np.stack(sorted_pixel_arrays, axis=0)\n        img = img.transpose((1, 0, 2, 3))\n        target_num_slices = 12\n        img = resample_volume_to_fixed_slices(img, target_num_slices)\n        return img\n\n    def __len__(self):\n        return self.df.shape[0]","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.084243Z","iopub.execute_input":"2024-07-19T11:47:14.084495Z","iopub.status.idle":"2024-07-19T11:47:14.096754Z","shell.execute_reply.started":"2024-07-19T11:47:14.084472Z","shell.execute_reply":"2024-07-19T11:47:14.095784Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_data = datasets(dl, mode=\"test\")\ntestLoader = DataLoader(test_data, batch_size=1)","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.099453Z","iopub.execute_input":"2024-07-19T11:47:14.099784Z","iopub.status.idle":"2024-07-19T11:47:14.106754Z","shell.execute_reply.started":"2024-07-19T11:47:14.099754Z","shell.execute_reply":"2024-07-19T11:47:14.105885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"models = {\n     'Sagittal T1': sagittal_t1_model,\n     'Axial T2': axial_t2_model,\n    'Sagittal T2/STIR': sagittal_t2stir_model,\n}\nT1 = {\n    'Sagittal T2/STIR':['spinal_canal_stenosis_l1_l2', 'spinal_canal_stenosis_l2_l3', 'spinal_canal_stenosis_l3_l4',\n           'spinal_canal_stenosis_l4_l5', 'spinal_canal_stenosis_l5_s1'],\n    'Axial T2':['left_subarticular_stenosis_l1_l2', 'left_subarticular_stenosis_l2_l3', 'left_subarticular_stenosis_l3_l4',\n       'left_subarticular_stenosis_l4_l5', 'left_subarticular_stenosis_l5_s1', 'right_subarticular_stenosis_l1_l2',\n       'right_subarticular_stenosis_l2_l3', 'right_subarticular_stenosis_l3_l4', 'right_subarticular_stenosis_l4_l5',\n       'right_subarticular_stenosis_l5_s1'],\n    'Sagittal T1' : ['left_neural_foraminal_narrowing_l1_l2', 'left_neural_foraminal_narrowing_l2_l3', 'left_neural_foraminal_narrowing_l3_l4',\n       'left_neural_foraminal_narrowing_l4_l5', 'left_neural_foraminal_narrowing_l5_s1', 'right_neural_foraminal_narrowing_l1_l2',\n       'right_neural_foraminal_narrowing_l2_l3', 'right_neural_foraminal_narrowing_l3_l4', 'right_neural_foraminal_narrowing_l4_l5',\n       'right_neural_foraminal_narrowing_l5_s1'],\n}","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.107785Z","iopub.execute_input":"2024-07-19T11:47:14.108098Z","iopub.status.idle":"2024-07-19T11:47:14.117636Z","shell.execute_reply.started":"2024-07-19T11:47:14.108064Z","shell.execute_reply":"2024-07-19T11:47:14.116869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"@torch.no_grad()\ndef predict(testloader, df,T1):\n    df2=pd.DataFrame(columns=['row_id', 'normal_mild', 'moderate', 'severe'])\n    for idx, images in enumerate(tqdm(testloader)):\n        images = images.to(device)\n        description = df.iloc[idx]['series_description']\n        study_id = df.iloc[idx]['study_id']\n        model = models.get(description)\n        if model:\n            model.eval()  # Set the model to eval mode\n            outputs = model(images)\n            probs = torch.sigmoid(outputs).squeeze(0)\n            probs = probs.detach().cpu().numpy()\n            probs = np.round(probs, 6)\n            probs = probs.reshape(-1, 3)\n            df_1 = pd.DataFrame(probs, columns=['moderate', 'normal_mild', 'severe'])\n\n            items = T1.get(description)\n            if items is not None:\n                x = [f\"{study_id}_{item}\" for item in items]\n                df1 = pd.DataFrame(x, columns=['row_id'])\n                df1 = pd.concat([df1, df_1], axis=1)\n            if df2.empty:\n                df2 = df1\n            else:\n                # 使用append方法，忽略索引\n                df2 = pd.concat([df2, df1], ignore_index=True)\n    return df2","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.118956Z","iopub.execute_input":"2024-07-19T11:47:14.119251Z","iopub.status.idle":"2024-07-19T11:47:14.129165Z","shell.execute_reply.started":"2024-07-19T11:47:14.119224Z","shell.execute_reply":"2024-07-19T11:47:14.128354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = predict(testLoader, dl, T1).groupby('row_id').mean().reset_index()\n\n# Order columns and row-wise normalization\ncols = ['row_id', 'normal_mild', 'moderate', 'severe']\nsubmission = submission[cols]\nsubmission[cols[1:]] = submission[cols[1:]].div(submission[cols[1:]].sum(axis=1) + 1e-8, axis=0)\nprint(f\"Total row_id counts: {len(submission):,}\")\n\n# Ordering and checks as before\nordered_row_ids = ss_df[[\"row_id\"]]\nsubmission = ordered_row_ids.merge(submission, how=\"left\", on=\"row_id\", validate=\"1:1\")\nsubmission[cols[1:]] = submission[cols[1:]].astype('float64')\nassert submission.row_id.isin(ss_df.row_id).all()\nassert not submission.isna().values.any(), \"Submission DataFrame contains NaN values\"\nassert np.allclose(submission[cols[1:]].sum(axis=1).values, 1.0), \"Not close to 1.0\"\nassert (submission[cols[1:]].values >= 0.0).all(), \"All greater than 0.0\"\n\n# Submit with float_format\nsubmission.to_csv('/kaggle/working/submission.csv', index=False, float_format='%.6f')\nsubmission.head(25)","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:14.130166Z","iopub.execute_input":"2024-07-19T11:47:14.130452Z","iopub.status.idle":"2024-07-19T11:47:17.260114Z","shell.execute_reply.started":"2024-07-19T11:47:14.130428Z","shell.execute_reply":"2024-07-19T11:47:17.259100Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.dtypes","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:17.261485Z","iopub.execute_input":"2024-07-19T11:47:17.261794Z","iopub.status.idle":"2024-07-19T11:47:17.268793Z","shell.execute_reply.started":"2024-07-19T11:47:17.261767Z","shell.execute_reply":"2024-07-19T11:47:17.267870Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Evaluate on metric","metadata":{}},{"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    \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(INPUT_DIR / \"train.csv\")))","metadata":{"execution":{"iopub.status.busy":"2024-07-19T11:47:17.270093Z","iopub.execute_input":"2024-07-19T11:47:17.270429Z","iopub.status.idle":"2024-07-19T11:47:17.296314Z","shell.execute_reply.started":"2024-07-19T11:47:17.270402Z","shell.execute_reply":"2024-07-19T11:47:17.295471Z"},"trusted":true},"execution_count":null,"outputs":[]}]}