{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# # This Python 3 environment comes with many helpful analytics libraries installed\n# # It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# # For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# # You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# # You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# # Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# # Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\n# import kagglehub\n# # kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:37.215404Z","iopub.execute_input":"2026-08-20T11:43:37.21573Z","iopub.status.idle":"2026-08-20T11:43:37.219773Z","shell.execute_reply.started":"2026-08-20T11:43:37.215711Z","shell.execute_reply":"2026-08-20T11:43:37.21912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport pydicom\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.model_selection import GroupKFold\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:37.220978Z","iopub.execute_input":"2026-08-20T11:43:37.221165Z","iopub.status.idle":"2026-08-20T11:43:47.733854Z","shell.execute_reply.started":"2026-08-20T11:43:37.221148Z","shell.execute_reply":"2026-08-20T11:43:47.733179Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/datasets/yuvrajbhatkariya/data-2/new_labeled.csv\")\nprint(df.shape)\ndf.sample(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:47.735201Z","iopub.execute_input":"2026-08-20T11:43:47.73591Z","iopub.status.idle":"2026-08-20T11:43:47.828265Z","shell.execute_reply.started":"2026-08-20T11:43:47.735888Z","shell.execute_reply":"2026-08-20T11:43:47.827606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom pathlib import Path\n\n# ==========================================\n# PATHS\n# ==========================================\nDATA_DIR = Path(\"/kaggle/input/competitions/rsna-knee-abnormality-detection\")\nLABELS_PATH = Path(\"/kaggle/input/datasets/yuvrajbhatkariya/data-2/new_labeled.csv\")\n\n# ==========================================\n# TARGETS\n# ==========================================\nTARGETS = [\n    \"ACL\",\n    \"MCL\",\n    \"Medial Meniscus\",\n    \"Lateral Meniscus\",\n    \"Medial OA\",\n    \"Lateral OA\",\n    \"PF OA\",\n    \"Effusion\",\n    \"Synovitis\",\n    \"Baker's\",\n    \"Contusion\",\n    \"Fracture\"\n]\n\n# ==========================================\n# CURRENT MASKS\n# ==========================================\nMASKS = {\n    \"Sagittal\": [1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1],\n    \"Coronal\":  [0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1],\n    \"Axial\":    [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]\n}\n\nprint(\"Setup complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:47.838545Z","iopub.execute_input":"2026-08-20T11:43:47.838781Z","iopub.status.idle":"2026-08-20T11:43:47.855722Z","shell.execute_reply.started":"2026-08-20T11:43:47.838756Z","shell.execute_reply":"2026-08-20T11:43:47.854832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# LOAD DATA\n# ==========================================\n\nlabels_df = pd.read_csv(LABELS_PATH)\nseries_df = pd.read_csv(DATA_DIR / \"train_series.csv\")\n\nprint(\"Labels shape :\", labels_df.shape)\nprint(\"Series shape :\", series_df.shape)\n\nprint(\"\\nLabels columns:\")\nprint(labels_df.columns.tolist())\n\nprint(\"\\nSeries columns:\")\nprint(series_df.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:47.856517Z","iopub.execute_input":"2026-08-20T11:43:47.856727Z","iopub.status.idle":"2026-08-20T11:43:47.983743Z","shell.execute_reply.started":"2026-08-20T11:43:47.856705Z","shell.execute_reply":"2026-08-20T11:43:47.982985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# MERGE SERIES + LABELS\n# ==========================================\n\nmerged_df = pd.merge(\n    series_df,\n    labels_df,\n    on=\"StudyInstanceUID\",\n    how=\"inner\"\n)\n\nprint(\"Merged shape:\", merged_df.shape)\n\nprint(\"\\nMerged columns:\")\nprint(merged_df.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:47.984624Z","iopub.execute_input":"2026-08-20T11:43:47.984782Z","iopub.status.idle":"2026-08-20T11:43:47.999205Z","shell.execute_reply.started":"2026-08-20T11:43:47.984765Z","shell.execute_reply":"2026-08-20T11:43:47.998496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# CHECK ANATOMICAL PLANES\n# ==========================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"ANATOMICAL PLANE COUNTS\")\nprint(\"=\"*60)\n\nprint(\n    merged_df[\"Anatomical_Plane\"]\n    .value_counts(dropna=False)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:47.999976Z","iopub.execute_input":"2026-08-20T11:43:48.000194Z","iopub.status.idle":"2026-08-20T11:43:48.010341Z","shell.execute_reply.started":"2026-08-20T11:43:48.000171Z","shell.execute_reply":"2026-08-20T11:43:48.009707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# UNIQUE STUDIES PER PLANE\n# ==========================================\n\nunique_studies_by_plane = (\n    merged_df\n    .groupby(\"Anatomical_Plane\")[\"StudyInstanceUID\"]\n    .nunique()\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"UNIQUE STUDIES PER PLANE\")\nprint(\"=\"*60)\n\nprint(unique_studies_by_plane)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.011298Z","iopub.execute_input":"2026-08-20T11:43:48.011589Z","iopub.status.idle":"2026-08-20T11:43:48.031897Z","shell.execute_reply.started":"2026-08-20T11:43:48.011565Z","shell.execute_reply":"2026-08-20T11:43:48.030886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# POSITIVE LABEL COUNTS BY PLANE\n# ==========================================\n\npositive_counts = (\n    merged_df\n    .groupby(\"Anatomical_Plane\")[TARGETS]\n    .sum()\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"POSITIVE LABEL COUNTS BY PLANE\")\nprint(\"=\"*60)\n\npositive_counts","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.033082Z","iopub.execute_input":"2026-08-20T11:43:48.033348Z","iopub.status.idle":"2026-08-20T11:43:48.051921Z","shell.execute_reply.started":"2026-08-20T11:43:48.033322Z","shell.execute_reply":"2026-08-20T11:43:48.051206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# POSITIVE LABEL PERCENTAGE BY PLANE\n# ==========================================\n\npositive_rates = (\n    merged_df\n    .groupby(\"Anatomical_Plane\")[TARGETS]\n    .mean() * 100\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"POSITIVE LABEL RATE (%) BY PLANE\")\nprint(\"=\"*60)\n\npositive_rates.round(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.053816Z","iopub.execute_input":"2026-08-20T11:43:48.054038Z","iopub.status.idle":"2026-08-20T11:43:48.071489Z","shell.execute_reply.started":"2026-08-20T11:43:48.054017Z","shell.execute_reply":"2026-08-20T11:43:48.07062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# CLEAN PLANE COMPARISON\n# ==========================================\n\nplane_comparison = (\n    merged_df\n    .groupby(\"Anatomical_Plane\")[TARGETS]\n    .mean()\n    .mul(100)\n    .round(2)\n    .T\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"ABNORMALITY × PLANE\")\nprint(\"=\"*60)\n\nprint(plane_comparison)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.072509Z","iopub.execute_input":"2026-08-20T11:43:48.072754Z","iopub.status.idle":"2026-08-20T11:43:48.083263Z","shell.execute_reply.started":"2026-08-20T11:43:48.072735Z","shell.execute_reply":"2026-08-20T11:43:48.082546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# CHECK ONE STUDY ACROSS PLANES\n# ==========================================\n\nstudy_id = merged_df[\"StudyInstanceUID\"].iloc[0]\n\none_study = merged_df[\n    merged_df[\"StudyInstanceUID\"] == study_id\n]\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"ONE STUDY ACROSS PLANES\")\nprint(\"=\"*60)\n\nprint(\n    one_study[\n        [\n            \"StudyInstanceUID\",\n            \"SeriesInstanceUID\",\n            \"Anatomical_Plane\"\n        ] + TARGETS\n    ].to_string(index=False)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.084132Z","iopub.execute_input":"2026-08-20T11:43:48.084364Z","iopub.status.idle":"2026-08-20T11:43:48.100582Z","shell.execute_reply.started":"2026-08-20T11:43:48.084343Z","shell.execute_reply":"2026-08-20T11:43:48.099778Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# CHECK WHETHER LABELS ARE IDENTICAL\n# ACROSS PLANES WITHIN EACH STUDY\n# ==========================================\n\ndef check_study_label_consistency(df, targets):\n    inconsistent_studies = []\n\n    for study_id, group in df.groupby(\"StudyInstanceUID\"):\n\n        # Remove duplicate label rows if necessary\n        label_values = group[targets].drop_duplicates()\n\n        # More than one unique label combination?\n        if len(label_values) > 1:\n            inconsistent_studies.append(study_id)\n\n    return inconsistent_studies\n\n\ninconsistent_studies = check_study_label_consistency(\n    merged_df,\n    TARGETS\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"LABEL CONSISTENCY CHECK\")\nprint(\"=\"*60)\n\nprint(\"Total studies:\",\n      merged_df[\"StudyInstanceUID\"].nunique())\n\nprint(\"Studies with different labels across series:\",\n      len(inconsistent_studies))\n\nprint(\n    \"Percentage:\",\n    round(\n        100 * len(inconsistent_studies) /\n        merged_df[\"StudyInstanceUID\"].nunique(),\n        2\n    ),\n    \"%\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:48.101493Z","iopub.execute_input":"2026-08-20T11:43:48.101739Z","iopub.status.idle":"2026-08-20T11:43:52.984923Z","shell.execute_reply.started":"2026-08-20T11:43:48.101713Z","shell.execute_reply":"2026-08-20T11:43:52.984231Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# PLANES AVAILABLE PER STUDY\n# ==========================================\n\nplanes_per_study = (\n    merged_df\n    .groupby(\"StudyInstanceUID\")[\"Anatomical_Plane\"]\n    .apply(lambda x: tuple(sorted(x.unique())))\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"PLANE COMBINATIONS PER STUDY\")\nprint(\"=\"*60)\n\nprint(\n    planes_per_study.value_counts()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:52.985742Z","iopub.execute_input":"2026-08-20T11:43:52.985943Z","iopub.status.idle":"2026-08-20T11:43:53.14333Z","shell.execute_reply.started":"2026-08-20T11:43:52.985919Z","shell.execute_reply":"2026-08-20T11:43:53.142658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# DISPLAY CURRENT MASKS\n# ==========================================\n\nmask_df = pd.DataFrame(\n    MASKS,\n    index=TARGETS\n)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"CURRENT MASKS\")\nprint(\"=\"*60)\n\nprint(mask_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:53.144131Z","iopub.execute_input":"2026-08-20T11:43:53.144544Z","iopub.status.idle":"2026-08-20T11:43:53.150412Z","shell.execute_reply.started":"2026-08-20T11:43:53.144524Z","shell.execute_reply":"2026-08-20T11:43:53.149803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# NUMBER OF ACTIVE TARGETS PER PLANE\n# ==========================================\n\nfor plane, mask in MASKS.items():\n    active_targets = [\n        target\n        for target, value in zip(TARGETS, mask)\n        if value == 1\n    ]\n\n    ignored_targets = [\n        target\n        for target, value in zip(TARGETS, mask)\n        if value == 0\n    ]\n\n    print(\"\\n\" + \"=\"*60)\n    print(plane.upper())\n    print(\"=\"*60)\n\n    print(\"Training on:\")\n    print(active_targets)\n\n    print(\"\\nIgnoring:\")\n    print(ignored_targets)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:53.151276Z","iopub.execute_input":"2026-08-20T11:43:53.15154Z","iopub.status.idle":"2026-08-20T11:43:53.165981Z","shell.execute_reply.started":"2026-08-20T11:43:53.15152Z","shell.execute_reply":"2026-08-20T11:43:53.16533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# MASK + DATA COMPARISON\n# ==========================================\n\nrate_table = (\n    merged_df\n    .groupby(\"Anatomical_Plane\")[TARGETS]\n    .mean()\n    .T\n)\n\ncomparison = rate_table.copy()\n\nfor plane in [\"Sagittal\", \"Coronal\", \"Axial\"]:\n    if plane in comparison.columns:\n        comparison[plane + \"_mask\"] = [\n            MASKS[plane][i]\n            for i in range(len(TARGETS))\n        ]\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"DATA RATE + CURRENT MASK\")\nprint(\"=\"*60)\n\ncomparison.round(4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:53.166906Z","iopub.execute_input":"2026-08-20T11:43:53.16738Z","iopub.status.idle":"2026-08-20T11:43:53.192881Z","shell.execute_reply.started":"2026-08-20T11:43:53.167357Z","shell.execute_reply":"2026-08-20T11:43:53.192244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# ==========================================\n# HEATMAP OF POSITIVE RATE\n# ==========================================\n\nplt.figure(figsize=(10, 8))\n\nplt.imshow(\n    plane_comparison.values,\n    aspect=\"auto\"\n)\n\nplt.xticks(\n    range(len(plane_comparison.columns)),\n    plane_comparison.columns\n)\n\nplt.yticks(\n    range(len(plane_comparison.index)),\n    plane_comparison.index\n)\n\nplt.xlabel(\"Anatomical Plane\")\nplt.ylabel(\"Abnormality\")\nplt.title(\"Positive Label Rate (%) by Anatomical Plane\")\n\nplt.colorbar(label=\"Positive Rate (%)\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T11:43:53.193639Z","iopub.execute_input":"2026-08-20T11:43:53.193854Z","iopub.status.idle":"2026-08-20T11:43:53.428465Z","shell.execute_reply.started":"2026-08-20T11:43:53.193828Z","shell.execute_reply":"2026-08-20T11:43:53.427904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# # ==========================================\n# # 1. Configuration & Hyperparameters\n# # ==========================================\n# DATA_DIR = Path(\"/kaggle/input/competitions/rsna-knee-abnormality-detection\")\n# LABELS_PATH = Path(\"/kaggle/input/datasets/yuvrajbhatkariya/data-2/new_labeled.csv\") \n# DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# TARGETS = [\n#     \"ACL\", \"MCL\", \"Medial Meniscus\", \"Lateral Meniscus\", \n#     \"Medial OA\", \"Lateral OA\", \"PF OA\", \"Effusion\", \n#     \"Synovitis\", \"Baker's\", \"Contusion\", \"Fracture\"\n# ]\n\n# # We recalculated these earlier to handle extreme class imbalance\n# CLASS_WEIGHTS = torch.tensor([\n#     4.06, 2.67, 1.31, 2.37, 5.41, 11.63, \n#     3.52, 1.79, 9.96, 8.66, 16.15, 28.98\n# ], dtype=torch.float32).to(DEVICE)\n\n# # View-Specific Masks (1 = Train, 0 = Ignore)\n# MASKS = {\n#     'Sagittal': [1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1],\n#     'Coronal':  [0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1],\n#     'Axial':    [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]\n# }\n\n# # ==========================================\n# # 2. Data Preparation (Merging Series & Labels)\n# # ==========================================\n# print(\"Loading and preparing data...\")\n# labels_df = pd.read_csv(LABELS_PATH)\n# series_df = pd.read_csv(DATA_DIR / \"train_series.csv\")\n\n# # Merge so we know the plane and the labels for every series\n# merged_df = pd.merge(series_df, labels_df, on=\"StudyInstanceUID\", how=\"inner\")\n\n# # Patient-Level Train/Val Split (Ensures no patient leaks into validation)\n# gkf = GroupKFold(n_splits=5)\n# train_idx, val_idx = next(gkf.split(merged_df, groups=merged_df['StudyInstanceUID']))\n\n# train_data = merged_df.iloc[train_idx].reset_index(drop=True)\n# val_data = merged_df.iloc[val_idx].reset_index(drop=True)\n# print(f\"Train Series: {len(train_data)} | Val Series: {len(val_data)}\")\n\n# # ==========================================\n# # 3. Medical Image Processing & Dataset\n# # ==========================================\n# def apply_mri_normalization(dicom_obj):\n#     img = dicom_obj.pixel_array.astype(np.float32)\n#     p1, p99 = np.percentile(img, 1), np.percentile(img, 99)\n#     img = np.clip(img, p1, p99)\n#     if p99 > p1: img = (img - p1) / (p99 - p1)\n#     else: img = img - p1\n#     return (img * 255.0).astype(np.uint8)\n\n# train_transforms = A.Compose([\n#     A.Resize(256, 256),\n#     A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.05, rotate_limit=15, p=0.5),\n#     A.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.5),\n#     A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n#     ToTensorV2()\n# ])\n\n# val_transforms = A.Compose([\n#     A.Resize(256, 256),\n#     A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n#     ToTensorV2()\n# ])\n\n# class RSNAMultiChunkDataset(Dataset):\n#     def __init__(self, df, data_dir, transforms=None):\n#         self.data = df\n#         self.data_dir = data_dir\n#         self.transforms = transforms\n\n#     def __len__(self): return len(self.data)\n\n#     def _get_chunk(self, dcm_paths, center_idx):\n#         center_idx = max(1, min(center_idx, len(dcm_paths) - 2))\n#         slice_indices = [center_idx - 1, center_idx, center_idx + 1]\n        \n#         channels = []\n#         for i in slice_indices:\n#             try: channels.append(apply_mri_normalization(pydicom.dcmread(dcm_paths[i])))\n#             except: channels.append(None)\n                \n#         valid_imgs = [img for img in channels if img is not None]\n#         if not valid_imgs: return np.zeros((256, 256, 3), dtype=np.uint8)\n            \n#         channels = [img if img is not None else valid_imgs[0] for img in channels]\n#         base_shape = channels[0].shape\n#         for i in range(1, 3):\n#             if channels[i].shape != base_shape:\n#                 channels[i] = cv2.resize(channels[i], (base_shape[1], base_shape[0]))\n#         return np.stack(channels, axis=-1)\n\n#     def __getitem__(self, idx):\n#         row = self.data.iloc[idx]\n#         series_path = self.data_dir / \"train_series\" / str(row['StudyInstanceUID']) / str(row['SeriesInstanceUID'])\n        \n#         dcm_paths = sorted(list(series_path.glob(\"*.dcm\")), key=lambda x: int(x.stem) if x.stem.isdigit() else str(x))\n#         n_slices = len(dcm_paths)\n        \n#         if n_slices >= 3:\n#             anchors = [int(n_slices * 0.25), int(n_slices * 0.50), int(n_slices * 0.75)]\n#             chunks = [self._get_chunk(dcm_paths, a) for a in anchors]\n#         else:\n#             single = np.zeros((256, 256, 3), dtype=np.uint8)\n#             chunks = [single, single, single]\n            \n#         processed = [self.transforms(image=c)[\"image\"] for c in chunks] if self.transforms else chunks\n#         labels = torch.tensor(row[TARGETS].values.astype(np.float32))\n#         return torch.stack(processed, dim=0), labels\n\n# # ==========================================\n# # 4. Architecture & Smoothed Loss\n# # ==========================================\n# class RSNAFeatureAveragingModel(nn.Module):\n#     def __init__(self, backbone='resnet34'):\n#         super().__init__()\n#         self.encoder = timm.create_model(backbone, pretrained=True, num_classes=0)\n#         self.head = nn.Linear(self.encoder.num_features, 12)\n\n#     def forward(self, x):\n#         b, n, c, h, w = x.shape\n#         x = x.view(b * n, c, h, w)\n#         feat = self.encoder(x).view(b, n, -1).mean(dim=1)\n#         return self.head(feat)\n\n# class SmoothedMaskedWeightedBCE(nn.Module):\n#     def __init__(self, pos_weights, view_mask, device, smoothing=0.15):\n#         super().__init__()\n#         self.bce = nn.BCEWithLogitsLoss(pos_weight=pos_weights, reduction='none')\n#         self.view_mask = torch.tensor(view_mask, dtype=torch.float32).to(device)\n#         self.smoothing = smoothing\n\n#     def forward(self, logits, targets):\n#         # 1.0 -> 0.925 | 0.0 -> 0.075\n#         smoothed = targets * (1.0 - self.smoothing) + (self.smoothing / 2.0)\n#         loss = self.bce(logits, smoothed) * self.view_mask\n#         valid = self.view_mask.sum() * logits.size(0)\n#         return loss.sum() / valid if valid > 0 else torch.tensor(0.0, requires_grad=True).to(logits.device)\n\n# # ==========================================\n# # 5. Training Engine\n# # ==========================================\n# def train_specialist_model(plane_name, mask):\n#     print(f\"\\n{'='*40}\\n🚀 TRAINING {plane_name.upper()} SPECIALIST\\n{'='*40}\")\n    \n#     # Filter Data\n#     t_df = train_data[train_data['Anatomical_Plane'] == plane_name].reset_index(drop=True)\n#     v_df = val_data[val_data['Anatomical_Plane'] == plane_name].reset_index(drop=True)\n    \n#     train_loader = DataLoader(RSNAMultiChunkDataset(t_df, DATA_DIR, train_transforms), batch_size=8, shuffle=True, num_workers=2, persistent_workers=True)\n#     val_loader = DataLoader(RSNAMultiChunkDataset(v_df, DATA_DIR, val_transforms), batch_size=8, shuffle=False, num_workers=2, persistent_workers=True)\n    \n#     # Initialize\n#     model = RSNAFeatureAveragingModel(backbone='resnet34').to(DEVICE)\n#     optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\n#     criterion = SmoothedMaskedWeightedBCE(pos_weights=CLASS_WEIGHTS, view_mask=mask, device=DEVICE, smoothing=0.15)\n    \n#     best_auc = 0.0\n#     EPOCHS = 8 # Sweet spot for ResNet34 with this dataset size\n    \n#     for epoch in range(EPOCHS):\n#         model.train()\n#         running_loss = 0.0\n        \n#         for imgs, lbls in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\"):\n#             imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n#             optimizer.zero_grad()\n#             loss = criterion(model(imgs), lbls)\n#             loss.backward()\n#             optimizer.step()\n#             running_loss += loss.item()\n            \n#         model.eval()\n#         preds, targets = [], []\n#         with torch.no_grad():\n#             for imgs, lbls in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Val]\"):\n#                 preds.append(torch.sigmoid(model(imgs.to(DEVICE))).cpu().numpy())\n#                 targets.append(lbls.numpy())\n                \n#         preds, targets = np.vstack(preds), np.vstack(targets)\n        \n#         aucs = []\n#         for i in range(12):\n#             if mask[i] == 1:\n#                 try: aucs.append(roc_auc_score(targets[:, i], preds[:, i]))\n#                 except: pass\n                \n#         macro_auc = np.mean(aucs) if aucs else 0\n#         print(f\"Loss: {running_loss/len(train_loader):.4f} | Valid Macro-AUC: {macro_auc:.4f}\")\n        \n#         if macro_auc > best_auc:\n#             best_auc = macro_auc\n#             torch.save(model.state_dict(), f\"/kaggle/working/best_{plane_name.lower()}_model.pth\")\n#             print(f\"🏆 New Best {plane_name} Model Saved!\")\n\n#     # Cleanup memory before the next model starts\n#     del model, optimizer, train_loader, val_loader\n#     torch.cuda.empty_cache()\n\n# # ==========================================\n# # 6. Execution Queue\n# # ==========================================\n# # Train all three sequentially\n# train_specialist_model('Sagittal', MASKS['Sagittal'])\n# train_specialist_model('Coronal', MASKS['Coronal'])\n# train_specialist_model('Axial', MASKS['Axial'])\n\n# print(\"\\n🎉 ALL SPECIALIST MODELS TRAINED SUCCESSFULLY!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}