{"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\n\nimport numpy as np\nimport pandas as pd\n\n# Check files available in Kaggle input\nimport os\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Check the main input folder\nprint(\"\\nInput folders:\")\nprint(os.listdir(\"/kaggle/input\"))\n\n# KaggleHub\nimport 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-20T07:31:54.57721Z","iopub.execute_input":"2026-08-20T07:31:54.577474Z","execution_failed":"2026-08-20T07:34:30.636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/input\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:05:50.230202Z","iopub.execute_input":"2026-08-20T08:05:50.230501Z","iopub.status.idle":"2026-08-20T08:05:50.234887Z","shell.execute_reply.started":"2026-08-20T08:05:50.230478Z","shell.execute_reply":"2026-08-20T08:05:50.23419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/input/competitions\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:06:07.565068Z","iopub.execute_input":"2026-08-20T08:06:07.565459Z","iopub.status.idle":"2026-08-20T08:06:07.569968Z","shell.execute_reply.started":"2026-08-20T08:06:07.565434Z","shell.execute_reply":"2026-08-20T08:06:07.569378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\nDATA_PATH = \"/kaggle/input/competitions/rsna-knee-abnormality-detection\"\n\ntrain = pd.read_csv(DATA_PATH + \"/train.csv\")\n\nprint(\"Train shape:\", train.shape)\nprint()\nprint(\"Columns:\")\nprint(train.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:06:23.929856Z","iopub.execute_input":"2026-08-20T08:06:23.930166Z","iopub.status.idle":"2026-08-20T08:06:24.014761Z","shell.execute_reply.started":"2026-08-20T08:06:23.930126Z","shell.execute_reply":"2026-08-20T08:06:24.01383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train.iloc[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:06:50.379791Z","iopub.execute_input":"2026-08-20T08:06:50.3801Z","iopub.status.idle":"2026-08-20T08:06:50.385102Z","shell.execute_reply.started":"2026-08-20T08:06:50.380078Z","shell.execute_reply":"2026-08-20T08:06:50.384508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = [\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\nprint(train[labels].notna().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:07:30.160449Z","iopub.execute_input":"2026-08-20T08:07:30.160711Z","iopub.status.idle":"2026-08-20T08:07:30.18142Z","shell.execute_reply.started":"2026-08-20T08:07:30.160691Z","shell.execute_reply":"2026-08-20T08:07:30.180572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labeled = train[train[labels].notna().all(axis=1)]\n\nprint(\"Fully labeled studies:\", len(labeled))\n\ndisplay(labeled[[\"StudyInstanceUID\"] + labels].head(10))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:08:00.215028Z","iopub.execute_input":"2026-08-20T08:08:00.215336Z","iopub.status.idle":"2026-08-20T08:08:00.249619Z","shell.execute_reply.started":"2026-08-20T08:08:00.215314Z","shell.execute_reply":"2026-08-20T08:08:00.248901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"series = pd.read_csv(DATA_PATH + \"/train_series.csv\")\n\nprint(\"Train series shape:\", series.shape)\nprint()\nprint(\"Columns:\")\nprint(series.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:08:29.710476Z","iopub.execute_input":"2026-08-20T08:08:29.710724Z","iopub.status.idle":"2026-08-20T08:08:29.786579Z","shell.execute_reply.started":"2026-08-20T08:08:29.710704Z","shell.execute_reply":"2026-08-20T08:08:29.785811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"study_id = labeled.iloc[0][\"StudyInstanceUID\"]\n\nprint(\"Study ID:\")\nprint(study_id)\n\nprint(\"\\nMRI series belonging to this study:\")\n\ndisplay(\n    series[series[\"StudyInstanceUID\"] == study_id]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:08:57.164915Z","iopub.execute_input":"2026-08-20T08:08:57.165313Z","iopub.status.idle":"2026-08-20T08:08:57.176493Z","shell.execute_reply.started":"2026-08-20T08:08:57.165287Z","shell.execute_reply":"2026-08-20T08:08:57.175796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nstudy_id = labeled.iloc[0][\"StudyInstanceUID\"]\n\nstudy_path = os.path.join(\n    DATA_PATH,\n    \"train_series\",\n    study_id\n)\n\nprint(\"Study folder:\")\nprint(study_path)\n\nprint(\"\\nSeries folders:\")\nprint(os.listdir(study_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:09:23.090737Z","iopub.execute_input":"2026-08-20T08:09:23.091092Z","iopub.status.idle":"2026-08-20T08:09:23.103268Z","shell.execute_reply.started":"2026-08-20T08:09:23.091073Z","shell.execute_reply":"2026-08-20T08:09:23.102425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"series_id = series[series[\"StudyInstanceUID\"] == study_id].iloc[0][\"SeriesInstanceUID\"]\n\nseries_path = os.path.join(\n    DATA_PATH,\n    \"train_series\",\n    study_id,\n    series_id\n)\n\nfiles = os.listdir(series_path)\n\nprint(\"Series ID:\")\nprint(series_id)\n\nprint(\"\\nNumber of files:\", len(files))\n\nprint(\"\\nFirst 5 files:\")\nprint(files[:5])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:09:49.155014Z","iopub.execute_input":"2026-08-20T08:09:49.155312Z","iopub.status.idle":"2026-08-20T08:09:49.168603Z","shell.execute_reply.started":"2026-08-20T08:09:49.155291Z","shell.execute_reply":"2026-08-20T08:09:49.16775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pydicom\nimport matplotlib.pyplot as plt\n\n# Get the first DICOM file\ndcm_file = os.path.join(series_path, files[0])\n\n# Read DICOM\ndicom_image = pydicom.dcmread(dcm_file)\n\n# Get pixel data\nimage = dicom_image.pixel_array\n\nprint(\"Image shape:\", image.shape)\nprint(\"Data type:\", image.dtype)\n\n# Display image\nplt.figure(figsize=(6, 6))\nplt.imshow(image, cmap=\"gray\")\nplt.axis(\"off\")\nplt.title(\"Knee MRI - First Slice\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:10:19.805088Z","iopub.execute_input":"2026-08-20T08:10:19.805454Z","iopub.status.idle":"2026-08-20T08:10:20.665123Z","shell.execute_reply.started":"2026-08-20T08:10:19.805432Z","shell.execute_reply":"2026-08-20T08:10:20.664466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport pydicom\nimport os\n\nimages = []\n\nfor file in files:\n    path = os.path.join(series_path, file)\n    ds = pydicom.dcmread(path)\n    images.append(ds.pixel_array)\n\nprint(\"Number of slices:\", len(images))\nprint(\"First image shape:\", images[0].shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:12:31.491128Z","iopub.execute_input":"2026-08-20T08:12:31.491742Z","iopub.status.idle":"2026-08-20T08:12:31.980919Z","shell.execute_reply.started":"2026-08-20T08:12:31.491713Z","shell.execute_reply":"2026-08-20T08:12:31.980247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nindices = [0, 7, 14, 21, 28, 35]\n\nplt.figure(figsize=(15, 8))\n\nfor i, index in enumerate(indices):\n    plt.subplot(2, 3, i + 1)\n    plt.imshow(images[index], cmap=\"gray\")\n    plt.title(f\"Slice {index + 1}\")\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:13:20.168941Z","iopub.execute_input":"2026-08-20T08:13:20.169181Z","iopub.status.idle":"2026-08-20T08:13:20.800658Z","shell.execute_reply.started":"2026-08-20T08:13:20.169162Z","shell.execute_reply":"2026-08-20T08:13:20.799854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Total reports:\", train[\"Report\"].notna().sum())\nprint(\"Missing reports:\", train[\"Report\"].isna().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:14:25.899351Z","iopub.execute_input":"2026-08-20T08:14:25.899607Z","iopub.status.idle":"2026-08-20T08:14:25.905867Z","shell.execute_reply.started":"2026-08-20T08:14:25.899586Z","shell.execute_reply":"2026-08-20T08:14:25.904962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train[\"Report\"].dropna().iloc[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:15:50.045051Z","iopub.execute_input":"2026-08-20T08:15:50.045355Z","iopub.status.idle":"2026-08-20T08:15:50.051737Z","shell.execute_reply.started":"2026-08-20T08:15:50.045334Z","shell.execute_reply":"2026-08-20T08:15:50.050555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in range(3):\n    row = labeled.iloc[i]\n\n    print(\"=\" * 80)\n    print(\"Study:\", row[\"StudyInstanceUID\"])\n    print(\"\\nReport:\")\n    print(row[\"Report\"])\n\n    print(\"\\nOfficial labels:\")\n    print(row[labels].to_dict())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:16:24.950627Z","iopub.execute_input":"2026-08-20T08:16:24.950902Z","iopub.status.idle":"2026-08-20T08:16:24.957375Z","shell.execute_reply.started":"2026-08-20T08:16:24.950881Z","shell.execute_reply":"2026-08-20T08:16:24.956651Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Positive cases in the 58 labeled studies:\")\nprint()\n\nfor label in labels:\n    positives = labeled[label].sum()\n    negatives = len(labeled) - positives\n\n    print(f\"{label:20} Positive: {int(positives):2}   Negative: {int(negatives):2}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:17:10.774767Z","iopub.execute_input":"2026-08-20T08:17:10.775046Z","iopub.status.idle":"2026-08-20T08:17:10.780261Z","shell.execute_reply.started":"2026-08-20T08:17:10.775023Z","shell.execute_reply":"2026-08-20T08:17:10.779535Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"series_count = (\n    series[series[\"StudyInstanceUID\"].isin(labeled[\"StudyInstanceUID\"])]\n    .groupby(\"StudyInstanceUID\")\n    .size()\n)\n\nprint(\"Number of labeled studies:\", len(series_count))\nprint(\"\\nSeries count distribution:\")\nprint(series_count.value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:17:51.877844Z","iopub.execute_input":"2026-08-20T08:17:51.878233Z","iopub.status.idle":"2026-08-20T08:17:51.898751Z","shell.execute_reply.started":"2026-08-20T08:17:51.878189Z","shell.execute_reply":"2026-08-20T08:17:51.897688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(series[\"Anatomical_Plane\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:18:31.87469Z","iopub.execute_input":"2026-08-20T08:18:31.874957Z","iopub.status.idle":"2026-08-20T08:18:31.880698Z","shell.execute_reply.started":"2026-08-20T08:18:31.874935Z","shell.execute_reply":"2026-08-20T08:18:31.879997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(series[\"Fluid_Sensitive\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:18:44.379846Z","iopub.execute_input":"2026-08-20T08:18:44.380118Z","iopub.status.idle":"2026-08-20T08:18:44.385367Z","shell.execute_reply.started":"2026-08-20T08:18:44.380098Z","shell.execute_reply":"2026-08-20T08:18:44.384596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\n    series.groupby(\n        [\"Anatomical_Plane\", \"Fluid_Sensitive\", \"Fat_Suppression\"]\n    ).size()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:20:26.884292Z","iopub.execute_input":"2026-08-20T08:20:26.884738Z","iopub.status.idle":"2026-08-20T08:20:26.897634Z","shell.execute_reply.started":"2026-08-20T08:20:26.884716Z","shell.execute_reply":"2026-08-20T08:20:26.896746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torchvision\nimport pydicom\n\nprint(\"PyTorch:\", torch.__version__)\nprint(\"Torchvision:\", torchvision.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:20:51.458667Z","iopub.execute_input":"2026-08-20T08:20:51.458966Z","iopub.status.idle":"2026-08-20T08:20:57.782509Z","shell.execute_reply.started":"2026-08-20T08:20:51.458934Z","shell.execute_reply":"2026-08-20T08:20:57.781561Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\nprint(\"PyTorch:\", torch.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())\n\nif torch.cuda.is_available():\n    print(\"GPU:\", torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:23:10.200377Z","iopub.execute_input":"2026-08-20T08:23:10.20055Z","iopub.status.idle":"2026-08-20T08:23:14.807339Z","shell.execute_reply.started":"2026-08-20T08:23:10.200529Z","shell.execute_reply":"2026-08-20T08:23:14.806498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Keep only the 58 fully labeled studies\nlabeled = train[train[labels].notna().all(axis=1)].copy()\n\nprint(\"Number of labeled studies:\", len(labeled))\nprint(\"Missing labels:\", labeled[labels].isna().sum().sum())\n\ndisplay(labeled[[\"StudyInstanceUID\"] + labels].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:23:52.208521Z","iopub.execute_input":"2026-08-20T08:23:52.209446Z","iopub.status.idle":"2026-08-20T08:23:52.217041Z","shell.execute_reply.started":"2026-08-20T08:23:52.209409Z","shell.execute_reply":"2026-08-20T08:23:52.216165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# Dataset location\nDATA_PATH = \"/kaggle/input/competitions/rsna-knee-abnormality-detection\"\n\n# Load training data\ntrain = pd.read_csv(DATA_PATH + \"/train.csv\")\n\n# The 12 competition labels\nlabels = [\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# Keep only fully labeled studies\nlabeled = train[train[labels].notna().all(axis=1)].copy()\n\nprint(\"Training studies:\", len(train))\nprint(\"Fully labeled studies:\", len(labeled))\nprint(\"Missing labels:\", labeled[labels].isna().sum().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:24:17.917741Z","iopub.execute_input":"2026-08-20T08:24:17.918194Z","iopub.status.idle":"2026-08-20T08:24:18.335823Z","shell.execute_reply.started":"2026-08-20T08:24:17.918164Z","shell.execute_reply":"2026-08-20T08:24:18.335161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ntrain_df, val_df = train_test_split(\n    labeled,\n    test_size=0.2,\n    random_state=42\n)\n\nprint(\"Training studies:\", len(train_df))\nprint(\"Validation studies:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:24:56.308522Z","iopub.execute_input":"2026-08-20T08:24:56.308968Z","iopub.status.idle":"2026-08-20T08:24:57.132494Z","shell.execute_reply.started":"2026-08-20T08:24:56.308939Z","shell.execute_reply":"2026-08-20T08:24:57.131811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ndef get_study_path(study_id):\n    return os.path.join(\n        DATA_PATH,\n        \"train_series\",\n        study_id\n    )\n\ntrain_df[\"study_path\"] = train_df[\"StudyInstanceUID\"].apply(get_study_path)\nval_df[\"study_path\"] = val_df[\"StudyInstanceUID\"].apply(get_study_path)\n\nprint(\"Training MRI folders:\")\nprint(train_df[\"study_path\"].head(3).to_string(index=False))\n\nprint(\"\\nValidation MRI folders:\")\nprint(val_df[\"study_path\"].head(3).to_string(index=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:25:23.939171Z","iopub.execute_input":"2026-08-20T08:25:23.939576Z","iopub.status.idle":"2026-08-20T08:25:23.9477Z","shell.execute_reply.started":"2026-08-20T08:25:23.939549Z","shell.execute_reply":"2026-08-20T08:25:23.94689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"series = pd.read_csv(DATA_PATH + \"/train_series.csv\")\n\nprint(\"Series loaded:\", len(series))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:25:44.734277Z","iopub.execute_input":"2026-08-20T08:25:44.735003Z","iopub.status.idle":"2026-08-20T08:25:44.822985Z","shell.execute_reply.started":"2026-08-20T08:25:44.734975Z","shell.execute_reply":"2026-08-20T08:25:44.822336Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def choose_series(study_id):\n    study_series = series[series[\"StudyInstanceUID\"] == study_id]\n\n    # Prefer fluid-sensitive sagittal series\n    preferred = study_series[\n        (study_series[\"Fluid_Sensitive\"] == 1) &\n        (study_series[\"Anatomical_Plane\"] == \"Sagittal\")\n    ]\n\n    if len(preferred) > 0:\n        return preferred.iloc[0][\"SeriesInstanceUID\"]\n\n    # If unavailable, use any fluid-sensitive series\n    fluid = study_series[study_series[\"Fluid_Sensitive\"] == 1]\n\n    if len(fluid) > 0:\n        return fluid.iloc[0][\"SeriesInstanceUID\"]\n\n    # Final fallback: first available series\n    return study_series.iloc[0][\"SeriesInstanceUID\"]\n\n\ntrain_df[\"selected_series\"] = train_df[\"StudyInstanceUID\"].apply(choose_series)\nval_df[\"selected_series\"] = val_df[\"StudyInstanceUID\"].apply(choose_series)\n\nprint(\"Training examples:\", len(train_df))\nprint(\"Validation examples:\", len(val_df))\n\ndisplay(\n    train_df[\n        [\"StudyInstanceUID\", \"selected_series\"]\n    ].head()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:26:13.779618Z","iopub.execute_input":"2026-08-20T08:26:13.780108Z","iopub.status.idle":"2026-08-20T08:26:13.952151Z","shell.execute_reply.started":"2026-08-20T08:26:13.780077Z","shell.execute_reply":"2026-08-20T08:26:13.951333Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def count_slices(row):\n    path = os.path.join(\n        DATA_PATH,\n        \"train_series\",\n        row[\"StudyInstanceUID\"],\n        row[\"selected_series\"]\n    )\n    return len([\n        f for f in os.listdir(path)\n        if f.endswith(\".dcm\")\n    ])\n\n\ntrain_df[\"num_slices\"] = train_df.apply(count_slices, axis=1)\nval_df[\"num_slices\"] = val_df.apply(count_slices, axis=1)\n\nprint(\"Training slice counts:\")\nprint(train_df[\"num_slices\"].describe())\n\nprint(\"\\nValidation slice counts:\")\nprint(val_df[\"num_slices\"].describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:26:43.988812Z","iopub.execute_input":"2026-08-20T08:26:43.989126Z","iopub.status.idle":"2026-08-20T08:26:44.585724Z","shell.execute_reply.started":"2026-08-20T08:26:43.989102Z","shell.execute_reply":"2026-08-20T08:26:44.585086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport os\nimport pydicom\n\nNUM_SLICES = 16\n\ndef load_selected_slices(row):\n    series_path = os.path.join(\n        DATA_PATH,\n        \"train_series\",\n        row[\"StudyInstanceUID\"],\n        row[\"selected_series\"]\n    )\n\n    dcm_files = [\n        f for f in os.listdir(series_path)\n        if f.endswith(\".dcm\")\n    ]\n\n    # Sort files by filename for a consistent starting order\n    dcm_files = sorted(dcm_files)\n\n    # Select 16 evenly spaced slices\n    indices = np.linspace(\n        0,\n        len(dcm_files) - 1,\n        NUM_SLICES\n    ).astype(int)\n\n    selected_files = [dcm_files[i] for i in indices]\n\n    images = []\n\n    for filename in selected_files:\n        path = os.path.join(series_path, filename)\n        ds = pydicom.dcmread(path)\n        image = ds.pixel_array.astype(np.float32)\n\n        # Normalize each slice to 0-1\n        image_min = image.min()\n        image_max = image.max()\n\n        if image_max > image_min:\n            image = (image - image_min) / (image_max - image_min)\n\n        images.append(image)\n\n    return np.stack(images)\n\n\n# Test on ONE training study\nsample_images = load_selected_slices(train_df.iloc[0])\n\nprint(\"Selected slices:\", sample_images.shape)\nprint(\"Minimum:\", sample_images.min())\nprint(\"Maximum:\", sample_images.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:27:18.114572Z","iopub.execute_input":"2026-08-20T08:27:18.115109Z","iopub.status.idle":"2026-08-20T08:27:19.066212Z","shell.execute_reply.started":"2026-08-20T08:27:18.115078Z","shell.execute_reply":"2026-08-20T08:27:19.065531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport numpy as np\n\ndef resize_slices(images, size=(224, 224)):\n    resized = []\n\n    for image in images:\n        image_uint8 = (image * 255).astype(np.uint8)\n\n        pil_image = Image.fromarray(image_uint8)\n        pil_image = pil_image.resize(size)\n\n        resized_image = np.array(pil_image).astype(np.float32) / 255.0\n        resized.append(resized_image)\n\n    return np.stack(resized)\n\n\n# Resize our 16 test slices\nsample_resized = resize_slices(sample_images)\n\nprint(\"Before resizing:\", sample_images.shape)\nprint(\"After resizing:\", sample_resized.shape)\nprint(\"Minimum:\", sample_resized.min())\nprint(\"Maximum:\", sample_resized.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:27:43.409512Z","iopub.execute_input":"2026-08-20T08:27:43.409778Z","iopub.status.idle":"2026-08-20T08:27:43.464089Z","shell.execute_reply.started":"2026-08-20T08:27:43.409756Z","shell.execute_reply":"2026-08-20T08:27:43.463259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\n# Convert grayscale MRI to 3 channels\nsample_rgb = np.stack(\n    [sample_resized, sample_resized, sample_resized],\n    axis=1\n)\n\nprint(\"Shape before PyTorch:\", sample_rgb.shape)\n\n# Convert to PyTorch tensor\nsample_tensor = torch.tensor(sample_rgb, dtype=torch.float32)\n\nprint(\"PyTorch tensor shape:\", sample_tensor.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:28:09.034133Z","iopub.execute_input":"2026-08-20T08:28:09.034695Z","iopub.status.idle":"2026-08-20T08:28:09.065662Z","shell.execute_reply.started":"2026-08-20T08:28:09.034664Z","shell.execute_reply":"2026-08-20T08:28:09.065038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torchvision import models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(\"Using device:\", device)\n\nmodel = models.resnet18(\n    weights=models.ResNet18_Weights.DEFAULT\n)\n\nmodel = model.to(device)\nmodel.eval()\n\nprint(\"ResNet18 loaded successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:30:26.034598Z","iopub.execute_input":"2026-08-20T08:30:26.035343Z","iopub.status.idle":"2026-08-20T08:30:30.288073Z","shell.execute_reply.started":"2026-08-20T08:30:26.035309Z","shell.execute_reply":"2026-08-20T08:30:30.287326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\n\n# Replace the final ImageNet layer\nmodel.fc = nn.Linear(\n    model.fc.in_features,\n    len(labels)\n)\n\nmodel = model.to(device)\n\nprint(\"Number of outputs:\", model.fc.out_features)\nprint(\"Output labels:\")\nprint(labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:31:03.900112Z","iopub.execute_input":"2026-08-20T08:31:03.900763Z","iopub.status.idle":"2026-08-20T08:31:03.908153Z","shell.execute_reply.started":"2026-08-20T08:31:03.900727Z","shell.execute_reply":"2026-08-20T08:31:03.907398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Move sample image tensor to the GPU\nsample_tensor = sample_tensor.to(device)\n\n# Run the model\nwith torch.no_grad():\n    output = model(sample_tensor)\n\nprint(\"Input shape:\", sample_tensor.shape)\nprint(\"Output shape:\", output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:31:29.060445Z","iopub.execute_input":"2026-08-20T08:31:29.061227Z","iopub.status.idle":"2026-08-20T08:31:29.747031Z","shell.execute_reply.started":"2026-08-20T08:31:29.061197Z","shell.execute_reply":"2026-08-20T08:31:29.746003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert slice predictions into probabilities\nprobabilities = torch.sigmoid(output)\n\n# Average the 16 slice predictions\nstudy_prediction = probabilities.mean(dim=0)\n\nprint(\"Slice predictions:\", probabilities.shape)\nprint(\"Study prediction:\", study_prediction.shape)\n\nprint(\"\\n12 study probabilities:\")\nfor label, probability in zip(labels, study_prediction):\n    print(f\"{label:20}: {probability.item():.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:39:43.590476Z","iopub.execute_input":"2026-08-20T08:39:43.59122Z","iopub.status.idle":"2026-08-20T08:39:43.619143Z","shell.execute_reply.started":"2026-08-20T08:39:43.59119Z","shell.execute_reply":"2026-08-20T08:39:43.618249Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Freeze the pretrained ResNet18 layers\nfor param in model.parameters():\n    param.requires_grad = False\n\n# Unfreeze only the final 12-output layer\nfor param in model.fc.parameters():\n    param.requires_grad = True\n\ntrainable_params = sum(\n    p.numel()\n    for p in model.parameters()\n    if p.requires_grad\n)\n\ntotal_params = sum(\n    p.numel()\n    for p in model.parameters()\n)\n\nprint(\"Total parameters:\", total_params)\nprint(\"Trainable parameters:\", trainable_params)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:41:56.112152Z","iopub.execute_input":"2026-08-20T08:41:56.112549Z","iopub.status.idle":"2026-08-20T08:41:56.118918Z","shell.execute_reply.started":"2026-08-20T08:41:56.11252Z","shell.execute_reply":"2026-08-20T08:41:56.118044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport numpy as np\nimport torch\nimport pydicom\nimport os\n\n\nclass KneeMRIDataset(Dataset):\n\n    def __init__(self, dataframe, num_slices=16):\n        self.df = dataframe.reset_index(drop=True)\n        self.num_slices = num_slices\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, index):\n\n        row = self.df.iloc[index]\n\n        study_id = row[\"StudyInstanceUID\"]\n        series_id = row[\"selected_series\"]\n\n        series_path = os.path.join(\n            DATA_PATH,\n            \"train_series\",\n            study_id,\n            series_id\n        )\n\n        # Find DICOM files\n        dcm_files = sorted([\n            f for f in os.listdir(series_path)\n            if f.endswith(\".dcm\")\n        ])\n\n        # Select evenly spaced slices\n        indices = np.linspace(\n            0,\n            len(dcm_files) - 1,\n            self.num_slices\n        ).astype(int)\n\n        images = []\n\n        for i in indices:\n\n            dcm_path = os.path.join(\n                series_path,\n                dcm_files[i]\n            )\n\n            ds = pydicom.dcmread(dcm_path)\n\n            image = ds.pixel_array.astype(np.float32)\n\n            # Normalize to 0-1\n            image_min = image.min()\n            image_max = image.max()\n\n            if image_max > image_min:\n                image = (\n                    image - image_min\n                ) / (\n                    image_max - image_min\n                )\n\n            # Convert to uint8\n            image = (image * 255).astype(np.uint8)\n\n            # Resize\n            image = Image.fromarray(image)\n            image = image.resize((224, 224))\n\n            image = np.array(image).astype(np.float32) / 255.0\n\n            # Grayscale → 3 channels\n            image = np.stack(\n                [image, image, image],\n                axis=0\n            )\n\n            images.append(image)\n\n        # Shape:\n        # 16 × 3 × 224 × 224\n        images = np.stack(images)\n\n        images = torch.tensor(\n            images,\n            dtype=torch.float32\n        )\n\n        # Get 12 labels\n        target = torch.tensor(\n            row[labels].values.astype(np.float32)\n        )\n\n        return images, target\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:47:14.092462Z","iopub.execute_input":"2026-08-20T08:47:14.093221Z","iopub.status.idle":"2026-08-20T08:47:14.102441Z","shell.execute_reply.started":"2026-08-20T08:47:14.09319Z","shell.execute_reply":"2026-08-20T08:47:14.101672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Notebook is working\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:48:09.012531Z","iopub.execute_input":"2026-08-20T08:48:09.013324Z","iopub.status.idle":"2026-08-20T08:48:09.01735Z","shell.execute_reply.started":"2026-08-20T08:48:09.013291Z","shell.execute_reply":"2026-08-20T08:48:09.016733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport numpy as np\nimport torch\nimport pydicom\nimport os\n\n\nclass KneeMRIDataset(Dataset):\n\n    def __init__(self, dataframe, num_slices=16):\n        self.df = dataframe.reset_index(drop=True)\n        self.num_slices = num_slices\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, index):\n\n        row = self.df.iloc[index]\n\n        study_id = row[\"StudyInstanceUID\"]\n        series_id = row[\"selected_series\"]\n\n        series_path = os.path.join(\n            DATA_PATH,\n            \"train_series\",\n            study_id,\n            series_id\n        )\n\n        # Find DICOM files\n        dcm_files = sorted([\n            f for f in os.listdir(series_path)\n            if f.endswith(\".dcm\")\n        ])\n\n        # Select evenly spaced slices\n        indices = np.linspace(\n            0,\n            len(dcm_files) - 1,\n            self.num_slices\n        ).astype(int)\n\n        images = []\n\n        for i in indices:\n\n            dcm_path = os.path.join(\n                series_path,\n                dcm_files[i]\n            )\n\n            ds = pydicom.dcmread(dcm_path)\n\n            image = ds.pixel_array.astype(np.float32)\n\n            # Normalize to 0-1\n            image_min = image.min()\n            image_max = image.max()\n\n            if image_max > image_min:\n                image = (\n                    image - image_min\n                ) / (\n                    image_max - image_min\n                )\n\n            # Convert to uint8\n            image = (image * 255).astype(np.uint8)\n\n            # Resize\n            image = Image.fromarray(image)\n            image = image.resize((224, 224))\n\n            image = np.array(image).astype(np.float32) / 255.0\n\n            # Grayscale → 3 channels\n            image = np.stack(\n                [image, image, image],\n                axis=0\n            )\n\n            images.append(image)\n\n        # Shape:\n        # 16 × 3 × 224 × 224\n        images = np.stack(images)\n\n        images = torch.tensor(\n            images,\n            dtype=torch.float32\n        )\n\n        # Get 12 labels\n        target = torch.tensor(\n            row[labels].values.astype(np.float32)\n        )\n\n        return images, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:48:24.038661Z","iopub.execute_input":"2026-08-20T08:48:24.039085Z","iopub.status.idle":"2026-08-20T08:48:24.048574Z","shell.execute_reply.started":"2026-08-20T08:48:24.039056Z","shell.execute_reply":"2026-08-20T08:48:24.047701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = KneeMRIDataset(train_df)\n\nimages, target = test_dataset[0]\n\nprint(\"Images shape:\", images.shape)\nprint(\"Target shape:\", target.shape)\nprint(\"Target labels:\", target)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:48:33.167254Z","iopub.execute_input":"2026-08-20T08:48:33.167507Z","iopub.status.idle":"2026-08-20T08:48:33.317318Z","shell.execute_reply.started":"2026-08-20T08:48:33.167485Z","shell.execute_reply":"2026-08-20T08:48:33.316632Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntrain_dataset = KneeMRIDataset(train_df, num_slices=16)\nval_dataset = KneeMRIDataset(val_df, num_slices=16)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=2,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=2,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"Training studies:\", len(train_dataset))\nprint(\"Validation studies:\", len(val_dataset))\nprint(\"Training batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:48:56.847119Z","iopub.execute_input":"2026-08-20T08:48:56.847399Z","iopub.status.idle":"2026-08-20T08:48:56.854707Z","shell.execute_reply.started":"2026-08-20T08:48:56.847374Z","shell.execute_reply":"2026-08-20T08:48:56.854064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\n\ncriterion = nn.BCEWithLogitsLoss()\n\noptimizer = torch.optim.Adam(\n    model.fc.parameters(),\n    lr=1e-3\n)\n\ndef forward_study(images):\n    \"\"\"\n    images shape:\n    batch × 16 × 3 × 224 × 224\n\n    returns:\n    batch × 12\n    \"\"\"\n\n    batch_size = images.size(0)\n    num_slices = images.size(1)\n\n    # Combine batch and slice dimensions\n    images = images.view(\n        batch_size * num_slices,\n        3,\n        224,\n        224\n    )\n\n    # ResNet predictions for each slice\n    outputs = model(images)\n\n    # Restore study and slice dimensions\n    outputs = outputs.view(\n        batch_size,\n        num_slices,\n        len(labels)\n    )\n\n    # Average slice predictions\n    outputs = outputs.mean(dim=1)\n\n    return outputs\n\n\nprint(\"Training function ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:49:38.912288Z","iopub.execute_input":"2026-08-20T08:49:38.913091Z","iopub.status.idle":"2026-08-20T08:49:38.919225Z","shell.execute_reply.started":"2026-08-20T08:49:38.913059Z","shell.execute_reply":"2026-08-20T08:49:38.918501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get one batch\nimages, targets = next(iter(train_loader))\n\nprint(\"Original batch shape:\", images.shape)\nprint(\"Target shape:\", targets.shape)\n\n# Move images and targets to GPU\nimages = images.to(device)\ntargets = targets.to(device)\n\n# Run one forward pass\noutputs = forward_study(images)\n\nprint(\"Output shape:\", outputs.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:50:07.907997Z","iopub.execute_input":"2026-08-20T08:50:07.908383Z","iopub.status.idle":"2026-08-20T08:50:09.18574Z","shell.execute_reply.started":"2026-08-20T08:50:07.908354Z","shell.execute_reply":"2026-08-20T08:50:09.184933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\n\nfor epoch in range(EPOCHS):\n\n    # -------------------------\n    # Training\n    # -------------------------\n    model.train()\n\n    # Keep the ResNet feature extractor frozen\n    for param in model.parameters():\n        param.requires_grad = False\n\n    for param in model.fc.parameters():\n        param.requires_grad = True\n\n    train_loss = 0.0\n\n    for images, targets in train_loader:\n\n        images = images.to(device, non_blocking=True)\n        targets = targets.to(device, non_blocking=True)\n\n        optimizer.zero_grad()\n\n        outputs = forward_study(images)\n\n        loss = criterion(outputs, targets)\n\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    train_loss /= len(train_loader)\n\n    # -------------------------\n    # Validation\n    # -------------------------\n    model.eval()\n\n    val_loss = 0.0\n\n    with torch.no_grad():\n\n        for images, targets in val_loader:\n\n            images = images.to(device, non_blocking=True)\n            targets = targets.to(device, non_blocking=True)\n\n            outputs = forward_study(images)\n\n            loss = criterion(outputs, targets)\n\n            val_loss += loss.item()\n\n    val_loss /= len(val_loader)\n\n    print(\n        f\"Epoch {epoch + 1}/{EPOCHS} \"\n        f\"| Train Loss: {train_loss:.4f} \"\n        f\"| Val Loss: {val_loss:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T08:50:33.423432Z","iopub.execute_input":"2026-08-20T08:50:33.423948Z","iopub.status.idle":"2026-08-20T08:50:54.086895Z","shell.execute_reply.started":"2026-08-20T08:50:33.423911Z","shell.execute_reply":"2026-08-20T08:50:54.086141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}