{"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":"markdown","source":"# 📊 Data Exploration & Dataset Architecture\n\nThis dataset contains knee MRI studies annotated for **12 common clinical findings**. The primary challenge is that ground-truth labels are available for only a small subset of studies (~58 cases), while the rest must be pseudo-labeled using the multi-lingual radiology text reports.","metadata":{}},{"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\nimport os\nimport glob\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport pydicom\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\nimport os\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\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-29T18:12:57.622616Z","iopub.execute_input":"2026-08-29T18:12:57.622986Z","iopub.status.idle":"2026-08-29T18:13:02.706767Z","shell.execute_reply.started":"2026-08-29T18:12:57.622945Z","shell.execute_reply":"2026-08-29T18:13:02.705744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set plotting style\nsns.set_theme(style=\"whitegrid\")\nplt.rcParams['figure.dpi'] = 100","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:13:02.708779Z","iopub.execute_input":"2026-08-29T18:13:02.709288Z","iopub.status.idle":"2026-08-29T18:13:02.715325Z","shell.execute_reply.started":"2026-08-29T18:13:02.709257Z","shell.execute_reply":"2026-08-29T18:13:02.714173Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Load dataset","metadata":{}},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/competitions/rsna-knee-abnormality-detection\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:13:09.650986Z","iopub.execute_input":"2026-08-29T18:13:09.651355Z","iopub.status.idle":"2026-08-29T18:13:09.658114Z","shell.execute_reply.started":"2026-08-29T18:13:09.651323Z","shell.execute_reply":"2026-08-29T18:13:09.656995Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(DATA_DIR, 'train.csv'))\ntrain_series_df = pd.read_csv(os.path.join(DATA_DIR, 'train_series.csv'))\ntest_df = pd.read_csv(os.path.join(DATA_DIR, 'test.csv'))\ntest_series_df = pd.read_csv(os.path.join(DATA_DIR, 'test_series.csv'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:13:13.499073Z","iopub.execute_input":"2026-08-29T18:13:13.499406Z","iopub.status.idle":"2026-08-29T18:13:13.795976Z","shell.execute_reply.started":"2026-08-29T18:13:13.499377Z","shell.execute_reply":"2026-08-29T18:13:13.795075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:13:19.009363Z","iopub.execute_input":"2026-08-29T18:13:19.010067Z","iopub.status.idle":"2026-08-29T18:13:19.017832Z","shell.execute_reply.started":"2026-08-29T18:13:19.010011Z","shell.execute_reply":"2026-08-29T18:13:19.016638Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_series_df.columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:14:15.39454Z","iopub.execute_input":"2026-08-29T18:14:15.395401Z","iopub.status.idle":"2026-08-29T18:14:15.401937Z","shell.execute_reply.started":"2026-08-29T18:14:15.39535Z","shell.execute_reply":"2026-08-29T18:14:15.400929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LABEL_COLS = [\n    'ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus', \n    'Medial OA', 'Lateral OA', 'PF OA', 'Effusion', \n    'Synovitis', \"Baker's\", 'Contusion', 'Fracture'\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:20:41.63291Z","iopub.execute_input":"2026-08-29T18:20:41.633247Z","iopub.status.idle":"2026-08-29T18:20:41.638467Z","shell.execute_reply.started":"2026-08-29T18:20:41.633218Z","shell.execute_reply":"2026-08-29T18:20:41.637613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== DATASET OVERVIEW ===\")\nprint(f\"Train Studies (train.csv): {len(train_df):,}\")\nprint(f\"Train Series (train_series.csv): {len(train_series_df):,}\")\nprint(f\"Test Studies (test.csv): {len(test_df):,}\")\nprint(f\"Test Series (test_series.csv): {len(test_series_df):,}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:20:46.509Z","iopub.execute_input":"2026-08-29T18:20:46.509334Z","iopub.status.idle":"2026-08-29T18:20:46.515107Z","shell.execute_reply.started":"2026-08-29T18:20:46.509304Z","shell.execute_reply":"2026-08-29T18:20:46.514162Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- **`train.csv`** (Study-Level Targets & Reports)\nContains study-level metadata, free-text radiology reports, and ground-truth target labels.\n\n* **`StudyInstanceUID`** *(str)*: Unique identifier for the MRI study session. Matches folder names in `train_series/`.\n* **`Report`** *(str)*: Free-text radiology report written by clinicians in various languages (English, Spanish, Turkish, German, etc.).\n* **12 Target Binary Findings (`0` or `1`):**\n  * **Ligament & Meniscus Injuries:** `ACL`, `MCL`, `Medial Meniscus`, `Lateral Meniscus`\n  * **Osteoarthritis (OA):** `Medial OA`, `Lateral OA`, `PF OA` (Patellofemoral)\n  * **Inflammation & Fluid Accumulation:** `Effusion`, `Synovitis`, `Baker's` (Popliteal Cyst)\n  * **Bone Integrity:** `Contusion` (Bone Bruise / Marrow Edema), `Fracture`\n\n\n---\n\n- **`train_series.csv`** (Acquisition Metadata)\n\nDescribes the specific MRI acquisition sequences performed during each study session.\n\n* **`StudyInstanceUID`** *(str)*: Unique identifier of the study also found in `train.csv`.\n* **`SeriesInstanceUID`** *(str)*: Unique identifier for the series. Matches subfolder names in `train_series/<StudyInstanceUID>/`.\n* **`Anatomical_Plane`** *(categorical)*: Spatial orientation of the image slices (`Sagittal`, `Coronal`, or `Axial`).\n* **`Fluid_Sensitive`** *(binary: 0/1)*: Indicates sequences optimized to highlight fluid signals (e.g., T2, PD, STIR).\n* **`Fat_Suppression`** *(binary: 0/1)*: Indicates whether fat signal suppression was applied to emphasize edema/inflammation.\n\n---\n\n- **Image Slices Folder** (`train_series/`)\nOrganized hierarchically on disk:\n`train_series/<StudyInstanceUID>/<SeriesInstanceUID>/<SOPInstanceUID>.dcm`\n\n* **Format:** DICOM (`.dcm`) 2D image slices.\n* **Volume:** Typically **20–45 slices per series** (median ~30 slices), with varying image resolutions and transfer syntaxes across clinical sites.","metadata":{}},{"cell_type":"markdown","source":"### Analyze label completeness and Text report status","metadata":{}},{"cell_type":"code","source":"labeled_mask = train_df[LABEL_COLS].notna().any(axis=1)\nlabeled_studies = train_df[labeled_mask]\nunlabeled_studies = train_df[~labeled_mask]\n\nprint(\"=== GROUND TRUTH LABELS ANALYTICS ===\")\nprint(f\"Studies WITH Ground-Truth Labels: {len(labeled_studies):,} ({len(labeled_studies)/len(train_df):.1%})\")\nprint(f\"Studies WITHOUT Ground-Truth Labels (Report Only): {len(unlabeled_studies):,} ({len(unlabeled_studies)/len(train_df):.1%})\")\n\n# Report presence check\ntrain_df['has_report'] = train_df['Report'].notna() & (train_df['Report'].str.strip() != '')\nprint(f\"Studies with text reports: {train_df['has_report'].sum():,} ({train_df['has_report'].mean():.1%})\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:24:39.513709Z","iopub.execute_input":"2026-08-29T18:24:39.514046Z","iopub.status.idle":"2026-08-29T18:24:39.534318Z","shell.execute_reply.started":"2026-08-29T18:24:39.514019Z","shell.execute_reply":"2026-08-29T18:24:39.533351Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Count how many labels are missing per study (row)\nnull_counts_per_row = train_df[LABEL_COLS].isna().sum(axis=1)\n\n# Group by the count of missing labels per row\nstatus_summary = null_counts_per_row.value_counts().reset_index()\nstatus_summary.columns = ['Missing Label Count', 'Number of Studies']\n\nprint(\"=== LABEL COMPLETENESS CHECK ===\")\nprint(status_summary.to_string(index=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:24:45.194077Z","iopub.execute_input":"2026-08-29T18:24:45.19438Z","iopub.status.idle":"2026-08-29T18:24:45.213093Z","shell.execute_reply.started":"2026-08-29T18:24:45.194354Z","shell.execute_reply":"2026-08-29T18:24:45.212202Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> **Key Observation:** Studies either have **all 12 labels provided** or **all 12 labels missing**.","metadata":{}},{"cell_type":"markdown","source":"### Label positivity & Co-occurrence analysis","metadata":{}},{"cell_type":"code","source":"positivity = labeled_studies[LABEL_COLS].mean().sort_values(ascending=False)\ncounts = labeled_studies[LABEL_COLS].sum().astype(int)\n\nfig, axes = plt.subplots(1, 2, figsize=(16, 6))\n\n# Positive Prevalence Plot\nsns.barplot(x=positivity.values, y=positivity.index, ax=axes[0], palette=\"crest\")\naxes[0].set_title(\"Finding Positivity Rate (Labeled Subset)\", fontsize=13, fontweight='bold')\naxes[0].set_xlabel(\"Positive Proportion\")\nfor idx, (val, count) in enumerate(zip(positivity.values, counts[positivity.index])):\n    axes[0].text(val + 0.005, idx, f\"{val:.1%} (n={count})\", va='center', fontsize=9)\n\n# Label Co-occurrence Heatmap\ncorr = labeled_studies[LABEL_COLS].corr()\nsns.heatmap(corr, annot=True, fmt=\".2f\", cmap=\"vlag\", ax=axes[1], cbar_kws={'label': 'Correlation'})\naxes[1].set_title(\"Label Co-occurrence Matrix\", fontsize=13, fontweight='bold')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:28:07.267324Z","iopub.execute_input":"2026-08-29T18:28:07.267642Z","iopub.status.idle":"2026-08-29T18:28:08.276822Z","shell.execute_reply.started":"2026-08-29T18:28:07.267613Z","shell.execute_reply":"2026-08-29T18:28:08.275882Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Series Metadata Analysis","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n\n# Anatomical Planes Distribution\nsns.countplot(data=train_series_df, x='Anatomical_Plane', ax=axes[0], palette=\"Set2\")\naxes[0].set_title(\"Series by Anatomical Plane\", fontsize=12, fontweight='bold')\n\n# Series per Study Count Distribution\nseries_per_study = train_series_df.groupby('StudyInstanceUID').size()\nsns.histplot(series_per_study, bins=range(1, 15), discrete=True, ax=axes[1], color=\"teal\")\naxes[1].set_title(\"Number of Series per Study\", fontsize=12, fontweight='bold')\naxes[1].set_xlabel(\"Series Count\")\n\n# Sequence Properties Breakdown\nprop_df = train_series_df.groupby(['Fluid_Sensitive', 'Fat_Suppression']).size().reset_index(name='count')\nsns.barplot(data=prop_df, x='Fluid_Sensitive', y='count', hue='Fat_Suppression', ax=axes[2], palette=\"Paired\")\naxes[2].set_title(\"Sequence Properties (Fluid Sensitive vs Fat Suppressed)\", fontsize=12, fontweight='bold')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:28:53.978434Z","iopub.execute_input":"2026-08-29T18:28:53.978791Z","iopub.status.idle":"2026-08-29T18:28:54.578762Z","shell.execute_reply.started":"2026-08-29T18:28:53.978763Z","shell.execute_reply":"2026-08-29T18:28:54.577903Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### DICOM Header Verification & Pixel Inspection","metadata":{}},{"cell_type":"markdown","source":"**What is a DICOM:** stands for **Digital Imaging and COmmunication in Medecine**, which is the international global standard for storing, transmitting, processing and displaying medical images.\n\n- **File format:** `.dcm` extension.\n- **Structure:** Each file contains a header with patient metadata (name, ID, scanner type ...) and the actual pixel image data.\n- **Interoperability:** It allows medical devices and software from different makers (like MRI, CT, X-ray, and ultrasound machines) to share data smoothly.","metadata":{}},{"cell_type":"markdown","source":"### 🎥 Understanding Medical Imaging Architecture: Studies, Series, and Slices\n\n* **Study (`StudyInstanceUID`):** The parent folder representing the entire patient exam session.\n* **Series / Pulse Sequence (`SeriesInstanceUID`):** A single 3D scanning pass made across the knee. Each study contains 3 to 6 Series, varying by **Anatomical Plane** (*Sagittal*, *Coronal*, or *Axial*) and **Pulse Properties** (*Fluid-Sensitive* or *Fat-Suppressed*).\n* **Slices & Indexes (`.dcm` files):** Each individual DICOM file corresponds to exactly **one 2D image slice** at depth $N$. A series consists of a continuous stack of 20–45 indexed slices running sequentially through the knee volume.\n* **Header Metadata:** **Every individual `.dcm` slice file contains both global series metadata** (contrast, plane, pixel spacing) **and local spatial metadata** (its physical $Z$-position index).","metadata":{}},{"cell_type":"code","source":"sample_study = train_series_df['StudyInstanceUID'].iloc[0]\nsample_series = train_series_df[train_series_df['StudyInstanceUID'] == sample_study]['SeriesInstanceUID'].iloc[0]\nsample_dir = os.path.join(DATA_DIR, 'train_series', sample_study, sample_series)\n\ndcm_files = sorted(glob.glob(os.path.join(sample_dir, \"*.dcm\")))\nprint(f\"=== DICOM INSPECTION (Sample Series: {sample_series}) ===\")\nprint(f\"Slices found in series: {len(dcm_files)}\")\n\nif dcm_files:\n    dcm = pydicom.dcmread(dcm_files[0])\n    \n    print(\"\\nKey Header Tags:\")\n    print(f\"  - Modality: {dcm.get('Modality', 'N/A')}\")\n    print(f\"  - Transfer Syntax UID: {dcm.file_meta.TransferSyntaxUID.name if 'TransferSyntaxUID' in dcm.file_meta else 'N/A'}\")\n    print(f\"  - Image Dimensions: {dcm.Rows} x {dcm.Columns}\")\n    print(f\"  - Pixel Spacing: {dcm.get('PixelSpacing', 'N/A')}\")\n    print(f\"  - Slice Thickness: {dcm.get('SliceThickness', 'N/A')}\")\n    print(f\"  - Photometric Interpretation: {dcm.get('PhotometricInterpretation', 'N/A')}\")\n    \n    # Display Slice Middle of Sequence\n    mid_idx = len(dcm_files) // 2\n    mid_dcm = pydicom.dcmread(dcm_files[mid_idx])\n    \n    plt.figure(figsize=(6, 6))\n    plt.imshow(mid_dcm.pixel_array, cmap='gray')\n    plt.title(f\"Sample DICOM Slice (Index: {mid_idx})\", fontsize=12, fontweight='bold')\n    plt.axis('off')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:34:19.353435Z","iopub.execute_input":"2026-08-29T18:34:19.353774Z","iopub.status.idle":"2026-08-29T18:34:19.573904Z","shell.execute_reply.started":"2026-08-29T18:34:19.353747Z","shell.execute_reply":"2026-08-29T18:34:19.572922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labeled_studies_df = train_df[train_df[LABEL_COLS].notna().all(axis=1)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:48:38.794345Z","iopub.execute_input":"2026-08-29T18:48:38.794727Z","iopub.status.idle":"2026-08-29T18:48:38.805002Z","shell.execute_reply.started":"2026-08-29T18:48:38.794696Z","shell.execute_reply":"2026-08-29T18:48:38.804022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def inspect_study_complete(study_id, sample_num=1):\n    \"\"\"\n    Renders full diagnostic inspection for a StudyInstanceUID:\n    - Text Report & 12 Ground-Truth Labels\n    - Visualizes middle slice of ALL DICOM series with series metadata\n    \"\"\"\n    # Fetch study row and series\n    study_row = train_df[train_df['StudyInstanceUID'] == study_id].iloc[0]\n    series_rows = train_series_df[train_series_df['StudyInstanceUID'] == study_id]\n    \n    print(\"=\" * 80)\n    print(f\"   STUDY INSPECTION #{sample_num}: {study_id}\")\n    print(\"=\" * 80)\n    \n    # --- A. PRINT RADIOLOGY REPORT ---\n    print(\"\\n[1] RADIOLOGY REPORT:\")\n    print(\"-\" * 50)\n    report_text = str(study_row.get('Report', 'N/A')).strip()\n    print(report_text if report_text else \"[No report text available]\")\n    print(\"-\" * 50)\n    \n    # --- B. PRINT GROUND TRUTH LABELS ---\n    print(\"\\n[2] GROUND TRUTH LABELS (12 Findings):\")\n    labels_present = []\n    labels_absent = []\n    \n    for col in LABEL_COLS:\n        val = study_row[col]\n        if pd.isna(val):\n            labels_present.append(f\"{col}: UNKNOWN (NaN)\")\n        elif val == 1:\n            labels_present.append(f\"{col}: 1 (POSITIVE)\")\n        else:\n            labels_absent.append(f\"{col}: 0\")\n            \n    print(\"  POSITIVE PATHOLOGIES:\", \", \".join(labels_present) if labels_present else \"None (All negative)\")\n    print(\"  NEGATIVE / ABSENT:    \", \", \".join(labels_absent))\n    \n    # --- C. PLOT ALL DICOM SERIES SIDE-BY-SIDE ---\n    num_series = len(series_rows)\n    if num_series == 0:\n        print(\"\\nNo series metadata found for this study.\")\n        return\n        \n    fig, axes = plt.subplots(1, num_series, figsize=(5 * num_series, 5.5))\n    if num_series == 1:\n        axes = [axes] # Ensure iterable for single series case\n        \n    print(f\"\\n[3] VISUALIZING ALL {num_series} MRI SERIES FOR THIS STUDY:\")\n    \n    for idx, (_, s_row) in enumerate(series_rows.iterrows()):\n        series_id = s_row['SeriesInstanceUID']\n        plane = s_row.get('Anatomical_Plane', 'Unknown')\n        fluid = \"Fluid-Sensitive\" if s_row.get('Fluid_Sensitive', 0) == 1 else \"Non-Fluid\"\n        fat = \"Fat-Suppressed\" if s_row.get('Fat_Suppression', 0) == 1 else \"Non-Fat-Suppressed\"\n        \n        # Path to DICOM folder\n        series_dir = os.path.join(DATA_DIR, 'train_series', study_id, series_id)\n        dcm_files = sorted(glob.glob(os.path.join(series_dir, \"*.dcm\")))\n        \n        ax = axes[idx]\n        \n        if len(dcm_files) > 0:\n            # Read middle slice of series\n            mid_idx = len(dcm_files) // 2\n            dcm = pydicom.dcmread(dcm_files[mid_idx])\n            img = dcm.pixel_array\n            \n            # Additional DICOM metadata tag check\n            dims = f\"{dcm.Rows}x{dcm.Columns}\" if hasattr(dcm, 'Rows') else \"N/A\"\n            ts_uid = dcm.file_meta.TransferSyntaxUID.name if 'TransferSyntaxUID' in dcm.file_meta else \"N/A\"\n            \n            # Plot Middle Image\n            ax.imshow(img, cmap='gray')\n            \n            # Format Title with Planes and Pulse Properties\n            title_text = (\n                f\"Series {idx+1}/{num_series}\\n\"\n                f\"Plane: {plane.upper()}\\n\"\n                f\"{fluid} | {fat}\\n\"\n                f\"Slices: {len(dcm_files)} | Size: {dims}\"\n            )\n            ax.set_title(title_text, fontsize=10, fontweight='bold', pad=10)\n        else:\n            ax.text(0.5, 0.5, f\"No DICOM Slices\\nFound in path\", ha='center', va='center')\n            ax.set_title(f\"Series {idx+1} (Empty)\")\n            \n        ax.axis('off')\n        \n    plt.suptitle(f\"Study ID: {study_id}\", fontsize=14, fontweight='bold', y=1.03)\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:48:51.666237Z","iopub.execute_input":"2026-08-29T18:48:51.666619Z","iopub.status.idle":"2026-08-29T18:48:51.682857Z","shell.execute_reply.started":"2026-08-29T18:48:51.66659Z","shell.execute_reply":"2026-08-29T18:48:51.68185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------------------------------------------------------\n# Inspect 2 Labeled Studies\n# ---------------------------------------------------------\nif len(labeled_studies_df) >= 2:\n    sample_study_1 = labeled_studies_df['StudyInstanceUID'].iloc[0]\n    sample_study_2 = labeled_studies_df['StudyInstanceUID'].iloc[1]\n    \n    inspect_study_complete(sample_study_1, sample_num=1)\n    inspect_study_complete(sample_study_2, sample_num=2)\nelse:\n    print(f\"Only found {len(labeled_studies_df)} fully labeled studies. Displaying sample 0...\")\n    sample_study_1 = train_df['StudyInstanceUID'].iloc[0]\n    inspect_study_complete(sample_study_1, sample_num=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:48:57.279295Z","iopub.execute_input":"2026-08-29T18:48:57.279642Z","iopub.status.idle":"2026-08-29T18:49:00.607849Z","shell.execute_reply.started":"2026-08-29T18:48:57.279611Z","shell.execute_reply":"2026-08-29T18:49:00.606831Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Inspecting reports","metadata":{}},{"cell_type":"code","source":"# Filter studies where label columns are NaN (unlabeled) but report exists\nunlabeled_df = train_df[train_df[LABEL_COLS].isna().any(axis=1) & train_df['Report'].notna()]\n\n# Sample 5 random reports to read\nsample_reports = unlabeled_df.sample(5, random_state=42)\n\nfor idx, row in sample_reports.iterrows():\n    print(f\"=== StudyUID: {row['StudyInstanceUID']} ===\")\n    print(row['Report'])\n    print(\"-\" * 50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-29T18:50:06.856862Z","iopub.execute_input":"2026-08-29T18:50:06.857206Z","iopub.status.idle":"2026-08-29T18:50:06.86976Z","shell.execute_reply.started":"2026-08-29T18:50:06.85718Z","shell.execute_reply":"2026-08-29T18:50:06.86873Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 📝 Text Reports & Pseudo-Labeling Strategy\n\n* **Coverage:** Free-text radiology reports are present across all study instances, including the ~98% of training cases that lack explicit ground-truth labels.\n* **Label Extraction:** These reports serve as the primary source for generating pseudo-labels across all 12 target pathologies to expand our training set.\n* **Multi-Lingual Consideration:** Because the reports are written in diverse international languages (e.g., English, Spanish, Turkish, German), the chosen NLP model or LLM must natively support multi-lingual understanding and translation.","metadata":{}}]}