{"cells":[{"cell_type":"markdown","metadata":{},"source":"# RSNA Knee Abnormality Detection — Boilerplate\n\nMulti-label classification of knee MRI studies. For each study, predict 12 abnormality outcomes:\nACL, MCL, Medial Meniscus, Lateral Meniscus, Medial OA, Lateral OA, PF OA, Effusion, Synovitis, Baker's, Contusion, Fracture.\n\nThis notebook loads the CSVs, explores the data, provides a DICOM volume loader, and writes a constant baseline submission."},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"import os, glob\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport pydicom\n\n# ---------- locate competition data ----------\ndef find_data_dir():\n    for base in [\"/kaggle/input/competitions\", \"/kaggle/input\"]:\n        if not os.path.isdir(base):\n            continue\n        for d in sorted(os.listdir(base)):\n            if \"knee\" in d.lower():\n                return os.path.join(base, d)\n    return \"/kaggle/input/rsna-knee-abnormality-detection\"\n\nDATA_DIR = find_data_dir()\nprint(\"DATA_DIR =\", DATA_DIR)\n\nTARGET_COLS = [\"ACL\", \"MCL\", \"Medial Meniscus\", \"Lateral Meniscus\",\n               \"Medial OA\", \"Lateral OA\", \"PF OA\", \"Effusion\",\n               \"Synovitis\", \"Baker's\", \"Contusion\", \"Fracture\"]\n\ntrain = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ntrain_series = pd.read_csv(os.path.join(DATA_DIR, \"train_series.csv\"))\ntest = pd.read_csv(os.path.join(DATA_DIR, \"test.csv\"))\ntest_series = pd.read_csv(os.path.join(DATA_DIR, \"test_series.csv\"))\nsample_sub = pd.read_csv(os.path.join(DATA_DIR, \"sample_submission.csv\"))\n\nprint(\"train:\", train.shape)\nprint(\"test:\", test.shape)\nprint(\"train_series:\", train_series.shape)\nprint(\"test_series:\", test_series.shape)\nprint(\"sample_sub:\", sample_sub.shape)"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"train.head()"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# target distribution\nprint(\"Missing values in train targets:\", train[TARGET_COLS].isna().sum().sum())\nprint(\"Unique target values:\", np.unique(train[TARGET_COLS].values))\ntrain[TARGET_COLS].mean().plot(kind=\"bar\", figsize=(10, 4), title=\"Positive rate per target\")\nplt.xticks(rotation=45, ha=\"right\")\nplt.show()\n\ntrain[\"has_abnormality\"] = (train[TARGET_COLS] > 0).any(axis=1)\nprint(\"Share of studies with >=1 abnormality:\", train[\"has_abnormality\"].mean())"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# series-level metadata\nprint(\"Anatomical planes:\")\nprint(train_series[\"Anatomical_Plane\"].value_counts())\nprint()\nprint(\"Fluid_Sensitive:\")\nprint(train_series[\"Fluid_Sensitive\"].value_counts())\nprint()\nprint(\"Series per study:\")\nprint(train_series.groupby(\"StudyInstanceUID\").size().describe())"},{"cell_type":"markdown","metadata":{},"source":"# Extended EDA\n\nExtra exploration: column overview, missing-value structure, target co-occurrence, series-level distributions, and abnormality counts per study."},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def overview(df, name):\n    print(f\"### {name} ({df.shape[0]} rows x {df.shape[1]} cols)\")\n    info = pd.DataFrame({\n        \"dtype\": df.dtypes.astype(str),\n        \"nunique\": df.nunique(dropna=False),\n        \"n_missing\": df.isna().sum(),\n        \"missing_pct\": (df.isna().mean() * 100).round(2),\n    })\n    print(info.to_string())\n\noverview(train, \"train\")\nprint()\noverview(train_series, \"train_series\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# --- target missing-value structure ---\nprint(\"Per-target missing (NaN) counts in train:\")\nprint(train[TARGET_COLS].isna().sum().to_string())\nprint()\nprint(\"Studies with any missing target:\", train[TARGET_COLS].isna().any(axis=1).sum())\nprint(\"Studies with all targets present:\", train[TARGET_COLS].notna().all(axis=1).sum())\nprint()\nprint(\"Target value distributions:\")\nprint(train[TARGET_COLS].stack().value_counts(dropna=False).to_string())"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# --- target co-occurrence ---\ncorr = train[TARGET_COLS].fillna(0).corr()\nplt.figure(figsize=(9, 7))\nsns.heatmap(corr, annot=True, fmt=\".2f\", cmap=\"coolwarm\", vmin=-1, vmax=1,\n            square=True, linewidths=0.5)\nplt.title(\"Target correlation matrix\")\nplt.tight_layout()\nplt.show()\n\nprint(\"Top 5 most correlated target pairs:\")\npairs = corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool)).stack()\nprint(pairs.sort_values(ascending=False).head(5).to_string())"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# --- abnormalities per study ---\nn_abn = (train[TARGET_COLS].fillna(0) > 0).sum(axis=1)\nn_abn.value_counts().sort_index().plot(kind=\"bar\", figsize=(8, 4),\n                                       title=\"Number of abnormalities per study\")\nplt.xlabel(\"n abnormalities\")\nplt.ylabel(\"studies\")\nplt.tight_layout()\nplt.show()"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# --- series-level breakdown ---\nprint(\"Series per study (train):\")\nprint(train_series.groupby(\"StudyInstanceUID\").size()\n      .value_counts().sort_index().rename(\"n_studies\").to_string())\nprint()\nprint(\"Plane x Fluid_Sensitive counts:\")\nprint(pd.crosstab(train_series[\"Anatomical_Plane\"],\n                  train_series[\"Fluid_Sensitive\"]).to_string())\nprint()\nprint(\"Series count per study stats:\")\nprint(train_series.groupby(\"StudyInstanceUID\")[\"SeriesInstanceUID\"]\n      .count().describe().to_string())"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"def load_series_volume(study_uid, series_uid, split=\"train\"):\n    \"\"\"Load all DICOM slices of a series into a (Z, H, W) float32 volume.\"\"\"\n    d = os.path.join(DATA_DIR, f\"{split}_series\", study_uid, series_uid)\n    files = sorted(glob.glob(os.path.join(d, \"*.dcm\")))\n    if not files:\n        return None\n    slices = [pydicom.dcmread(f) for f in files]\n    slices.sort(key=lambda s: float(s.ImagePositionPatient[2]))\n    arr = np.stack([s.pixel_array for s in slices]).astype(np.float32)\n    arr = (arr - arr.min()) / (arr.max() - arr.min() + 1e-6)\n    return arr"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# visualize one example volume\nstudy = train[\"StudyInstanceUID\"].iloc[0]\nser = train_series[train_series[\"StudyInstanceUID\"] == study].iloc[0]\nvol = load_series_volume(study, ser[\"SeriesInstanceUID\"])\nprint(\"study:\", study)\nprint(\"series:\", ser[\"SeriesInstanceUID\"], \"| plane:\", ser[\"Anatomical_Plane\"], \"| shape:\", vol.shape)\n\nidx = np.linspace(len(vol) // 4, 3 * len(vol) // 4, 4).astype(int)\nfig, axes = plt.subplots(1, len(idx), figsize=(4 * len(idx), 4))\nfor ax, i in zip(axes, idx):\n    ax.imshow(vol[i], cmap=\"gray\")\n    ax.axis(\"off\")\n    ax.set_title(f\"slice {i}\")\nplt.show()"},{"cell_type":"markdown","metadata":{},"source":"## Baseline\n\nA constant baseline predicts the training-set positive rate for every target on every test study. The evaluation metric is average AUC across the 12 targets."},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# constant baseline -> sample submission\nmean_targets = train[TARGET_COLS].mean()\nsub = test[[\"StudyInstanceUID\"]].copy()\nfor c in TARGET_COLS:\n    sub[c] = mean_targets[c]\nsub.to_csv(\"submission.csv\", index=False)\nprint(sub.shape)\nsub.head()"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# cross-validation setup (group by study so volumes never leak across folds)\nfrom sklearn.model_selection import StratifiedGroupKFold\n\nX = train.copy()\nX[\"has_abnormality\"] = (X[TARGET_COLS] > 0).any(axis=1)\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)\nfolds = np.zeros(len(X), dtype=int)\nfor fold, (tr_idx, va_idx) in enumerate(sgkf.split(X, X[\"has_abnormality\"], groups=X[\"StudyInstanceUID\"])):\n    folds[va_idx] = fold\nX[\"fold\"] = folds\nprint(X[\"fold\"].value_counts().sort_index())\nX.to_csv(\"train_with_folds.csv\", index=False)"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.12"}},"nbformat":4,"nbformat_minor":0}