{"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":"\"\"\"\nFixed Split — Handles Rare Size Categories (6-Dataset Version)\n================================================================\nProblem: Kvasir-SEG has only 1 diminutive sample\n         Cannot stratify on class with 1 member\n\nFix: Merge diminutive into small for stratification\n     when any category has fewer than 2 samples\n     (keeps original size_category label intact)\n\nDatasets (6 total, matching literature-standard protocol + BKAI):\n    Train + val (in-distribution, stratified 80/10/10):\n        - BKAI-IGH NeoPolyp   (stratify by risk label)\n        - CVC-ClinicDB        (stratify by size)\n        - Kvasir-SEG          (stratify by size)\n    Benchmark-only (zero train contribution, unseen generalization test):\n        - CVC-ColonDB\n        - ETIS-Larib\n        - CVC-300 (CVC-T)     <- added this pass, same pattern as ColonDB/ETIS\n\nNote: CVC-300 is derived from CVC-ClinicDB / CVC-ColonDB source videos in\nthe literature. A duplicate/near-duplicate check against CVC-ClinicDB\n(the training contributor) has NOT been run in this pass -- if benchmark\ndice on CVC-300 looks unusually high, that overlap is the first thing to\ncheck before trusting the number.\n\"\"\"\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom pathlib import Path\nfrom sklearn.model_selection import train_test_split\nimport warnings\nwarnings.filterwarnings('ignore')\n\n\n# ============================================================\n# PATHS\n# ============================================================\nBKAI_IMG     = \"/kaggle/input/competitions/bkai-igh-neopolyp/train/train\"\nBKAI_MASK    = \"/kaggle/input/competitions/bkai-igh-neopolyp/train_gt/train_gt\"\nCVC_IMG      = \"/kaggle/input/datasets/balraj98/cvcclinicdb/PNG/Original\"\nCVC_MASK     = \"/kaggle/input/datasets/balraj98/cvcclinicdb/PNG/Ground Truth\"\nKVASIR_IMG   = \"/kaggle/input/datasets/debeshjha1/kvasirseg/Kvasir-SEG/Kvasir-SEG/images\"\nKVASIR_MASK  = \"/kaggle/input/datasets/debeshjha1/kvasirseg/Kvasir-SEG/Kvasir-SEG/masks\"\nCOLONDB_IMG  = \"/kaggle/input/datasets/longvil/cvc-colondb/CVC-ColonDB/images\"\nCOLONDB_MASK = \"/kaggle/input/datasets/longvil/cvc-colondb/CVC-ColonDB/masks\"\nETIS_IMG     = \"/kaggle/input/datasets/nguyenvoquocduong/etis-laribpolypdb/images\"\nETIS_MASK    = \"/kaggle/input/datasets/nguyenvoquocduong/etis-laribpolypdb/masks\"\n\n# NEW — CVC-300 (CVC-T), benchmark-only, same pattern as ColonDB/ETIS\nCVC300_IMG   = \"/kaggle/input/datasets/nourabentaher/cvc-300/CVC-300/images\"\nCVC300_MASK  = \"/kaggle/input/datasets/nourabentaher/cvc-300/CVC-300/masks\"\n\nSEED = 42\n\n# ============================================================\n# HELPERS\n# ============================================================\n\ndef get_files(folder, exts=('.jpg', '.jpeg', '.png', '.tif')):\n    if not os.path.exists(folder):\n        print(f\"WARNING: {folder} not found\")\n        return []\n    return sorted([\n        os.path.join(folder, f)\n        for f in os.listdir(folder)\n        if f.lower().endswith(exts)\n    ])\n\ndef get_bkai_label(mask_path):\n    mask = np.array(Image.open(mask_path).convert('RGB'))\n    r, g, b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n    red_px   = ((r>150) & (r>g*1.5) & (r>b*1.5)).sum()\n    green_px = ((g>150) & (g>r*1.5) & (g>b*1.5)).sum()\n    has_polyp = (red_px + green_px) > 50\n    if not has_polyp:\n        return 0, 'non_neoplastic', False\n    return (1,'neoplastic',True) if red_px>=green_px \\\n           else (0,'non_neoplastic',True)\n\ndef get_size_category(mask_path, is_bkai=False):\n    if is_bkai:\n        mask = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0],mask[:,:,1],mask[:,:,2]\n        polyp = ((r>150)&(r>g*1.5)&(r>b*1.5)) | \\\n                ((g>150)&(g>r*1.5)&(g>b*1.5))\n    else:\n        gray  = np.array(Image.open(mask_path).convert('L'))\n        polyp = gray > 127\n    h,w   = polyp.shape\n    ratio = polyp.sum()/(h*w)\n    if ratio < 0.005: return 'diminutive', round(float(ratio),4)\n    if ratio < 0.02:  return 'small',      round(float(ratio),4)\n    return 'large', round(float(ratio),4)\n\ndef build_bkai_df():\n    imgs  = {Path(f).stem:f for f in get_files(BKAI_IMG)}\n    masks = {Path(f).stem:f for f in get_files(BKAI_MASK)}\n    stems = sorted(imgs.keys() & masks.keys())\n    records = []\n    for s in stems:\n        label, label_str, has_polyp = get_bkai_label(masks[s])\n        size_cat, ratio = get_size_category(masks[s], is_bkai=True)\n        records.append({\n            'stem':s, 'img_path':imgs[s], 'mask_path':masks[s],\n            'label':label, 'label_str':label_str,\n            'has_risk_label':True, 'dataset':'bkai',\n            'size_category':size_cat, 'polyp_ratio':ratio\n        })\n    return pd.DataFrame(records)\n\ndef build_seg_df(img_dir, mask_dir, name):\n    \"\"\"\n    Generic builder for datasets shipped as matched images/ + masks/\n    folders (or any two folders whose files share a common stem).\n    Used for CVC-ClinicDB, Kvasir, ColonDB, ETIS, and CVC-300 --\n    the folder *names* differ (Original/Ground Truth vs images/masks)\n    but that's absorbed by the path constants above, not this function.\n    \"\"\"\n    imgs  = {Path(f).stem:f for f in get_files(img_dir)}\n    masks = {Path(f).stem:f for f in get_files(mask_dir)}\n    stems = sorted(imgs.keys() & masks.keys())\n    records = []\n    for s in stems:\n        size_cat, ratio = get_size_category(masks[s], is_bkai=False)\n        records.append({\n            'stem':s, 'img_path':imgs[s], 'mask_path':masks[s],\n            'label':-1, 'label_str':'unknown',\n            'has_risk_label':False, 'dataset':name,\n            'size_category':size_cat, 'polyp_ratio':ratio\n        })\n    return pd.DataFrame(records)\n\ndef get_stratify_col(df, col):\n    \"\"\"\n    Returns stratification series.\n    Merges any category with fewer than 2 members\n    into the next larger category.\n    Original df column is NOT modified.\n    \"\"\"\n    s = df[col].copy()\n    counts = s.value_counts()\n\n    rare = counts[counts < 2].index.tolist()\n\n    if rare:\n        print(f\"  WARNING: merging rare categories {rare} \"\n              f\"into 'small' for stratification only\")\n        for cat in rare:\n            s = s.replace(cat, 'small')\n\n    counts2 = s.value_counts()\n    still_rare = counts2[counts2 < 2].index.tolist()\n    if still_rare:\n        print(f\"  WARNING: still rare after merge {still_rare}, \"\n              f\"using binary large/not_large stratification\")\n        s = s.apply(lambda x: 'large' if x=='large' else 'not_large')\n\n    # Final safety net: even the binary fallback can leave a class with\n    # <2 members once the pool being split is already small (e.g. the\n    # second 50/50 cut of an already-tiny val/test temp set). If so,\n    # stratification isn't viable on this subset at all -- fall back to\n    # a plain unstratified split rather than crashing train_test_split.\n    counts3 = s.value_counts()\n    if counts3.min() < 2:\n        print(f\"  WARNING: stratification not viable on this subset \"\n              f\"(n={len(s)}, smallest class={counts3.min()}) -- \"\n              f\"using unstratified split\")\n        return None\n\n    return s\n\ndef split_80_10_10(df, stratify_col, seed=42):\n    \"\"\"\n    80% train, 10% val, 10% test\n    Handles rare categories automatically\n    \"\"\"\n    strat = get_stratify_col(df, stratify_col)\n\n    train_idx, temp_idx = train_test_split(\n        np.arange(len(df)),\n        test_size=0.20,\n        random_state=seed,\n        stratify=strat   # None here means sklearn does an unstratified split\n    )\n\n    temp_df    = df.iloc[temp_idx].reset_index(drop=True)\n    strat_temp = get_stratify_col(temp_df, stratify_col)\n\n    val_idx, test_idx = train_test_split(\n        np.arange(len(temp_df)),\n        test_size=0.50,\n        random_state=seed,\n        stratify=strat_temp   # None here means sklearn does an unstratified split\n    )\n\n    train = df.iloc[train_idx].reset_index(drop=True)\n    val   = temp_df.iloc[val_idx].reset_index(drop=True)\n    test  = temp_df.iloc[test_idx].reset_index(drop=True)\n\n    return train, val, test\n\ndef low_n_warning(name, df, min_n=10):\n    \"\"\"\n    Flags subsets small enough that per-dataset metrics computed on\n    them later will be noisy -- not a fix, just makes sure a small\n    sample size isn't silently misread as a stable result.\n    \"\"\"\n    if len(df) < min_n:\n        print(f\"  WARNING: {name} has only {len(df)} samples -- \"\n              f\"treat any metric on this subset as low-confidence\")\n\n# ============================================================\n# BUILD DATAFRAMES\n# ============================================================\nprint(\"Building dataset records...\")\nbkai_df    = build_bkai_df()\ncvc_df     = build_seg_df(CVC_IMG,     CVC_MASK,    'cvc')\nkvasir_df  = build_seg_df(KVASIR_IMG,  KVASIR_MASK, 'kvasir')\ncolondb_df = build_seg_df(COLONDB_IMG, COLONDB_MASK,'colondb')\netis_df    = build_seg_df(ETIS_IMG,    ETIS_MASK,   'etis')\ncvc300_df  = build_seg_df(CVC300_IMG,  CVC300_MASK, 'cvc300')   # NEW\n\nprint(f\"\\nRaw sizes:\")\nprint(f\"  BKAI-IGH    : {len(bkai_df)}\")\nprint(f\"  CVC-ClinicDB: {len(cvc_df)}\")\nprint(f\"  Kvasir-SEG  : {len(kvasir_df)}\")\nprint(f\"  CVC-ColonDB : {len(colondb_df)}  benchmark test only\")\nprint(f\"  ETIS-Larib  : {len(etis_df)}  benchmark test only\")\nprint(f\"  CVC-300     : {len(cvc300_df)}  benchmark test only\")\n\nprint(f\"\\nSize category distributions:\")\nfor name, df in [('BKAI',   bkai_df),\n                  ('CVC',    cvc_df),\n                  ('Kvasir', kvasir_df)]:\n    counts = df['size_category'].value_counts()\n    print(f\"  {name}: {dict(counts)}\")\n\n# ============================================================\n# APPLY SPLITS\n# ============================================================\nprint(\"\\nApplying 80/10/10 splits...\")\n\nprint(\"\\nBKAI-IGH (stratify by risk label):\")\nbkai_train, bkai_val, bkai_test = split_80_10_10(\n    bkai_df, 'label'\n)\n\nprint(\"\\nCVC-ClinicDB (stratify by size):\")\ncvc_train, cvc_val, cvc_test = split_80_10_10(\n    cvc_df, 'size_category'\n)\n\nprint(\"\\nKvasir-SEG (stratify by size):\")\nkvasir_train, kvasir_val, kvasir_test = split_80_10_10(\n    kvasir_df, 'size_category'\n)\n\n# Benchmark test sets — no split, zero train contribution\ncolondb_test = colondb_df.copy()\netis_test    = etis_df.copy()\ncvc300_test  = cvc300_df.copy()   # NEW\n\n# ============================================================\n# COMBINE\n# ============================================================\ntrain_df = pd.concat([\n    bkai_train, cvc_train, kvasir_train\n]).reset_index(drop=True)\n\nval_df = pd.concat([\n    bkai_val, cvc_val, kvasir_val\n]).reset_index(drop=True)\n\nindist_test_df = pd.concat([\n    bkai_test, cvc_test, kvasir_test\n]).reset_index(drop=True)\n\nbenchmark_test_df = pd.concat([\n    colondb_test, etis_test, cvc300_test   # NEW: cvc300_test added\n]).reset_index(drop=True)\n\n# ============================================================\n# LOW-N WARNINGS\n# ============================================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"SAMPLE SIZE CHECK\")\nprint(\"=\"*60)\nfor name, df in [('Train', train_df), ('Val', val_df),\n                  ('Indist test', indist_test_df),\n                  ('Benchmark test', benchmark_test_df),\n                  ('CVC-300 benchmark', cvc300_test)]:\n    low_n_warning(name, df)\n\n# ============================================================\n# PRINT RESULTS\n# ============================================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"FINAL SPLIT\")\nprint(\"=\"*60)\nprint(f\"\\n{'Dataset':<15} {'Train':>7} {'Val':>7} {'Test':>7}\")\nprint(\"-\"*40)\nprint(f\"{'BKAI-IGH':<15} {len(bkai_train):>7} \"\n      f\"{len(bkai_val):>7} {len(bkai_test):>7}\")\nprint(f\"{'CVC-ClinicDB':<15} {len(cvc_train):>7} \"\n      f\"{len(cvc_val):>7} {len(cvc_test):>7}\")\nprint(f\"{'Kvasir-SEG':<15} {len(kvasir_train):>7} \"\n      f\"{len(kvasir_val):>7} {len(kvasir_test):>7}\")\nprint(\"-\"*40)\nprint(f\"{'TOTAL':<15} {len(train_df):>7} \"\n      f\"{len(val_df):>7} {len(indist_test_df):>7}\")\n\nprint(f\"\\nBenchmark Test (literature standard, unseen):\")\nprint(f\"  CVC-ColonDB : {len(colondb_test)}\")\nprint(f\"  ETIS-Larib  : {len(etis_test)}\")\nprint(f\"  CVC-300     : {len(cvc300_test)}\")\nprint(f\"  Total       : {len(benchmark_test_df)}\")\n\n# ============================================================\n# VERIFY RISK LABEL STRATIFICATION\n# ============================================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STRATIFICATION CHECK\")\nprint(\"=\"*60)\n\ntotal_rate = (bkai_df['label']==1).mean()\nprint(f\"\\nBKAI neoplastic rate (should be ~{total_rate:.3f} everywhere):\")\nfor name, split in [('Full', bkai_df), ('Train', bkai_train),\n                     ('Val',  bkai_val), ('Test',  bkai_test)]:\n    rate = (split['label']==1).mean()\n    neo  = (split['label']==1).sum()\n    non  = (split['label']==0).sum()\n    ok   = '✓' if abs(rate-total_rate) < 0.02 else '✗'\n    print(f\"  {name:<6}: {len(split):>4} images | \"\n          f\"neo={neo:>3} non={non:>3} | \"\n          f\"rate={rate:.4f} {ok}\")\n\n# Also verify size-category balance held for CVC and Kvasir\nfor ds_name, full_df, train_s, val_s, test_s in [\n    ('CVC-ClinicDB', cvc_df, cvc_train, cvc_val, cvc_test),\n    ('Kvasir-SEG',   kvasir_df, kvasir_train, kvasir_val, kvasir_test),\n]:\n    print(f\"\\n{ds_name} size_category distribution \"\n          f\"(should roughly match across splits):\")\n    for name, split in [('Full', full_df), ('Train', train_s),\n                         ('Val', val_s), ('Test', test_s)]:\n        dist = split['size_category'].value_counts(normalize=True).round(3).to_dict()\n        print(f\"  {name:<6}: n={len(split):>4}  {dist}\")\n\nprint(f\"\\nRisk samples available:\")\nfor name, split in [('Train', train_df),\n                     ('Val',   val_df),\n                     ('Test',  indist_test_df)]:\n    r = split[split['has_risk_label']]\n    print(f\"  {name}: {len(r)} \"\n          f\"(neo={(r['label']==1).sum()}, \"\n          f\"non-neo={(r['label']==0).sum()})\")\n\n# ============================================================\n# SAVE\n# ============================================================\ntrain_df.to_csv('train_split.csv',             index=False)\nval_df.to_csv('val_split.csv',                 index=False)\nindist_test_df.to_csv('indist_test.csv',       index=False)\nbenchmark_test_df.to_csv('benchmark_test.csv', index=False)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"SAVED: train_split.csv, val_split.csv,\")\nprint(\"       indist_test.csv, benchmark_test.csv\")\nprint(\"Benchmark test now includes 6th dataset: CVC-300\")\nprint(\"Next: model architecture (baseline segmentation)\")\nprint(\"=\"*60)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-08T00:47:23.24721Z","iopub.execute_input":"2026-09-08T00:47:23.247421Z","iopub.status.idle":"2026-09-08T00:48:44.52162Z","shell.execute_reply.started":"2026-09-08T00:47:23.247399Z","shell.execute_reply":"2026-09-08T00:48:44.520922Z"}},"outputs":[{"name":"stdout","text":"Building dataset records...\n\nRaw sizes:\n  BKAI-IGH    : 1000\n  CVC-ClinicDB: 612\n  Kvasir-SEG  : 1000\n  CVC-ColonDB : 380  benchmark test only\n  ETIS-Larib  : 196  benchmark test only\n  CVC-300     : 60  benchmark test only\n\nSize category distributions:\n  BKAI: {'large': np.int64(703), 'small': np.int64(261), 'diminutive': np.int64(36)}\n  CVC: {'large': np.int64(551), 'small': np.int64(58), 'diminutive': np.int64(3)}\n  Kvasir: {'large': np.int64(969), 'small': np.int64(30), 'diminutive': np.int64(1)}\n\nApplying 80/10/10 splits...\n\nBKAI-IGH (stratify by risk label):\n\nCVC-ClinicDB (stratify by size):\n\nKvasir-SEG (stratify by size):\n  WARNING: merging rare categories ['diminutive'] into 'small' for stratification only\n\n============================================================\nSAMPLE SIZE CHECK\n============================================================\n\n============================================================\nFINAL SPLIT\n============================================================\n\nDataset           Train     Val    Test\n----------------------------------------\nBKAI-IGH            800     100     100\nCVC-ClinicDB        489      61      62\nKvasir-SEG          800     100     100\n----------------------------------------\nTOTAL              2089     261     262\n\nBenchmark Test (literature standard, unseen):\n  CVC-ColonDB : 380\n  ETIS-Larib  : 196\n  CVC-300     : 60\n  Total       : 636\n\n============================================================\nSTRATIFICATION CHECK\n============================================================\n\nBKAI neoplastic rate (should be ~0.736 everywhere):\n  Full  : 1000 images | neo=736 non=264 | rate=0.7360 ✓\n  Train :  800 images | neo=589 non=211 | rate=0.7362 ✓\n  Val   :  100 images | neo= 74 non= 26 | rate=0.7400 ✓\n  Test  :  100 images | neo= 73 non= 27 | rate=0.7300 ✓\n\nCVC-ClinicDB size_category distribution (should roughly match across splits):\n  Full  : n= 612  {'large': 0.9, 'small': 0.095, 'diminutive': 0.005}\n  Train : n= 489  {'large': 0.9, 'small': 0.094, 'diminutive': 0.006}\n  Val   : n=  61  {'large': 0.902, 'small': 0.098}\n  Test  : n=  62  {'large': 0.903, 'small': 0.097}\n\nKvasir-SEG size_category distribution (should roughly match across splits):\n  Full  : n=1000  {'large': 0.969, 'small': 0.03, 'diminutive': 0.001}\n  Train : n= 800  {'large': 0.969, 'small': 0.03, 'diminutive': 0.001}\n  Val   : n= 100  {'large': 0.97, 'small': 0.03}\n  Test  : n= 100  {'large': 0.97, 'small': 0.03}\n\nRisk samples available:\n  Train: 800 (neo=589, non-neo=211)\n  Val: 100 (neo=74, non-neo=26)\n  Test: 100 (neo=73, non-neo=27)\n\n============================================================\nSAVED: train_split.csv, val_split.csv,\n       indist_test.csv, benchmark_test.csv\nBenchmark test now includes 6th dataset: CVC-300\nNext: model architecture (baseline segmentation)\n============================================================\n","output_type":"stream"}],"execution_count":1},{"cell_type":"code","source":"\"\"\"\nPolypSeg-EncoderComparison — Swappable-Encoder Baseline\n=========================================================\nPurpose: run a controlled encoder ablation. Decoder, loss, input size,\naugmentation, splits, optimizer/scheduler shape, and training budget\nare IDENTICAL across every run. Only CFG[\"encoder_name\"] changes.\n\nEncoders covered (8 total, verified to run at 352x352 before being\nincluded here -- see build_encoder() for the two fixes Swin needed):\n\n  Lightweight (<10M params):\n    - mobilenetv3_large_100   (~5.4M)   CNN, standard efficient baseline\n    - mit_b0                  (~3.4M)   lightweight transformer (SegFormer)\n\n  Mid-size (main comparison set):\n    - resnet34 / resnet50     (~21-25M) CNN floor\n    - res2net50_26w_4s        (~25M)    polyp-literature-standard encoder\n    - swin_tiny_patch4_window7_224 (~28M)  windowed-attention transformer\n    - mit_b3                  (~45M)    within-family transformer sibling to PVTv2\n    - pvt_v2_b3               (~45M)    PROPOSED encoder\n\n  Heavy (~>80M params):\n    - swin_base_patch4_window7_224 (~88M)  upper-bound scale reference\n\nIMPORTANT — verified compatibility notes (checked by actually running\nforward passes at 352x352 before writing this):\n  - mit_b0 / mit_b3 are NOT in timm. They come from segmentation_models_\n    pytorch (SMP)'s encoder registry. pip install segmentation_models_pytorch.\n  - swin_tiny/swin_base FAIL at 352x352 with timm's default config\n    (hardcoded for 224). Fix: pass img_size=352 explicitly at model\n    creation. Swin's timm output is also channels-last [B,H,W,C] --\n    every other encoder here is channels-first [B,C,H,W] -- so Swin\n    features are permuted before being handed to the decoder.\n  - All other encoders (MobileNetV3, ResNet, Res2Net, PVTv2) work at\n    352x352 with no changes.\n\nRequirements:\n    pip install torch torchvision timm segmentation_models_pytorch\n                albumentations opencv-python pandas numpy tqdm scikit-learn\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\nimport segmentation_models_pytorch as smp\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCFG = {\n    # Paths\n    \"train_csv\"         : \"train_split.csv\",\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n    \"save_dir\"          : \"checkpoints_encoder_compare\",\n\n    # ---- THE ONE THING THAT CHANGES BETWEEN RUNS ----\n    \"encoder_name\"       : \"pvt_v2_b3\",\n    # Valid values:\n    #   \"mobilenetv3_large_100\", \"mit_b0\",\n    #   \"resnet34\", \"resnet50\", \"res2net50_26w_4s\",\n    #   \"swin_tiny_patch4_window7_224\", \"mit_b3\", \"pvt_v2_b3\",\n    #   \"swin_base_patch4_window7_224\"\n    # ---------------------------------------------------\n\n    # Image — identical across all encoders\n    \"img_size\"          : 352,\n    \"mean\"              : [0.485, 0.456, 0.406],\n    \"std\"               : [0.229, 0.224, 0.225],\n\n    # Training — identical across all encoders\n    \"batch_size\"        : 8,\n    \"num_workers\"       : 2,\n    \"seed\"              : 42,\n    \"num_epochs\"        : 60,\n\n    # Optimizer — identical across all encoders\n    \"encoder_lr\"        : 1e-4,\n    \"decoder_lr\"        : 3e-4,\n    \"weight_decay\"      : 1e-4,\n    \"grad_clip\"         : 1.0,\n    \"min_lr\"            : 1e-6,\n\n    # Early stopping — identical across all encoders\n    \"patience\"          : 12,\n\n    # Decoder — identical across all encoders (this is the point)\n    \"dec_channels\"      : 64,\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\n\n# ============================================================\n# REPRODUCIBILITY\n# ============================================================\n\ndef set_seed(seed):\n    import random\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark     = False\n\n\n# ============================================================\n# DATA  (identical to baseline_segmentation.py — unchanged)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_train_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.ShiftScaleRotate(\n            shift_limit=0.05, scale_limit=0.1,\n            rotate_limit=30,\n            border_mode=cv2.BORDER_CONSTANT, value=0, p=0.5),\n        A.RandomBrightnessContrast(\n            brightness_limit=0.2, contrast_limit=0.2, p=0.4),\n        A.HueSaturationValue(\n            hue_shift_limit=10, sat_shift_limit=20,\n            val_shift_limit=10, p=0.3),\n        A.GaussNoise(var_limit=(10., 50.), p=0.2),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(\n                image.transpose(2, 0, 1)).float() / 255.\n            mask_t = torch.from_numpy(mask).float().unsqueeze(0) / 255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_dataloaders(cfg):\n    print(\"Loading CSVs...\")\n    train_df     = pd.read_csv(cfg['train_csv'])\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    # Confirms every run reads the exact same split -- prints a\n    # lightweight fingerprint so you can eyeball-match this across\n    # runs of different encoders and know the data didn't drift.\n    print(f\"  Split fingerprint -> \"\n          f\"train={len(train_df)} (hash={pd.util.hash_pandas_object(train_df['stem']).sum()}) \"\n          f\"val={len(val_df)} indist={len(indist_df)} bench={len(benchmark_df)}\")\n\n    sz = cfg['img_size']\n    nw = cfg['num_workers']\n    bs = cfg['batch_size']\n\n    train_loader = DataLoader(\n        PolypSegDataset(train_df, get_train_transform(sz)),\n        batch_size=bs, shuffle=True,\n        num_workers=nw, pin_memory=True, drop_last=True)\n    val_loader = DataLoader(\n        PolypSegDataset(val_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(\n        PolypSegDataset(indist_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(\n        PolypSegDataset(benchmark_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n\n    print(f\"  Train:{len(train_df)} | Val:{len(val_df)} | \"\n          f\"Indist:{len(indist_df)} | Bench:{len(benchmark_df)}\")\n\n    return {\n        'train'         : train_loader,\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# ENCODER FACTORY — the only thing that changes between runs\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    \"\"\"\n    Normalises every supported encoder to the same contract:\n    forward(x) -> tuple of 4 feature maps, channels-first [B,C,H,W],\n    at strides matching a 352x352 input: 88x88, 44x44, 22x22, 11x11\n    (except MobileNetV3, whose native 4-stage strides are documented\n    below and are NOT changed -- see note in build_encoder).\n    \"\"\"\n    def __init__(self, backbone, source, needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source          # 'timm' or 'smp'\n        self.needs_permute = needs_permute    # True for Swin (NHWC->NCHW)\n\n    def forward(self, x):\n        if self.source == 'timm':\n            feats = self.backbone(x)\n        else:  # smp — returns 6 stages incl. stem stages; keep last 4\n            all_feats = self.backbone(x)\n            feats = all_feats[-4:]\n\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n\n        return tuple(feats)\n\n\ndef build_encoder(name, pretrained=True):\n    \"\"\"\n    Returns (EncoderWrapper, enc_channels_tuple).\n    enc_channels_tuple is read from the ACTUAL model, never hardcoded,\n    since every encoder family has different channel counts at each\n    of its 4 stages -- this is what lets the decoder stay identical\n    while accepting any of the 8 encoders below.\n    \"\"\"\n    if name == \"mobilenetv3_large_100\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"resnet34\", \"resnet50\", \"res2net50_26w_4s\"):\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(1,2,3,4))\n        # out_indices (1,2,3,4) skips the very first stem-level stage\n        # (stride 2) so the 4 returned stages are at strides 4/8/16/32,\n        # matching the other encoders' 4-stage granularity.\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name == \"pvt_v2_b3\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"swin_tiny_patch4_window7_224\",\n                  \"swin_base_patch4_window7_224\"):\n        # FIX 1: img_size must be passed explicitly, or this errors\n        # outright on any input != 224x224.\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3),\n                              img_size=CFG[\"img_size\"])\n        # FIX 2: Swin's timm output is channels-last [B,H,W,C].\n        wrapper = EncoderWrapper(m, source='timm', needs_permute=True)\n\n    elif name in (\"mit_b0\", \"mit_b3\"):\n        # Not available in timm at all -- comes from SMP's encoder\n        # registry (SegFormer's Mix Transformer).\n        weights = \"imagenet\" if pretrained else None\n        m = smp.encoders.get_encoder(name, in_channels=3, depth=5,\n                                     weights=weights)\n        wrapper = EncoderWrapper(m, source='smp')\n\n    else:\n        raise ValueError(f\"Unknown encoder_name: {name}\")\n\n    # Determine actual output channels with a real forward pass —\n    # never hardcode this, since it must be correct for the decoder\n    # to wire up correctly regardless of which encoder is chosen.\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, CFG[\"img_size\"], CFG[\"img_size\"])\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n    wrapper.train()\n\n    return wrapper, enc_channels\n\n\n# ============================================================\n# MODEL COMPONENTS — identical across all encoders\n# ============================================================\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride,\n                      padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass PlainFPNDecoder(nn.Module):\n    \"\"\"\n    IDENTICAL across every encoder in this comparison. Only the first\n    four lateral conv input-channel counts adapt to enc_channels,\n    which is read dynamically from whichever encoder is plugged in --\n    the decoder's design (lateral -> top-down fuse -> single head,\n    width 64 throughout) never changes.\n    \"\"\"\n    def __init__(self, enc_channels, dec_channels=64):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, 1, 1),\n        )\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.lat4(f4)\n        p3 = self.lat3(f3)\n        p2 = self.lat2(f2)\n        p1 = self.lat1(f1)\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.fuse3(torch.cat([up(p4, p3), p3], dim=1))\n        p2f = self.fuse2(torch.cat([up(p3f, p2), p2], dim=1))\n        p1f = self.fuse1(torch.cat([up(p2f, p1), p1], dim=1))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        mask_logits = self.seg_head(out)\n        return mask_logits\n\n\nclass PolypSegEncoderCompare(nn.Module):\n    \"\"\"\n    Same architecture skeleton as PolypSegBaseline, but the encoder\n    is built via build_encoder() so it's swappable via CFG alone.\n    Decoder, head, and everything downstream is byte-for-byte\n    identical regardless of which encoder is selected.\n    \"\"\"\n    def __init__(self, encoder_name, pretrained=True, dec_channels=64):\n        super().__init__()\n        self.encoder, enc_channels = build_encoder(encoder_name, pretrained)\n        self.decoder = PlainFPNDecoder(enc_channels, dec_channels)\n        self.encoder_name = encoder_name\n        self.enc_channels = enc_channels\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        mask_logits = self.decoder(features, (H, W))\n        return {'mask_logits': mask_logits}\n\n\n# ============================================================\n# LOSS — identical across all encoders\n# ============================================================\n\ndef dice_loss(pred, target, smooth=1.0):\n    p = torch.sigmoid(pred)\n    p = p.view(p.size(0), -1)\n    t = target.view(target.size(0), -1)\n    i = (p * t).sum(1)\n    return 1 - (2 * i + smooth) / (p.sum(1) + t.sum(1) + smooth)\n\n\ndef bce_dice_loss(pred, target):\n    bce = F.binary_cross_entropy_with_logits(\n        pred, target, reduction='mean')\n    return bce + dice_loss(pred, target).mean()\n\n\n# ============================================================\n# METRICS — now WITH per-dataset breakdown surfaced every epoch\n# ============================================================\n\nclass MetricTracker:\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses      = []\n        self.dice_scores = []\n        self.iou_scores  = []\n        self.prec_scores = []\n        self.rec_scores  = []\n        self.dataset_results = {}\n\n    @torch.no_grad()\n    def update(self, outputs, batch, loss):\n        self.losses.append(loss.item())\n\n        pred = (torch.sigmoid(outputs['mask_logits']) > 0.5).float()\n        tgt  = batch['mask'].to(pred.device)\n\n        pf = pred.view(pred.size(0), -1)\n        tf = tgt.view(tgt.size(0), -1)\n        tp = (pf * tf).sum(1)\n        fp = pf.sum(1) - tp\n        fn = tf.sum(1) - tp\n\n        dice = (2 * tp / (2 * tp + fp + fn + 1e-6)).mean().item()\n        iou  = (tp / (tp + fp + fn + 1e-6)).mean().item()\n        prec = (tp / (tp + fp + 1e-6)).mean().item()\n        rec  = (tp / (tp + fn + 1e-6)).mean().item()\n\n        self.dice_scores.append(dice)\n        self.iou_scores.append(iou)\n        self.prec_scores.append(prec)\n        self.rec_scores.append(rec)\n\n        for i, ds in enumerate(batch['dataset']):\n            if ds not in self.dataset_results:\n                self.dataset_results[ds] = {\n                    'dice': [], 'iou': [], 'prec': [], 'rec': []}\n            d = self.dataset_results[ds]\n            p_i, t_i = pred[i:i+1].view(-1), tgt[i:i+1].view(-1)\n            tp_i = (p_i * t_i).sum()\n            fp_i = p_i.sum() - tp_i\n            fn_i = t_i.sum() - tp_i\n            d['dice'].append((2*tp_i/(2*tp_i+fp_i+fn_i+1e-6)).item())\n            d['iou'].append((tp_i/(tp_i+fp_i+fn_i+1e-6)).item())\n            d['prec'].append((tp_i/(tp_i+fp_i+1e-6)).item())\n            d['rec'].append((tp_i/(tp_i+fn_i+1e-6)).item())\n\n    def compute(self):\n        gross = dict(\n            loss      = np.mean(self.losses),\n            dice      = np.mean(self.dice_scores),\n            iou       = np.mean(self.iou_scores),\n            precision = np.mean(self.prec_scores),\n            recall    = np.mean(self.rec_scores),\n        )\n        # Per-dataset breakdown now included in every compute() call --\n        # not just at final evaluation -- so it can be logged every epoch.\n        per_dataset = {}\n        for ds_key, d in self.dataset_results.items():\n            per_dataset[ds_key] = dict(\n                n         = len(d['dice']),\n                dice      = np.mean(d['dice']),\n                iou       = np.mean(d['iou']),\n                precision = np.mean(d['prec']),\n                recall    = np.mean(d['rec']),\n            )\n        gross['per_dataset'] = per_dataset\n        return gross\n\n\n# ============================================================\n# OPTIMIZER — identical across all encoders\n# ============================================================\n\ndef build_optimizer(model, cfg):\n    enc_params = list(model.encoder.parameters())\n    enc_ids    = {id(p) for p in enc_params}\n    other      = [p for p in model.parameters() if id(p) not in enc_ids]\n    opt = torch.optim.AdamW([\n        {'params': enc_params, 'lr': cfg['encoder_lr']},\n        {'params': other,      'lr': cfg['decoder_lr']},\n    ], weight_decay=cfg['weight_decay'])\n    sch = torch.optim.lr_scheduler.CosineAnnealingLR(\n        opt, T_max=cfg['num_epochs'], eta_min=cfg['min_lr'])\n    return opt, sch\n\n\n# ============================================================\n# TRAIN / VALIDATE\n# ============================================================\n\ndef train_one_epoch(model, loader, optimizer, device, cfg):\n    model.train()\n    tracker = MetricTracker()\n    pbar = tqdm(loader, desc='  Train', leave=False, ncols=100)\n    for batch in pbar:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n\n        optimizer.zero_grad()\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(\n            model.parameters(), cfg['grad_clip'])\n        optimizer.step()\n\n        tracker.update(outputs, batch, loss)\n        pbar.set_postfix(\n            loss=f\"{loss.item():.4f}\",\n            dice=f\"{tracker.dice_scores[-1]:.4f}\")\n    return tracker.compute()\n\n\n@torch.no_grad()\ndef validate(model, loader, device):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in loader:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n    return tracker.compute()\n\n\n# ============================================================\n# CHECKPOINTING\n# ============================================================\n\nclass CheckpointManager:\n    def __init__(self, save_dir, patience):\n        self.save_dir   = Path(save_dir)\n        self.save_dir.mkdir(parents=True, exist_ok=True)\n        self.patience   = patience\n        self.best_dice  = 0.\n        self.best_epoch = 0\n        self.counter    = 0\n\n    def step(self, model, optimizer, scheduler, epoch, metrics):\n        dice     = metrics['dice']\n        improved = dice > self.best_dice\n        if improved:\n            self.best_dice  = dice\n            self.best_epoch = epoch\n            self.counter    = 0\n            torch.save(dict(\n                epoch=epoch, model=model.state_dict(),\n                optimizer=optimizer.state_dict(),\n                scheduler=scheduler.state_dict(),\n                best_dice=self.best_dice,\n                encoder_name=model.encoder_name,\n            ), self.save_dir / 'best_model.pth')\n            print(f\"  \\u2713 Saved best (dice={dice:.4f})\")\n        else:\n            self.counter += 1\n        return self.counter >= self.patience\n\n    def load_best(self, model, device):\n        ck = torch.load(\n            self.save_dir / 'best_model.pth',\n            map_location=device, weights_only=False)\n        model.load_state_dict(ck['model'])\n        print(f\"  Loaded best epoch {ck['epoch']} \"\n              f\"(dice={ck['best_dice']:.4f})\")\n        return model\n\n\n# ============================================================\n# LOGGING — per-epoch, gross + per-dataset, printed AND saved to CSV\n# ============================================================\n\ndef print_epoch_summary(epoch, num_epochs, train_m, val_m, lr):\n    print(f\"\\nEpoch [{epoch+1:>3}/{num_epochs}]  LR={lr:.2e}\")\n\n    print(f\"  {'GROSS METRIC':14s}  {'Train':>8}  {'Val':>8}\")\n    print(f\"  {'-'*36}\")\n    for key, label in [\n        ('loss',      'Loss'),\n        ('dice',      'Dice'),\n        ('iou',       'IoU'),\n        ('precision', 'Precision'),\n        ('recall',    'Recall'),\n    ]:\n        print(f\"  {label:14s}  {train_m.get(key,0):8.4f}  {val_m.get(key,0):8.4f}\")\n\n    # Per-dataset breakdown for validation (train per-dataset is also\n    # available in train_m['per_dataset'] if you want it printed too --\n    # kept to val here to keep the per-epoch log readable).\n    if val_m.get('per_dataset'):\n        print(f\"  {'-'*36}\")\n        print(f\"  VAL PER-DATASET:\")\n        for ds_key, d in val_m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"    {ds_name:15s} n={d['n']:>4}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n\ndef append_epoch_log(log_path, epoch, encoder_name, train_m, val_m, lr):\n    \"\"\"\n    Saves gross + per-dataset metrics for this epoch to a running CSV.\n    One row per (epoch, split, dataset) so it's easy to filter/plot\n    later without re-parsing terminal output.\n    \"\"\"\n    rows = []\n    for split_name, m in [('train', train_m), ('val', val_m)]:\n        rows.append(dict(\n            encoder=encoder_name, epoch=epoch+1, split=split_name,\n            dataset='ALL', lr=lr,\n            loss=m.get('loss'), dice=m.get('dice'), iou=m.get('iou'),\n            precision=m.get('precision'), recall=m.get('recall'),\n        ))\n        for ds_key, d in m.get('per_dataset', {}).items():\n            rows.append(dict(\n                encoder=encoder_name, epoch=epoch+1, split=split_name,\n                dataset=DATASET_INFO.get(ds_key, ds_key), lr=lr,\n                loss=None, dice=d['dice'], iou=d['iou'],\n                precision=d['precision'], recall=d['recall'],\n            ))\n\n    df = pd.DataFrame(rows)\n    header = not Path(log_path).exists()\n    df.to_csv(log_path, mode='a', header=header, index=False)\n\n\n# ============================================================\n# FINAL EVALUATION\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_single_loader(model, loader, device, split_name):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in tqdm(loader, desc=f'  Eval {split_name}', leave=False):\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n\n    m = tracker.compute()\n\n    print(f\"\\n{'='*60}\\n  {split_name.upper()}\\n{'='*60}\")\n    print(f\"  Dice     : {m['dice']:.4f}\")\n    print(f\"  IoU      : {m['iou']:.4f}\")\n    print(f\"  Precision: {m['precision']:.4f}\")\n    print(f\"  Recall   : {m['recall']:.4f}\")\n\n    if m['per_dataset']:\n        print(f\"\\n  PER-DATASET BREAKDOWN\")\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"  [{ds_name}] n={d['n']}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n    return m\n\n\n@torch.no_grad()\ndef evaluate_all_datasets(model, data, device, save_dir, encoder_name):\n    print(\"\\n\" + \"=\"*60)\n    print(f\"FINAL EVALUATION \\u2014 encoder={encoder_name}\")\n    print(\"=\"*60)\n\n    results = {}\n    results['val']       = evaluate_single_loader(model, data['val'], device, 'VALIDATION SET')\n    results['indist']    = evaluate_single_loader(model, data['indist_test'], device, 'IN-DISTRIBUTION TEST')\n    results['benchmark'] = evaluate_single_loader(model, data['benchmark_test'], device, 'BENCHMARK (UNSEEN)')\n\n    rows = []\n    total_params = sum(p.numel() for p in model.parameters())\n    for split, m in results.items():\n        rows.append(dict(\n            encoder=encoder_name, params=total_params, split=split,\n            dice=round(m['dice'],4), iou=round(m['iou'],4),\n            precision=round(m['precision'],4), recall=round(m['recall'],4),\n        ))\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            rows.append(dict(\n                encoder=encoder_name, params=total_params,\n                split=f\"{split}_{ds_name}\",\n                dice=round(d['dice'],4), iou=round(d['iou'],4),\n                precision=round(d['precision'],4), recall=round(d['recall'],4),\n            ))\n\n    # Append to a SHARED comparison CSV across all 8 encoder runs\n    comparison_path = Path(save_dir) / 'encoder_comparison_results.csv'\n    df = pd.DataFrame(rows)\n    header = not comparison_path.exists()\n    df.to_csv(comparison_path, mode='a', header=header, index=False)\n    print(f\"\\n  Results appended to: {comparison_path}\")\n\n    return results\n\n\n# ============================================================\n# MAIN TRAINING\n# ============================================================\n\ndef train(cfg):\n    set_seed(cfg['seed'])\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n    print(f\"Encoder: {cfg['encoder_name']}\")\n\n    data = create_dataloaders(cfg)\n\n    model = PolypSegEncoderCompare(\n        encoder_name=cfg['encoder_name'],\n        pretrained=True,\n        dec_channels=cfg['dec_channels'],\n    ).to(device)\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"Encoder output channels: {model.enc_channels}\")\n    print(f\"Params: {total/1e6:.2f}M total\")\n\n    optimizer, scheduler = build_optimizer(model, cfg)\n    save_dir = Path(cfg['save_dir'])\n    ckpt = CheckpointManager(save_dir / cfg['encoder_name'], cfg['patience'])\n    epoch_log_path = save_dir / f\"epoch_log_{cfg['encoder_name']}.csv\"\n\n    print(\"\\n\" + \"=\"*60)\n    print(f\"TRAINING — encoder={cfg['encoder_name']}\")\n    print(\"  Decoder: plain FPN, no attention (IDENTICAL across all encoders)\")\n    print(\"  Loss   : BCE + Dice only (IDENTICAL across all encoders)\")\n    print(\"=\"*60)\n\n    for epoch in range(cfg['num_epochs']):\n        train_m = train_one_epoch(model, data['train'], optimizer, device, cfg)\n        val_m   = validate(model, data['val'], device)\n\n        scheduler.step()\n        lr = optimizer.param_groups[0]['lr']\n\n        print_epoch_summary(epoch, cfg['num_epochs'], train_m, val_m, lr)\n        append_epoch_log(epoch_log_path, epoch, cfg['encoder_name'], train_m, val_m, lr)\n\n        stop = ckpt.step(model, optimizer, scheduler, epoch, val_m)\n        if stop:\n            print(f\"\\nEarly stopping at epoch {epoch+1}. \"\n                  f\"Best: epoch {ckpt.best_epoch+1} (dice={ckpt.best_dice:.4f})\")\n            break\n\n    model = ckpt.load_best(model, device)\n    results = evaluate_all_datasets(model, data, device, cfg['save_dir'], cfg['encoder_name'])\n\n    return model, results\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\nif __name__ == '__main__':\n    print(\"=\"*60)\n    print(f\"SANITY CHECK \\u2014 encoder={CFG['encoder_name']}\")\n    print(\"=\"*60)\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    model  = PolypSegEncoderCompare(\n        encoder_name=CFG['encoder_name'], pretrained=False).to(device)\n\n    dummy_img = torch.randn(2, 3, CFG['img_size'], CFG['img_size']).to(device)\n    with torch.no_grad():\n        out = model(dummy_img)\n    print(\"Output shapes (forward pass):\")\n    for k, v in out.items():\n        print(f\"  {k:15s}: {list(v.shape)}\")\n\n    dummy_target = torch.randint(0, 2, (2, 1, CFG['img_size'], CFG['img_size'])).float().to(device)\n    loss = bce_dice_loss(out['mask_logits'], dummy_target)\n    print(f\"\\nLoss (random init, random target): {loss.item():.4f}\")\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"\\nEncoder output channels: {model.enc_channels}\")\n    print(f\"Total params: {total/1e6:.2f}M\")\n    print(\"\\nSanity check passed \\u2713\")\n    print(\"=\"*60)\n    print(\"\\nStarting training...\")\n    model, results = train(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-17T10:33:45.844226Z","iopub.execute_input":"2026-08-17T10:33:45.844718Z","iopub.status.idle":"2026-08-17T15:00:46.084535Z","shell.execute_reply.started":"2026-08-17T10:33:45.844682Z","shell.execute_reply":"2026-08-17T15:00:46.083389Z"}},"outputs":[{"name":"stdout","text":"============================================================\nSANITY CHECK — encoder=pvt_v2_b3\n============================================================\nOutput shapes (forward pass):\n  mask_logits    : [2, 1, 352, 352]\n\nLoss (random init, random target): 1.1868\n\nEncoder output channels: (64, 128, 320, 512)\nTotal params: 45.57M\n\nSanity check passed ✓\n============================================================\n\nStarting training...\nDevice: cuda\nEncoder: pvt_v2_b3\nLoading CSVs...\n  Split fingerprint -> train=2089 (hash=6780967082095866867) val=261 indist=262 bench=636\n  Train:2089 | Val:261 | Indist:262 | Bench:636\n","output_type":"stream"},{"name":"stderr","text":"Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"model.safetensors:   0%|          | 0.00/181M [00:00<?, ?B/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"ffe363eed6b943ccaec63d7ed572f745"}},"metadata":{}},{"name":"stdout","text":"Encoder output channels: (64, 128, 320, 512)\nParams: 45.57M total\n\n============================================================\nTRAINING — encoder=pvt_v2_b3\n  Decoder: plain FPN, no attention (IDENTICAL across all encoders)\n  Loss   : BCE + Dice only (IDENTICAL across all encoders)\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  1/60]  LR=9.99e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.8753    0.5290\n  Dice              0.7216    0.8549\n  IoU               0.6183    0.7714\n  Precision         0.6855    0.8189\n  Recall            0.8664    0.9355\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8383  IoU=0.7451  Prec=0.7639  Rec=0.9677\n    CVC-ClinicDB    n=  61  Dice=0.8557  IoU=0.7676  Prec=0.8278  Rec=0.9289\n    Kvasir-SEG      n= 100  Dice=0.8765  IoU=0.8049  Prec=0.8715  Rec=0.9153\n  ✓ Saved best (dice=0.8549)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  2/60]  LR=9.97e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.5001    0.3309\n  Dice              0.8187    0.8830\n  IoU               0.7376    0.8105\n  Precision         0.8093    0.8827\n  Recall            0.8826    0.9094\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8832  IoU=0.8134  Prec=0.8634  Rec=0.9202\n    CVC-ClinicDB    n=  61  Dice=0.8856  IoU=0.8126  Prec=0.9006  Rec=0.8992\n    Kvasir-SEG      n= 100  Dice=0.8830  IoU=0.8090  Prec=0.8918  Rec=0.9070\n  ✓ Saved best (dice=0.8830)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  3/60]  LR=9.94e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.3289    0.1951\n  Dice              0.8551    0.9094\n  IoU               0.7829    0.8488\n  Precision         0.8674    0.9218\n  Recall            0.8823    0.9169\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9105  IoU=0.8506  Prec=0.9162  Rec=0.9264\n    CVC-ClinicDB    n=  61  Dice=0.9177  IoU=0.8571  Prec=0.9297  Rec=0.9199\n    Kvasir-SEG      n= 100  Dice=0.9063  IoU=0.8452  Prec=0.9256  Rec=0.9088\n  ✓ Saved best (dice=0.9094)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  4/60]  LR=9.89e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2631    0.1686\n  Dice              0.8642    0.9183\n  IoU               0.7966    0.8622\n  Precision         0.8762    0.9325\n  Recall            0.8868    0.9215\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9199  IoU=0.8658  Prec=0.9256  Rec=0.9312\n    CVC-ClinicDB    n=  61  Dice=0.9217  IoU=0.8626  Prec=0.9423  Rec=0.9149\n    Kvasir-SEG      n= 100  Dice=0.9170  IoU=0.8617  Prec=0.9375  Rec=0.9168\n  ✓ Saved best (dice=0.9183)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  5/60]  LR=9.83e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2312    0.1731\n  Dice              0.8778    0.9006\n  IoU               0.8132    0.8353\n  Precision         0.8902    0.8737\n  Recall            0.8970    0.9537\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8895  IoU=0.8218  Prec=0.8405  Rec=0.9757\n    CVC-ClinicDB    n=  61  Dice=0.9151  IoU=0.8505  Prec=0.8942  Rec=0.9481\n    Kvasir-SEG      n= 100  Dice=0.9039  IoU=0.8408  Prec=0.8954  Rec=0.9369\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  6/60]  LR=9.76e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2116    0.1595\n  Dice              0.8824    0.9191\n  IoU               0.8205    0.8645\n  Precision         0.8931    0.9414\n  Recall            0.9005    0.9160\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9263  IoU=0.8760  Prec=0.9327  Rec=0.9342\n    CVC-ClinicDB    n=  61  Dice=0.9276  IoU=0.8732  Prec=0.9472  Rec=0.9199\n    Kvasir-SEG      n= 100  Dice=0.9099  IoU=0.8519  Prec=0.9495  Rec=0.8975\n  ✓ Saved best (dice=0.9191)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  7/60]  LR=9.67e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1939    0.1605\n  Dice              0.8909    0.9140\n  IoU               0.8313    0.8588\n  Precision         0.9066    0.9268\n  Recall            0.9034    0.9226\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9338  IoU=0.8835  Prec=0.9497  Rec=0.9286\n    CVC-ClinicDB    n=  61  Dice=0.9353  IoU=0.8851  Prec=0.9385  Rec=0.9399\n    Kvasir-SEG      n= 100  Dice=0.8898  IoU=0.8273  Prec=0.9014  Rec=0.9145\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  8/60]  LR=9.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1850    0.1263\n  Dice              0.8952    0.9316\n  IoU               0.8364    0.8823\n  Precision         0.9085    0.9450\n  Recall            0.9081    0.9320\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9452  IoU=0.9019  Prec=0.9475  Rec=0.9503\n    CVC-ClinicDB    n=  61  Dice=0.9359  IoU=0.8867  Prec=0.9465  Rec=0.9352\n    Kvasir-SEG      n= 100  Dice=0.9178  IoU=0.8637  Prec=0.9437  Rec=0.9143\n  ✓ Saved best (dice=0.9316)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  9/60]  LR=9.46e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1855    0.1250\n  Dice              0.8922    0.9290\n  IoU               0.8346    0.8800\n  Precision         0.9062    0.9422\n  Recall            0.9033    0.9318\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9354  IoU=0.8900  Prec=0.9375  Rec=0.9496\n    CVC-ClinicDB    n=  61  Dice=0.9414  IoU=0.8934  Prec=0.9479  Rec=0.9412\n    Kvasir-SEG      n= 100  Dice=0.9189  IoU=0.8668  Prec=0.9463  Rec=0.9129\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 10/60]  LR=9.34e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1821    0.1633\n  Dice              0.8946    0.9111\n  IoU               0.8372    0.8580\n  Precision         0.9119    0.9045\n  Recall            0.9025    0.9459\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9270  IoU=0.8784  Prec=0.9293  Rec=0.9450\n    CVC-ClinicDB    n=  61  Dice=0.9182  IoU=0.8689  Prec=0.9125  Rec=0.9505\n    Kvasir-SEG      n= 100  Dice=0.8970  IoU=0.8374  Prec=0.8822  Rec=0.9460\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 11/60]  LR=9.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1618    0.1349\n  Dice              0.9051    0.9237\n  IoU               0.8495    0.8717\n  Precision         0.9170    0.9511\n  Recall            0.9123    0.9137\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9287  IoU=0.8804  Prec=0.9476  Rec=0.9282\n    CVC-ClinicDB    n=  61  Dice=0.9417  IoU=0.8944  Prec=0.9635  Rec=0.9263\n    Kvasir-SEG      n= 100  Dice=0.9110  IoU=0.8533  Prec=0.9501  Rec=0.8950\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 12/60]  LR=9.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1660    0.1462\n  Dice              0.9023    0.9259\n  IoU               0.8467    0.8749\n  Precision         0.9174    0.9353\n  Recall            0.9105    0.9346\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9409  IoU=0.8977  Prec=0.9462  Rec=0.9476\n    CVC-ClinicDB    n=  61  Dice=0.9381  IoU=0.8896  Prec=0.9572  Rec=0.9269\n    Kvasir-SEG      n= 100  Dice=0.9052  IoU=0.8460  Prec=0.9122  Rec=0.9288\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 13/60]  LR=8.90e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1546    0.1280\n  Dice              0.9104    0.9287\n  IoU               0.8573    0.8810\n  Precision         0.9216    0.9391\n  Recall            0.9170    0.9365\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9324  IoU=0.8909  Prec=0.9458  Rec=0.9408\n    CVC-ClinicDB    n=  61  Dice=0.9441  IoU=0.8977  Prec=0.9475  Rec=0.9459\n    Kvasir-SEG      n= 100  Dice=0.9185  IoU=0.8651  Prec=0.9299  Rec=0.9294\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 14/60]  LR=8.73e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1454    0.1213\n  Dice              0.9147    0.9320\n  IoU               0.8624    0.8864\n  Precision         0.9272    0.9368\n  Recall            0.9191    0.9448\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9440  IoU=0.9028  Prec=0.9411  Rec=0.9577\n    CVC-ClinicDB    n=  61  Dice=0.9506  IoU=0.9078  Prec=0.9496  Rec=0.9545\n    Kvasir-SEG      n= 100  Dice=0.9133  IoU=0.8624  Prec=0.9295  Rec=0.9277\n  ✓ Saved best (dice=0.9320)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 15/60]  LR=8.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1435    0.1213\n  Dice              0.9141    0.9310\n  IoU               0.8631    0.8823\n  Precision         0.9253    0.9395\n  Recall            0.9193    0.9377\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9465  IoU=0.9055  Prec=0.9455  Rec=0.9560\n    CVC-ClinicDB    n=  61  Dice=0.9447  IoU=0.8990  Prec=0.9459  Rec=0.9491\n    Kvasir-SEG      n= 100  Dice=0.9091  IoU=0.8518  Prec=0.9315  Rec=0.9144\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 16/60]  LR=8.36e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1416    0.1353\n  Dice              0.9149    0.9253\n  IoU               0.8635    0.8742\n  Precision         0.9246    0.9373\n  Recall            0.9225    0.9309\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9428  IoU=0.9004  Prec=0.9405  Rec=0.9555\n    CVC-ClinicDB    n=  61  Dice=0.9433  IoU=0.8968  Prec=0.9544  Rec=0.9382\n    Kvasir-SEG      n= 100  Dice=0.9002  IoU=0.8386  Prec=0.9273  Rec=0.9038\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 17/60]  LR=8.17e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1323    0.1115\n  Dice              0.9206    0.9366\n  IoU               0.8708    0.8908\n  Precision         0.9290    0.9489\n  Recall            0.9277    0.9371\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9439  IoU=0.9026  Prec=0.9491  Rec=0.9501\n    CVC-ClinicDB    n=  61  Dice=0.9516  IoU=0.9101  Prec=0.9517  Rec=0.9543\n    Kvasir-SEG      n= 100  Dice=0.9230  IoU=0.8708  Prec=0.9487  Rec=0.9164\n  ✓ Saved best (dice=0.9366)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 18/60]  LR=7.96e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1348    0.1085\n  Dice              0.9180    0.9399\n  IoU               0.8687    0.8960\n  Precision         0.9270    0.9521\n  Recall            0.9244    0.9397\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9531  IoU=0.9149  Prec=0.9630  Rec=0.9490\n    CVC-ClinicDB    n=  61  Dice=0.9519  IoU=0.9106  Prec=0.9502  Rec=0.9570\n    Kvasir-SEG      n= 100  Dice=0.9224  IoU=0.8722  Prec=0.9447  Rec=0.9226\n  ✓ Saved best (dice=0.9399)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 19/60]  LR=7.75e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1298    0.1157\n  Dice              0.9192    0.9371\n  IoU               0.8720    0.8904\n  Precision         0.9270    0.9532\n  Recall            0.9250    0.9322\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9486  IoU=0.9080  Prec=0.9617  Rec=0.9422\n    CVC-ClinicDB    n=  61  Dice=0.9494  IoU=0.9067  Prec=0.9510  Rec=0.9519\n    Kvasir-SEG      n= 100  Dice=0.9196  IoU=0.8652  Prec=0.9475  Rec=0.9119\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 20/60]  LR=7.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1286    0.1020\n  Dice              0.9217    0.9423\n  IoU               0.8732    0.8989\n  Precision         0.9302    0.9576\n  Recall            0.9273    0.9369\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9559  IoU=0.9192  Prec=0.9700  Rec=0.9465\n    CVC-ClinicDB    n=  61  Dice=0.9521  IoU=0.9109  Prec=0.9594  Rec=0.9480\n    Kvasir-SEG      n= 100  Dice=0.9255  IoU=0.8752  Prec=0.9458  Rec=0.9239\n  ✓ Saved best (dice=0.9423)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 21/60]  LR=7.30e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1258    0.1102\n  Dice              0.9207    0.9394\n  IoU               0.8727    0.8960\n  Precision         0.9287    0.9456\n  Recall            0.9254    0.9456\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9489  IoU=0.9111  Prec=0.9485  Rec=0.9598\n    CVC-ClinicDB    n=  61  Dice=0.9521  IoU=0.9108  Prec=0.9447  Rec=0.9621\n    Kvasir-SEG      n= 100  Dice=0.9248  IoU=0.8759  Prec=0.9452  Rec=0.9245\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 22/60]  LR=7.06e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1222    0.1086\n  Dice              0.9231    0.9384\n  IoU               0.8770    0.8933\n  Precision         0.9292    0.9543\n  Recall            0.9273    0.9340\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9512  IoU=0.9130  Prec=0.9517  Rec=0.9583\n    CVC-ClinicDB    n=  61  Dice=0.9482  IoU=0.9044  Prec=0.9582  Rec=0.9421\n    Kvasir-SEG      n= 100  Dice=0.9225  IoU=0.8710  Prec=0.9563  Rec=0.9085\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 23/60]  LR=6.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1142    0.1110\n  Dice              0.9308    0.9393\n  IoU               0.8848    0.8948\n  Precision         0.9367    0.9505\n  Recall            0.9355    0.9394\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9567  IoU=0.9207  Prec=0.9512  Rec=0.9670\n    CVC-ClinicDB    n=  61  Dice=0.9506  IoU=0.9079  Prec=0.9509  Rec=0.9531\n    Kvasir-SEG      n= 100  Dice=0.9181  IoU=0.8652  Prec=0.9514  Rec=0.9075\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 24/60]  LR=6.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1177    0.1038\n  Dice              0.9293    0.9428\n  IoU               0.8838    0.8999\n  Precision         0.9391    0.9487\n  Recall            0.9322    0.9463\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9555  IoU=0.9183  Prec=0.9585  Rec=0.9574\n    CVC-ClinicDB    n=  61  Dice=0.9525  IoU=0.9110  Prec=0.9433  Rec=0.9637\n    Kvasir-SEG      n= 100  Dice=0.9274  IoU=0.8791  Prec=0.9445  Rec=0.9282\n  ✓ Saved best (dice=0.9428)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 25/60]  LR=6.33e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1081    0.1082\n  Dice              0.9325    0.9404\n  IoU               0.8887    0.8979\n  Precision         0.9385    0.9450\n  Recall            0.9361    0.9476\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9511  IoU=0.9140  Prec=0.9541  Rec=0.9568\n    CVC-ClinicDB    n=  61  Dice=0.9539  IoU=0.9133  Prec=0.9416  Rec=0.9682\n    Kvasir-SEG      n= 100  Dice=0.9253  IoU=0.8774  Prec=0.9407  Rec=0.9299\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 26/60]  LR=6.08e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1073    0.1063\n  Dice              0.9339    0.9423\n  IoU               0.8888    0.8998\n  Precision         0.9392    0.9478\n  Recall            0.9391    0.9479\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9524  IoU=0.9157  Prec=0.9581  Rec=0.9549\n    CVC-ClinicDB    n=  61  Dice=0.9546  IoU=0.9144  Prec=0.9469  Rec=0.9643\n    Kvasir-SEG      n= 100  Dice=0.9271  IoU=0.8788  Prec=0.9401  Rec=0.9334\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 27/60]  LR=5.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1144    0.1073\n  Dice              0.9279    0.9419\n  IoU               0.8832    0.8985\n  Precision         0.9352    0.9547\n  Recall            0.9322    0.9397\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9519  IoU=0.9132  Prec=0.9557  Rec=0.9550\n    CVC-ClinicDB    n=  61  Dice=0.9554  IoU=0.9157  Prec=0.9506  Rec=0.9620\n    Kvasir-SEG      n= 100  Dice=0.9263  IoU=0.8771  Prec=0.9578  Rec=0.9141\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 28/60]  LR=5.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1027    0.1078\n  Dice              0.9341    0.9413\n  IoU               0.8909    0.8982\n  Precision         0.9393    0.9516\n  Recall            0.9374    0.9427\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9513  IoU=0.9139  Prec=0.9598  Rec=0.9519\n    CVC-ClinicDB    n=  61  Dice=0.9554  IoU=0.9156  Prec=0.9512  Rec=0.9613\n    Kvasir-SEG      n= 100  Dice=0.9247  IoU=0.8749  Prec=0.9451  Rec=0.9248\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 29/60]  LR=5.31e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1058    0.1150\n  Dice              0.9321    0.9402\n  IoU               0.8889    0.8970\n  Precision         0.9379    0.9467\n  Recall            0.9348    0.9467\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9492  IoU=0.9120  Prec=0.9515  Rec=0.9583\n    CVC-ClinicDB    n=  61  Dice=0.9518  IoU=0.9104  Prec=0.9486  Rec=0.9580\n    Kvasir-SEG      n= 100  Dice=0.9264  IoU=0.8771  Prec=0.9422  Rec=0.9306\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 30/60]  LR=5.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0982    0.1146\n  Dice              0.9365    0.9381\n  IoU               0.8950    0.8947\n  Precision         0.9431    0.9401\n  Recall            0.9385    0.9504\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9491  IoU=0.9115  Prec=0.9485  Rec=0.9602\n    CVC-ClinicDB    n=  61  Dice=0.9487  IoU=0.9066  Prec=0.9424  Rec=0.9596\n    Kvasir-SEG      n= 100  Dice=0.9226  IoU=0.8735  Prec=0.9319  Rec=0.9369\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 31/60]  LR=4.79e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1063    0.1040\n  Dice              0.9326    0.9413\n  IoU               0.8903    0.8997\n  Precision         0.9379    0.9451\n  Recall            0.9365    0.9509\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9490  IoU=0.9139  Prec=0.9464  Rec=0.9657\n    CVC-ClinicDB    n=  61  Dice=0.9478  IoU=0.9046  Prec=0.9378  Rec=0.9625\n    Kvasir-SEG      n= 100  Dice=0.9325  IoU=0.8871  Prec=0.9519  Rec=0.9308\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 32/60]  LR=4.53e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0982    0.1090\n  Dice              0.9371    0.9397\n  IoU               0.8954    0.8980\n  Precision         0.9415    0.9414\n  Recall            0.9419    0.9527\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9440  IoU=0.9062  Prec=0.9388  Rec=0.9644\n    CVC-ClinicDB    n=  61  Dice=0.9540  IoU=0.9141  Prec=0.9470  Rec=0.9635\n    Kvasir-SEG      n= 100  Dice=0.9286  IoU=0.8831  Prec=0.9414  Rec=0.9373\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 33/60]  LR=4.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0935    0.1090\n  Dice              0.9406    0.9420\n  IoU               0.9000    0.8993\n  Precision         0.9445    0.9517\n  Recall            0.9444    0.9433\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9554  IoU=0.9203  Prec=0.9648  Rec=0.9537\n    CVC-ClinicDB    n=  61  Dice=0.9512  IoU=0.9108  Prec=0.9416  Rec=0.9652\n    Kvasir-SEG      n= 100  Dice=0.9254  IoU=0.8748  Prec=0.9465  Rec=0.9226\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 34/60]  LR=4.02e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0981    0.1112\n  Dice              0.9371    0.9418\n  IoU               0.8964    0.8999\n  Precision         0.9430    0.9421\n  Recall            0.9406    0.9538\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9509  IoU=0.9152  Prec=0.9478  Rec=0.9656\n    CVC-ClinicDB    n=  61  Dice=0.9550  IoU=0.9154  Prec=0.9493  Rec=0.9627\n    Kvasir-SEG      n= 100  Dice=0.9276  IoU=0.8790  Prec=0.9340  Rec=0.9398\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 35/60]  LR=3.77e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0938    0.1061\n  Dice              0.9410    0.9436\n  IoU               0.9009    0.9031\n  Precision         0.9470    0.9534\n  Recall            0.9429    0.9462\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9504  IoU=0.9154  Prec=0.9514  Rec=0.9622\n    CVC-ClinicDB    n=  61  Dice=0.9534  IoU=0.9129  Prec=0.9536  Rec=0.9556\n    Kvasir-SEG      n= 100  Dice=0.9336  IoU=0.8886  Prec=0.9567  Rec=0.9274\n  ✓ Saved best (dice=0.9436)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 36/60]  LR=3.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0891    0.1016\n  Dice              0.9434    0.9478\n  IoU               0.9038    0.9077\n  Precision         0.9472    0.9605\n  Recall            0.9468    0.9436\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9595  IoU=0.9266  Prec=0.9649  Rec=0.9598\n    CVC-ClinicDB    n=  61  Dice=0.9521  IoU=0.9104  Prec=0.9571  Rec=0.9494\n    Kvasir-SEG      n= 100  Dice=0.9352  IoU=0.8900  Prec=0.9597  Rec=0.9259\n  ✓ Saved best (dice=0.9478)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 37/60]  LR=3.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0898    0.0986\n  Dice              0.9434    0.9473\n  IoU               0.9042    0.9079\n  Precision         0.9483    0.9539\n  Recall            0.9453    0.9509\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9535  IoU=0.9194  Prec=0.9535  Rec=0.9642\n    CVC-ClinicDB    n=  61  Dice=0.9551  IoU=0.9151  Prec=0.9524  Rec=0.9596\n    Kvasir-SEG      n= 100  Dice=0.9380  IoU=0.8948  Prec=0.9560  Rec=0.9348\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 38/60]  LR=3.04e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0846    0.0985\n  Dice              0.9462    0.9470\n  IoU               0.9071    0.9073\n  Precision         0.9502    0.9526\n  Recall            0.9486    0.9515\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9534  IoU=0.9186  Prec=0.9498  Rec=0.9673\n    CVC-ClinicDB    n=  61  Dice=0.9543  IoU=0.9143  Prec=0.9491  Rec=0.9613\n    Kvasir-SEG      n= 100  Dice=0.9380  IoU=0.8945  Prec=0.9585  Rec=0.9321\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 39/60]  LR=2.80e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0907    0.1037\n  Dice              0.9410    0.9441\n  IoU               0.9026    0.9036\n  Precision         0.9459    0.9440\n  Recall            0.9433    0.9563\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9562  IoU=0.9225  Prec=0.9538  Rec=0.9671\n    CVC-ClinicDB    n=  61  Dice=0.9555  IoU=0.9161  Prec=0.9477  Rec=0.9650\n    Kvasir-SEG      n= 100  Dice=0.9280  IoU=0.8812  Prec=0.9332  Rec=0.9437\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 40/60]  LR=2.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0893    0.1017\n  Dice              0.9415    0.9470\n  IoU               0.9030    0.9065\n  Precision         0.9454    0.9583\n  Recall            0.9437    0.9447\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9598  IoU=0.9257  Prec=0.9686  Rec=0.9553\n    CVC-ClinicDB    n=  61  Dice=0.9576  IoU=0.9198  Prec=0.9582  Rec=0.9582\n    Kvasir-SEG      n= 100  Dice=0.9305  IoU=0.8833  Prec=0.9493  Rec=0.9294\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 41/60]  LR=2.35e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0867    0.0975\n  Dice              0.9443    0.9483\n  IoU               0.9062    0.9083\n  Precision         0.9489    0.9494\n  Recall            0.9467    0.9555\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9623  IoU=0.9300  Prec=0.9573  Rec=0.9710\n    CVC-ClinicDB    n=  61  Dice=0.9572  IoU=0.9190  Prec=0.9488  Rec=0.9672\n    Kvasir-SEG      n= 100  Dice=0.9313  IoU=0.8838  Prec=0.9436  Rec=0.9356\n  ✓ Saved best (dice=0.9483)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 42/60]  LR=2.14e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0850    0.1087\n  Dice              0.9457    0.9414\n  IoU               0.9077    0.8998\n  Precision         0.9506    0.9436\n  Recall            0.9479    0.9526\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9511  IoU=0.9171  Prec=0.9499  Rec=0.9657\n    CVC-ClinicDB    n=  61  Dice=0.9534  IoU=0.9133  Prec=0.9500  Rec=0.9590\n    Kvasir-SEG      n= 100  Dice=0.9263  IoU=0.8774  Prec=0.9349  Rec=0.9379\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 43/60]  LR=1.93e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0851    0.1003\n  Dice              0.9455    0.9479\n  IoU               0.9083    0.9082\n  Precision         0.9491    0.9514\n  Recall            0.9487    0.9536\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9589  IoU=0.9263  Prec=0.9590  Rec=0.9659\n    CVC-ClinicDB    n=  61  Dice=0.9556  IoU=0.9162  Prec=0.9519  Rec=0.9613\n    Kvasir-SEG      n= 100  Dice=0.9340  IoU=0.8881  Prec=0.9455  Rec=0.9385\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 44/60]  LR=1.74e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0827    0.0943\n  Dice              0.9471    0.9498\n  IoU               0.9103    0.9110\n  Precision         0.9506    0.9544\n  Recall            0.9489    0.9536\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9639  IoU=0.9330  Prec=0.9627  Rec=0.9688\n    CVC-ClinicDB    n=  61  Dice=0.9566  IoU=0.9182  Prec=0.9553  Rec=0.9598\n    Kvasir-SEG      n= 100  Dice=0.9331  IoU=0.8871  Prec=0.9464  Rec=0.9368\n  ✓ Saved best (dice=0.9498)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 45/60]  LR=1.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0805    0.0998\n  Dice              0.9495    0.9484\n  IoU               0.9134    0.9090\n  Precision         0.9533    0.9514\n  Recall            0.9509    0.9542\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9613  IoU=0.9285  Prec=0.9593  Rec=0.9675\n    CVC-ClinicDB    n=  61  Dice=0.9565  IoU=0.9179  Prec=0.9527  Rec=0.9620\n    Kvasir-SEG      n= 100  Dice=0.9330  IoU=0.8875  Prec=0.9440  Rec=0.9392\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 46/60]  LR=1.37e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0772    0.0969\n  Dice              0.9498    0.9501\n  IoU               0.9139    0.9116\n  Precision         0.9530    0.9528\n  Recall            0.9513    0.9557\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9610  IoU=0.9288  Prec=0.9578  Rec=0.9695\n    CVC-ClinicDB    n=  61  Dice=0.9587  IoU=0.9217  Prec=0.9542  Rec=0.9646\n    Kvasir-SEG      n= 100  Dice=0.9359  IoU=0.8915  Prec=0.9484  Rec=0.9390\n  ✓ Saved best (dice=0.9501)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 47/60]  LR=1.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0768    0.0991\n  Dice              0.9496    0.9499\n  IoU               0.9130    0.9113\n  Precision         0.9538    0.9587\n  Recall            0.9502    0.9495\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9599  IoU=0.9267  Prec=0.9618  Rec=0.9632\n    CVC-ClinicDB    n=  61  Dice=0.9575  IoU=0.9195  Prec=0.9580  Rec=0.9586\n    Kvasir-SEG      n= 100  Dice=0.9376  IoU=0.8943  Prec=0.9572  Rec=0.9332\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 48/60]  LR=1.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0731    0.0992\n  Dice              0.9522    0.9470\n  IoU               0.9166    0.9069\n  Precision         0.9558    0.9524\n  Recall            0.9535    0.9513\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9576  IoU=0.9240  Prec=0.9605  Rec=0.9618\n    CVC-ClinicDB    n=  61  Dice=0.9565  IoU=0.9180  Prec=0.9580  Rec=0.9568\n    Kvasir-SEG      n= 100  Dice=0.9324  IoU=0.8861  Prec=0.9425  Rec=0.9397\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 49/60]  LR=8.99e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0752    0.0974\n  Dice              0.9503    0.9504\n  IoU               0.9146    0.9119\n  Precision         0.9534    0.9574\n  Recall            0.9524    0.9514\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9612  IoU=0.9288  Prec=0.9613  Rec=0.9658\n    CVC-ClinicDB    n=  61  Dice=0.9564  IoU=0.9177  Prec=0.9544  Rec=0.9601\n    Kvasir-SEG      n= 100  Dice=0.9377  IoU=0.8942  Prec=0.9565  Rec=0.9338\n  ✓ Saved best (dice=0.9504)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 50/60]  LR=7.63e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0784    0.0975\n  Dice              0.9476    0.9520\n  IoU               0.9122    0.9143\n  Precision         0.9530    0.9584\n  Recall            0.9478    0.9529\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9639  IoU=0.9328  Prec=0.9655  Rec=0.9658\n    CVC-ClinicDB    n=  61  Dice=0.9567  IoU=0.9180  Prec=0.9558  Rec=0.9591\n    Kvasir-SEG      n= 100  Dice=0.9388  IoU=0.8961  Prec=0.9540  Rec=0.9382\n  ✓ Saved best (dice=0.9520)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 51/60]  LR=6.40e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0715    0.0969\n  Dice              0.9534    0.9520\n  IoU               0.9179    0.9143\n  Precision         0.9562    0.9588\n  Recall            0.9551    0.9525\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9638  IoU=0.9327  Prec=0.9652  Rec=0.9661\n    CVC-ClinicDB    n=  61  Dice=0.9568  IoU=0.9183  Prec=0.9562  Rec=0.9589\n    Kvasir-SEG      n= 100  Dice=0.9390  IoU=0.8963  Prec=0.9552  Rec=0.9372\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 52/60]  LR=5.28e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0765    0.0998\n  Dice              0.9501    0.9495\n  IoU               0.9150    0.9112\n  Precision         0.9532    0.9536\n  Recall            0.9531    0.9545\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9582  IoU=0.9253  Prec=0.9553  Rec=0.9687\n    CVC-ClinicDB    n=  61  Dice=0.9569  IoU=0.9185  Prec=0.9548  Rec=0.9606\n    Kvasir-SEG      n= 100  Dice=0.9384  IoU=0.8957  Prec=0.9525  Rec=0.9390\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 53/60]  LR=4.29e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0720    0.0978\n  Dice              0.9520    0.9515\n  IoU               0.9171    0.9136\n  Precision         0.9552    0.9585\n  Recall            0.9534    0.9521\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9632  IoU=0.9318  Prec=0.9649  Rec=0.9654\n    CVC-ClinicDB    n=  61  Dice=0.9561  IoU=0.9172  Prec=0.9567  Rec=0.9573\n    Kvasir-SEG      n= 100  Dice=0.9388  IoU=0.8961  Prec=0.9545  Rec=0.9376\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 54/60]  LR=3.42e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0720    0.0991\n  Dice              0.9534    0.9504\n  IoU               0.9186    0.9120\n  Precision         0.9568    0.9549\n  Recall            0.9549    0.9541\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9600  IoU=0.9274  Prec=0.9581  Rec=0.9679\n    CVC-ClinicDB    n=  61  Dice=0.9562  IoU=0.9173  Prec=0.9553  Rec=0.9588\n    Kvasir-SEG      n= 100  Dice=0.9389  IoU=0.8962  Prec=0.9529  Rec=0.9393\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 55/60]  LR=2.69e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0716    0.0985\n  Dice              0.9526    0.9509\n  IoU               0.9175    0.9129\n  Precision         0.9549    0.9564\n  Recall            0.9554    0.9534\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9616  IoU=0.9300  Prec=0.9611  Rec=0.9676\n    CVC-ClinicDB    n=  61  Dice=0.9557  IoU=0.9166  Prec=0.9562  Rec=0.9570\n    Kvasir-SEG      n= 100  Dice=0.9389  IoU=0.8964  Prec=0.9533  Rec=0.9391\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 56/60]  LR=2.08e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0697    0.0998\n  Dice              0.9547    0.9505\n  IoU               0.9202    0.9123\n  Precision         0.9571    0.9564\n  Recall            0.9566    0.9528\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9612  IoU=0.9294  Prec=0.9610  Rec=0.9670\n    CVC-ClinicDB    n=  61  Dice=0.9557  IoU=0.9165  Prec=0.9562  Rec=0.9571\n    Kvasir-SEG      n= 100  Dice=0.9386  IoU=0.8958  Prec=0.9535  Rec=0.9383\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 57/60]  LR=1.61e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0732    0.0989\n  Dice              0.9513    0.9511\n  IoU               0.9170    0.9130\n  Precision         0.9547    0.9571\n  Recall            0.9519    0.9529\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9626  IoU=0.9311  Prec=0.9626  Rec=0.9672\n    CVC-ClinicDB    n=  61  Dice=0.9558  IoU=0.9168  Prec=0.9566  Rec=0.9569\n    Kvasir-SEG      n= 100  Dice=0.9385  IoU=0.8956  Prec=0.9533  Rec=0.9382\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 58/60]  LR=1.27e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0695    0.0991\n  Dice              0.9542    0.9511\n  IoU               0.9203    0.9130\n  Precision         0.9561    0.9562\n  Recall            0.9561    0.9537\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9628  IoU=0.9315  Prec=0.9625  Rec=0.9677\n    CVC-ClinicDB    n=  61  Dice=0.9558  IoU=0.9167  Prec=0.9553  Rec=0.9581\n    Kvasir-SEG      n= 100  Dice=0.9384  IoU=0.8954  Prec=0.9521  Rec=0.9393\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 59/60]  LR=1.07e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0738    0.0983\n  Dice              0.9511    0.9512\n  IoU               0.9164    0.9132\n  Precision         0.9533    0.9563\n  Recall            0.9533    0.9539\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9628  IoU=0.9316  Prec=0.9620  Rec=0.9685\n    CVC-ClinicDB    n=  61  Dice=0.9559  IoU=0.9169  Prec=0.9557  Rec=0.9579\n    Kvasir-SEG      n= 100  Dice=0.9385  IoU=0.8957  Prec=0.9526  Rec=0.9390\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 60/60]  LR=1.00e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0763    0.0986\n  Dice              0.9513    0.9514\n  IoU               0.9162    0.9135\n  Precision         0.9549    0.9573\n  Recall            0.9535    0.9532\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9632  IoU=0.9322  Prec=0.9639  Rec=0.9671\n    CVC-ClinicDB    n=  61  Dice=0.9561  IoU=0.9172  Prec=0.9560  Rec=0.9580\n    Kvasir-SEG      n= 100  Dice=0.9386  IoU=0.8957  Prec=0.9530  Rec=0.9387\n  Loaded best epoch 49 (dice=0.9520)\n\n============================================================\nFINAL EVALUATION — encoder=pvt_v2_b3\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                      \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  VALIDATION SET\n============================================================\n  Dice     : 0.9520\n  IoU      : 0.9143\n  Precision: 0.9584\n  Recall   : 0.9529\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9639  IoU=0.9328  Prec=0.9655  Rec=0.9658\n  [CVC-ClinicDB] n=61  Dice=0.9567  IoU=0.9180  Prec=0.9558  Rec=0.9591\n  [Kvasir-SEG] n=100  Dice=0.9388  IoU=0.8961  Prec=0.9540  Rec=0.9382\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  IN-DISTRIBUTION TEST\n============================================================\n  Dice     : 0.9354\n  IoU      : 0.8919\n  Precision: 0.9502\n  Recall   : 0.9384\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9418  IoU=0.8993  Prec=0.9449  Rec=0.9518\n  [CVC-ClinicDB] n=62  Dice=0.9272  IoU=0.8859  Prec=0.9601  Rec=0.9225\n  [Kvasir-SEG] n=100  Dice=0.9342  IoU=0.8885  Prec=0.9499  Rec=0.9343\n","output_type":"stream"},{"name":"stderr","text":"                                                                          ","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  BENCHMARK (UNSEEN)\n============================================================\n  Dice     : 0.8444\n  IoU      : 0.7693\n  Precision: 0.8582\n  Recall   : 0.8712\n\n  PER-DATASET BREAKDOWN\n  [CVC-ColonDB] n=380  Dice=0.8263  IoU=0.7499  Prec=0.8605  Rec=0.8391\n  [ETIS-Larib] n=196  Dice=0.8595  IoU=0.7847  Prec=0.8400  Rec=0.9147\n  [CVC-300] n=60  Dice=0.9107  IoU=0.8465  Prec=0.9111  Rec=0.9267\n\n  Results appended to: checkpoints_encoder_compare/encoder_comparison_results.csv\n","output_type":"stream"},{"name":"stderr","text":"\r","output_type":"stream"}],"execution_count":4},{"cell_type":"code","source":"!pip install segmentation_models_pytorch -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-20T07:15:56.899516Z","iopub.execute_input":"2026-08-20T07:15:56.899869Z","iopub.status.idle":"2026-08-20T07:16:01.695566Z","shell.execute_reply.started":"2026-08-20T07:15:56.899841Z","shell.execute_reply":"2026-08-20T07:16:01.694853Z"}},"outputs":[{"name":"stdout","text":"\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m154.8/154.8 kB\u001b[0m \u001b[31m1.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n\u001b[?25h","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"!zip -rq pvt_polyp.zip /kaggle/working/checkpoints_encoder_compare/pvt_v2_b3","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-17T15:09:09.578073Z","iopub.execute_input":"2026-08-17T15:09:09.578503Z","iopub.status.idle":"2026-08-17T15:09:37.09489Z","shell.execute_reply.started":"2026-08-17T15:09:09.578468Z","shell.execute_reply":"2026-08-17T15:09:37.094086Z"}},"outputs":[],"execution_count":5},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink('pvt_polyp.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-18T12:50:10.846802Z","iopub.execute_input":"2026-08-18T12:50:10.847572Z","iopub.status.idle":"2026-08-18T12:50:10.853895Z","shell.execute_reply.started":"2026-08-18T12:50:10.847543Z","shell.execute_reply":"2026-08-18T12:50:10.85311Z"}},"outputs":[{"execution_count":7,"output_type":"execute_result","data":{"text/plain":"/kaggle/working/pvt_polyp.zip","text/html":"Path (<tt>pvt_polyp.zip</tt>) doesn't exist. It may still be in the process of being generated, or you may have the incorrect path."},"metadata":{}}],"execution_count":7},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nPolypSeg-EncoderComparison — Swappable-Encoder Baseline\n=========================================================\nPurpose: run a controlled encoder ablation. Decoder, loss, input size,\naugmentation, splits, optimizer/scheduler shape, and training budget\nare IDENTICAL across every run. Only CFG[\"encoder_name\"] changes.\n\nEncoders covered (8 total, verified to run at 352x352 before being\nincluded here -- see build_encoder() for the two fixes Swin needed):\n\n  Lightweight (<10M params):\n    - mobilenetv3_large_100   (~5.4M)   CNN, standard efficient baseline\n    - mit_b0                  (~3.4M)   lightweight transformer (SegFormer)\n\n  Mid-size (main comparison set):\n    - resnet34 / resnet50     (~21-25M) CNN floor\n    - res2net50_26w_4s        (~25M)    polyp-literature-standard encoder\n    - swin_tiny_patch4_window7_224 (~28M)  windowed-attention transformer\n    - mit_b3                  (~45M)    within-family transformer sibling to PVTv2\n    - pvt_v2_b3               (~45M)    PROPOSED encoder\n\n  Heavy (~>80M params):\n    - swin_base_patch4_window7_224 (~88M)  upper-bound scale reference\n\nIMPORTANT — verified compatibility notes (checked by actually running\nforward passes at 352x352 before writing this):\n  - mit_b0 / mit_b3 are NOT in timm. They come from segmentation_models_\n    pytorch (SMP)'s encoder registry. pip install segmentation_models_pytorch.\n  - swin_tiny/swin_base FAIL at 352x352 with timm's default config\n    (hardcoded for 224). Fix: pass img_size=352 explicitly at model\n    creation. Swin's timm output is also channels-last [B,H,W,C] --\n    every other encoder here is channels-first [B,C,H,W] -- so Swin\n    features are permuted before being handed to the decoder.\n  - All other encoders (MobileNetV3, ResNet, Res2Net, PVTv2) work at\n    352x352 with no changes.\n\nRequirements:\n    pip install torch torchvision timm segmentation_models_pytorch\n                albumentations opencv-python pandas numpy tqdm scikit-learn\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\nimport segmentation_models_pytorch as smp\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCFG = {\n    # Paths\n    \"train_csv\"         : \"train_split.csv\",\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n    \"save_dir\"          : \"checkpoints_encoder_compare\",\n\n    # ---- THE ONE THING THAT CHANGES BETWEEN RUNS ----\n    \"encoder_name\"       : \"mit_b3\",\n    # Valid values:\n    #   \"mobilenetv3_large_100\", \"mit_b0\",\n    #   \"resnet34\", \"resnet50\", \"res2net50_26w_4s\",\n    #   \"swin_tiny_patch4_window7_224\", \"mit_b3\", \"pvt_v2_b3\",\n    #   \"swin_base_patch4_window7_224\"\n    # ---------------------------------------------------\n\n    # Image — identical across all encoders\n    \"img_size\"          : 352,\n    \"mean\"              : [0.485, 0.456, 0.406],\n    \"std\"               : [0.229, 0.224, 0.225],\n\n    # Training — identical across all encoders\n    \"batch_size\"        : 8,\n    \"num_workers\"       : 2,\n    \"seed\"              : 42,\n    \"num_epochs\"        : 60,\n\n    # Optimizer — identical across all encoders\n    \"encoder_lr\"        : 1e-4,\n    \"decoder_lr\"        : 3e-4,\n    \"weight_decay\"      : 1e-4,\n    \"grad_clip\"         : 1.0,\n    \"min_lr\"            : 1e-6,\n\n    # Early stopping — identical across all encoders\n    \"patience\"          : 12,\n\n    # Decoder — identical across all encoders (this is the point)\n    \"dec_channels\"      : 64,\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\n\n# ============================================================\n# REPRODUCIBILITY\n# ============================================================\n\ndef set_seed(seed):\n    import random\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark     = False\n\n\n# ============================================================\n# DATA  (identical to baseline_segmentation.py — unchanged)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_train_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.ShiftScaleRotate(\n            shift_limit=0.05, scale_limit=0.1,\n            rotate_limit=30,\n            border_mode=cv2.BORDER_CONSTANT, value=0, p=0.5),\n        A.RandomBrightnessContrast(\n            brightness_limit=0.2, contrast_limit=0.2, p=0.4),\n        A.HueSaturationValue(\n            hue_shift_limit=10, sat_shift_limit=20,\n            val_shift_limit=10, p=0.3),\n        A.GaussNoise(var_limit=(10., 50.), p=0.2),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(\n                image.transpose(2, 0, 1)).float() / 255.\n            mask_t = torch.from_numpy(mask).float().unsqueeze(0) / 255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_dataloaders(cfg):\n    print(\"Loading CSVs...\")\n    train_df     = pd.read_csv(cfg['train_csv'])\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    # Confirms every run reads the exact same split -- prints a\n    # lightweight fingerprint so you can eyeball-match this across\n    # runs of different encoders and know the data didn't drift.\n    print(f\"  Split fingerprint -> \"\n          f\"train={len(train_df)} (hash={pd.util.hash_pandas_object(train_df['stem']).sum()}) \"\n          f\"val={len(val_df)} indist={len(indist_df)} bench={len(benchmark_df)}\")\n\n    sz = cfg['img_size']\n    nw = cfg['num_workers']\n    bs = cfg['batch_size']\n\n    train_loader = DataLoader(\n        PolypSegDataset(train_df, get_train_transform(sz)),\n        batch_size=bs, shuffle=True,\n        num_workers=nw, pin_memory=True, drop_last=True)\n    val_loader = DataLoader(\n        PolypSegDataset(val_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(\n        PolypSegDataset(indist_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(\n        PolypSegDataset(benchmark_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n\n    print(f\"  Train:{len(train_df)} | Val:{len(val_df)} | \"\n          f\"Indist:{len(indist_df)} | Bench:{len(benchmark_df)}\")\n\n    return {\n        'train'         : train_loader,\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# ENCODER FACTORY — the only thing that changes between runs\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    \"\"\"\n    Normalises every supported encoder to the same contract:\n    forward(x) -> tuple of 4 feature maps, channels-first [B,C,H,W],\n    at strides matching a 352x352 input: 88x88, 44x44, 22x22, 11x11\n    (except MobileNetV3, whose native 4-stage strides are documented\n    below and are NOT changed -- see note in build_encoder).\n    \"\"\"\n    def __init__(self, backbone, source, needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source          # 'timm' or 'smp'\n        self.needs_permute = needs_permute    # True for Swin (NHWC->NCHW)\n\n    def forward(self, x):\n        if self.source == 'timm':\n            feats = self.backbone(x)\n        else:  # smp — returns 6 stages incl. stem stages; keep last 4\n            all_feats = self.backbone(x)\n            feats = all_feats[-4:]\n\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n\n        return tuple(feats)\n\n\ndef build_encoder(name, pretrained=True):\n    \"\"\"\n    Returns (EncoderWrapper, enc_channels_tuple).\n    enc_channels_tuple is read from the ACTUAL model, never hardcoded,\n    since every encoder family has different channel counts at each\n    of its 4 stages -- this is what lets the decoder stay identical\n    while accepting any of the 8 encoders below.\n    \"\"\"\n    if name == \"mobilenetv3_large_100\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"resnet34\", \"resnet50\", \"res2net50_26w_4s\"):\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(1,2,3,4))\n        # out_indices (1,2,3,4) skips the very first stem-level stage\n        # (stride 2) so the 4 returned stages are at strides 4/8/16/32,\n        # matching the other encoders' 4-stage granularity.\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name == \"pvt_v2_b3\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"swin_tiny_patch4_window7_224\",\n                  \"swin_base_patch4_window7_224\"):\n        # FIX 1: img_size must be passed explicitly, or this errors\n        # outright on any input != 224x224.\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3),\n                              img_size=CFG[\"img_size\"])\n        # FIX 2: Swin's timm output is channels-last [B,H,W,C].\n        wrapper = EncoderWrapper(m, source='timm', needs_permute=True)\n\n    elif name in (\"mit_b0\", \"mit_b3\"):\n        # Not available in timm at all -- comes from SMP's encoder\n        # registry (SegFormer's Mix Transformer).\n        weights = \"imagenet\" if pretrained else None\n        m = smp.encoders.get_encoder(name, in_channels=3, depth=5,\n                                     weights=weights)\n        wrapper = EncoderWrapper(m, source='smp')\n\n    else:\n        raise ValueError(f\"Unknown encoder_name: {name}\")\n\n    # Determine actual output channels with a real forward pass —\n    # never hardcode this, since it must be correct for the decoder\n    # to wire up correctly regardless of which encoder is chosen.\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, CFG[\"img_size\"], CFG[\"img_size\"])\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n    wrapper.train()\n\n    return wrapper, enc_channels\n\n\n# ============================================================\n# MODEL COMPONENTS — identical across all encoders\n# ============================================================\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride,\n                      padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass PlainFPNDecoder(nn.Module):\n    \"\"\"\n    IDENTICAL across every encoder in this comparison. Only the first\n    four lateral conv input-channel counts adapt to enc_channels,\n    which is read dynamically from whichever encoder is plugged in --\n    the decoder's design (lateral -> top-down fuse -> single head,\n    width 64 throughout) never changes.\n    \"\"\"\n    def __init__(self, enc_channels, dec_channels=64):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, 1, 1),\n        )\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.lat4(f4)\n        p3 = self.lat3(f3)\n        p2 = self.lat2(f2)\n        p1 = self.lat1(f1)\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.fuse3(torch.cat([up(p4, p3), p3], dim=1))\n        p2f = self.fuse2(torch.cat([up(p3f, p2), p2], dim=1))\n        p1f = self.fuse1(torch.cat([up(p2f, p1), p1], dim=1))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        mask_logits = self.seg_head(out)\n        return mask_logits\n\n\nclass PolypSegEncoderCompare(nn.Module):\n    \"\"\"\n    Same architecture skeleton as PolypSegBaseline, but the encoder\n    is built via build_encoder() so it's swappable via CFG alone.\n    Decoder, head, and everything downstream is byte-for-byte\n    identical regardless of which encoder is selected.\n    \"\"\"\n    def __init__(self, encoder_name, pretrained=True, dec_channels=64):\n        super().__init__()\n        self.encoder, enc_channels = build_encoder(encoder_name, pretrained)\n        self.decoder = PlainFPNDecoder(enc_channels, dec_channels)\n        self.encoder_name = encoder_name\n        self.enc_channels = enc_channels\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        mask_logits = self.decoder(features, (H, W))\n        return {'mask_logits': mask_logits}\n\n\n# ============================================================\n# LOSS — identical across all encoders\n# ============================================================\n\ndef dice_loss(pred, target, smooth=1.0):\n    p = torch.sigmoid(pred)\n    p = p.view(p.size(0), -1)\n    t = target.view(target.size(0), -1)\n    i = (p * t).sum(1)\n    return 1 - (2 * i + smooth) / (p.sum(1) + t.sum(1) + smooth)\n\n\ndef bce_dice_loss(pred, target):\n    bce = F.binary_cross_entropy_with_logits(\n        pred, target, reduction='mean')\n    return bce + dice_loss(pred, target).mean()\n\n\n# ============================================================\n# METRICS — now WITH per-dataset breakdown surfaced every epoch\n# ============================================================\n\nclass MetricTracker:\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses      = []\n        self.dice_scores = []\n        self.iou_scores  = []\n        self.prec_scores = []\n        self.rec_scores  = []\n        self.dataset_results = {}\n\n    @torch.no_grad()\n    def update(self, outputs, batch, loss):\n        self.losses.append(loss.item())\n\n        pred = (torch.sigmoid(outputs['mask_logits']) > 0.5).float()\n        tgt  = batch['mask'].to(pred.device)\n\n        pf = pred.view(pred.size(0), -1)\n        tf = tgt.view(tgt.size(0), -1)\n        tp = (pf * tf).sum(1)\n        fp = pf.sum(1) - tp\n        fn = tf.sum(1) - tp\n\n        dice = (2 * tp / (2 * tp + fp + fn + 1e-6)).mean().item()\n        iou  = (tp / (tp + fp + fn + 1e-6)).mean().item()\n        prec = (tp / (tp + fp + 1e-6)).mean().item()\n        rec  = (tp / (tp + fn + 1e-6)).mean().item()\n\n        self.dice_scores.append(dice)\n        self.iou_scores.append(iou)\n        self.prec_scores.append(prec)\n        self.rec_scores.append(rec)\n\n        for i, ds in enumerate(batch['dataset']):\n            if ds not in self.dataset_results:\n                self.dataset_results[ds] = {\n                    'dice': [], 'iou': [], 'prec': [], 'rec': []}\n            d = self.dataset_results[ds]\n            p_i, t_i = pred[i:i+1].view(-1), tgt[i:i+1].view(-1)\n            tp_i = (p_i * t_i).sum()\n            fp_i = p_i.sum() - tp_i\n            fn_i = t_i.sum() - tp_i\n            d['dice'].append((2*tp_i/(2*tp_i+fp_i+fn_i+1e-6)).item())\n            d['iou'].append((tp_i/(tp_i+fp_i+fn_i+1e-6)).item())\n            d['prec'].append((tp_i/(tp_i+fp_i+1e-6)).item())\n            d['rec'].append((tp_i/(tp_i+fn_i+1e-6)).item())\n\n    def compute(self):\n        gross = dict(\n            loss      = np.mean(self.losses),\n            dice      = np.mean(self.dice_scores),\n            iou       = np.mean(self.iou_scores),\n            precision = np.mean(self.prec_scores),\n            recall    = np.mean(self.rec_scores),\n        )\n        # Per-dataset breakdown now included in every compute() call --\n        # not just at final evaluation -- so it can be logged every epoch.\n        per_dataset = {}\n        for ds_key, d in self.dataset_results.items():\n            per_dataset[ds_key] = dict(\n                n         = len(d['dice']),\n                dice      = np.mean(d['dice']),\n                iou       = np.mean(d['iou']),\n                precision = np.mean(d['prec']),\n                recall    = np.mean(d['rec']),\n            )\n        gross['per_dataset'] = per_dataset\n        return gross\n\n\n# ============================================================\n# OPTIMIZER — identical across all encoders\n# ============================================================\n\ndef build_optimizer(model, cfg):\n    enc_params = list(model.encoder.parameters())\n    enc_ids    = {id(p) for p in enc_params}\n    other      = [p for p in model.parameters() if id(p) not in enc_ids]\n    opt = torch.optim.AdamW([\n        {'params': enc_params, 'lr': cfg['encoder_lr']},\n        {'params': other,      'lr': cfg['decoder_lr']},\n    ], weight_decay=cfg['weight_decay'])\n    sch = torch.optim.lr_scheduler.CosineAnnealingLR(\n        opt, T_max=cfg['num_epochs'], eta_min=cfg['min_lr'])\n    return opt, sch\n\n\n# ============================================================\n# TRAIN / VALIDATE\n# ============================================================\n\ndef train_one_epoch(model, loader, optimizer, device, cfg):\n    model.train()\n    tracker = MetricTracker()\n    pbar = tqdm(loader, desc='  Train', leave=False, ncols=100)\n    for batch in pbar:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n\n        optimizer.zero_grad()\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(\n            model.parameters(), cfg['grad_clip'])\n        optimizer.step()\n\n        tracker.update(outputs, batch, loss)\n        pbar.set_postfix(\n            loss=f\"{loss.item():.4f}\",\n            dice=f\"{tracker.dice_scores[-1]:.4f}\")\n    return tracker.compute()\n\n\n@torch.no_grad()\ndef validate(model, loader, device):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in loader:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n    return tracker.compute()\n\n\n# ============================================================\n# CHECKPOINTING\n# ============================================================\n\nclass CheckpointManager:\n    def __init__(self, save_dir, patience):\n        self.save_dir   = Path(save_dir)\n        self.save_dir.mkdir(parents=True, exist_ok=True)\n        self.patience   = patience\n        self.best_dice  = 0.\n        self.best_epoch = 0\n        self.counter    = 0\n\n    def step(self, model, optimizer, scheduler, epoch, metrics):\n        dice     = metrics['dice']\n        improved = dice > self.best_dice\n        if improved:\n            self.best_dice  = dice\n            self.best_epoch = epoch\n            self.counter    = 0\n            torch.save(dict(\n                epoch=epoch, model=model.state_dict(),\n                optimizer=optimizer.state_dict(),\n                scheduler=scheduler.state_dict(),\n                best_dice=self.best_dice,\n                encoder_name=model.encoder_name,\n            ), self.save_dir / 'best_model.pth')\n            print(f\"  \\u2713 Saved best (dice={dice:.4f})\")\n        else:\n            self.counter += 1\n        return self.counter >= self.patience\n\n    def load_best(self, model, device):\n        ck = torch.load(\n            self.save_dir / 'best_model.pth',\n            map_location=device, weights_only=False)\n        model.load_state_dict(ck['model'])\n        print(f\"  Loaded best epoch {ck['epoch']} \"\n              f\"(dice={ck['best_dice']:.4f})\")\n        return model\n\n\n# ============================================================\n# LOGGING — per-epoch, gross + per-dataset, printed AND saved to CSV\n# ============================================================\n\ndef print_epoch_summary(epoch, num_epochs, train_m, val_m, lr):\n    print(f\"\\nEpoch [{epoch+1:>3}/{num_epochs}]  LR={lr:.2e}\")\n\n    print(f\"  {'GROSS METRIC':14s}  {'Train':>8}  {'Val':>8}\")\n    print(f\"  {'-'*36}\")\n    for key, label in [\n        ('loss',      'Loss'),\n        ('dice',      'Dice'),\n        ('iou',       'IoU'),\n        ('precision', 'Precision'),\n        ('recall',    'Recall'),\n    ]:\n        print(f\"  {label:14s}  {train_m.get(key,0):8.4f}  {val_m.get(key,0):8.4f}\")\n\n    # Per-dataset breakdown for validation (train per-dataset is also\n    # available in train_m['per_dataset'] if you want it printed too --\n    # kept to val here to keep the per-epoch log readable).\n    if val_m.get('per_dataset'):\n        print(f\"  {'-'*36}\")\n        print(f\"  VAL PER-DATASET:\")\n        for ds_key, d in val_m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"    {ds_name:15s} n={d['n']:>4}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n\ndef append_epoch_log(log_path, epoch, encoder_name, train_m, val_m, lr):\n    \"\"\"\n    Saves gross + per-dataset metrics for this epoch to a running CSV.\n    One row per (epoch, split, dataset) so it's easy to filter/plot\n    later without re-parsing terminal output.\n    \"\"\"\n    rows = []\n    for split_name, m in [('train', train_m), ('val', val_m)]:\n        rows.append(dict(\n            encoder=encoder_name, epoch=epoch+1, split=split_name,\n            dataset='ALL', lr=lr,\n            loss=m.get('loss'), dice=m.get('dice'), iou=m.get('iou'),\n            precision=m.get('precision'), recall=m.get('recall'),\n        ))\n        for ds_key, d in m.get('per_dataset', {}).items():\n            rows.append(dict(\n                encoder=encoder_name, epoch=epoch+1, split=split_name,\n                dataset=DATASET_INFO.get(ds_key, ds_key), lr=lr,\n                loss=None, dice=d['dice'], iou=d['iou'],\n                precision=d['precision'], recall=d['recall'],\n            ))\n\n    df = pd.DataFrame(rows)\n    header = not Path(log_path).exists()\n    df.to_csv(log_path, mode='a', header=header, index=False)\n\n\n# ============================================================\n# FINAL EVALUATION\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_single_loader(model, loader, device, split_name):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in tqdm(loader, desc=f'  Eval {split_name}', leave=False):\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n\n    m = tracker.compute()\n\n    print(f\"\\n{'='*60}\\n  {split_name.upper()}\\n{'='*60}\")\n    print(f\"  Dice     : {m['dice']:.4f}\")\n    print(f\"  IoU      : {m['iou']:.4f}\")\n    print(f\"  Precision: {m['precision']:.4f}\")\n    print(f\"  Recall   : {m['recall']:.4f}\")\n\n    if m['per_dataset']:\n        print(f\"\\n  PER-DATASET BREAKDOWN\")\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"  [{ds_name}] n={d['n']}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n    return m\n\n\n@torch.no_grad()\ndef evaluate_all_datasets(model, data, device, save_dir, encoder_name):\n    print(\"\\n\" + \"=\"*60)\n    print(f\"FINAL EVALUATION \\u2014 encoder={encoder_name}\")\n    print(\"=\"*60)\n\n    results = {}\n    results['val']       = evaluate_single_loader(model, data['val'], device, 'VALIDATION SET')\n    results['indist']    = evaluate_single_loader(model, data['indist_test'], device, 'IN-DISTRIBUTION TEST')\n    results['benchmark'] = evaluate_single_loader(model, data['benchmark_test'], device, 'BENCHMARK (UNSEEN)')\n\n    rows = []\n    total_params = sum(p.numel() for p in model.parameters())\n    for split, m in results.items():\n        rows.append(dict(\n            encoder=encoder_name, params=total_params, split=split,\n            dice=round(m['dice'],4), iou=round(m['iou'],4),\n            precision=round(m['precision'],4), recall=round(m['recall'],4),\n        ))\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            rows.append(dict(\n                encoder=encoder_name, params=total_params,\n                split=f\"{split}_{ds_name}\",\n                dice=round(d['dice'],4), iou=round(d['iou'],4),\n                precision=round(d['precision'],4), recall=round(d['recall'],4),\n            ))\n\n    # Append to a SHARED comparison CSV across all 8 encoder runs\n    comparison_path = Path(save_dir) / 'encoder_comparison_results.csv'\n    df = pd.DataFrame(rows)\n    header = not comparison_path.exists()\n    df.to_csv(comparison_path, mode='a', header=header, index=False)\n    print(f\"\\n  Results appended to: {comparison_path}\")\n\n    return results\n\n\n# ============================================================\n# MAIN TRAINING\n# ============================================================\n\ndef train(cfg):\n    set_seed(cfg['seed'])\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n    print(f\"Encoder: {cfg['encoder_name']}\")\n\n    data = create_dataloaders(cfg)\n\n    model = PolypSegEncoderCompare(\n        encoder_name=cfg['encoder_name'],\n        pretrained=True,\n        dec_channels=cfg['dec_channels'],\n    ).to(device)\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"Encoder output channels: {model.enc_channels}\")\n    print(f\"Params: {total/1e6:.2f}M total\")\n\n    optimizer, scheduler = build_optimizer(model, cfg)\n    save_dir = Path(cfg['save_dir'])\n    ckpt = CheckpointManager(save_dir / cfg['encoder_name'], cfg['patience'])\n    epoch_log_path = save_dir / f\"epoch_log_{cfg['encoder_name']}.csv\"\n\n    print(\"\\n\" + \"=\"*60)\n    print(f\"TRAINING — encoder={cfg['encoder_name']}\")\n    print(\"  Decoder: plain FPN, no attention (IDENTICAL across all encoders)\")\n    print(\"  Loss   : BCE + Dice only (IDENTICAL across all encoders)\")\n    print(\"=\"*60)\n\n    for epoch in range(cfg['num_epochs']):\n        train_m = train_one_epoch(model, data['train'], optimizer, device, cfg)\n        val_m   = validate(model, data['val'], device)\n\n        scheduler.step()\n        lr = optimizer.param_groups[0]['lr']\n\n        print_epoch_summary(epoch, cfg['num_epochs'], train_m, val_m, lr)\n        append_epoch_log(epoch_log_path, epoch, cfg['encoder_name'], train_m, val_m, lr)\n\n        stop = ckpt.step(model, optimizer, scheduler, epoch, val_m)\n        if stop:\n            print(f\"\\nEarly stopping at epoch {epoch+1}. \"\n                  f\"Best: epoch {ckpt.best_epoch+1} (dice={ckpt.best_dice:.4f})\")\n            break\n\n    model = ckpt.load_best(model, device)\n    results = evaluate_all_datasets(model, data, device, cfg['save_dir'], cfg['encoder_name'])\n\n    return model, results\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\nif __name__ == '__main__':\n    print(\"=\"*60)\n    print(f\"SANITY CHECK \\u2014 encoder={CFG['encoder_name']}\")\n    print(\"=\"*60)\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    model  = PolypSegEncoderCompare(\n        encoder_name=CFG['encoder_name'], pretrained=False).to(device)\n\n    dummy_img = torch.randn(2, 3, CFG['img_size'], CFG['img_size']).to(device)\n    with torch.no_grad():\n        out = model(dummy_img)\n    print(\"Output shapes (forward pass):\")\n    for k, v in out.items():\n        print(f\"  {k:15s}: {list(v.shape)}\")\n\n    dummy_target = torch.randint(0, 2, (2, 1, CFG['img_size'], CFG['img_size'])).float().to(device)\n    loss = bce_dice_loss(out['mask_logits'], dummy_target)\n    print(f\"\\nLoss (random init, random target): {loss.item():.4f}\")\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"\\nEncoder output channels: {model.enc_channels}\")\n    print(f\"Total params: {total/1e6:.2f}M\")\n    print(\"\\nSanity check passed \\u2713\")\n    print(\"=\"*60)\n    print(\"\\nStarting training...\")\n    model, results = train(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-18T13:12:52.800006Z","iopub.execute_input":"2026-08-18T13:12:52.800721Z","iopub.status.idle":"2026-08-18T16:18:16.62307Z","shell.execute_reply.started":"2026-08-18T13:12:52.800685Z","shell.execute_reply":"2026-08-18T16:18:16.622258Z"}},"outputs":[{"name":"stdout","text":"============================================================\nSANITY CHECK — encoder=mit_b3\n============================================================\nOutput shapes (forward pass):\n  mask_logits    : [2, 1, 352, 352]\n\nLoss (random init, random target): 1.2198\n\nEncoder output channels: (64, 128, 320, 512)\nTotal params: 44.92M\n\nSanity check passed ✓\n============================================================\n\nStarting training...\nDevice: cuda\nEncoder: mit_b3\nLoading CSVs...\n  Split fingerprint -> train=2089 (hash=6780967082095866867) val=261 indist=262 bench=636\n  Train:2089 | Val:261 | Indist:262 | Bench:636\n","output_type":"stream"},{"name":"stderr","text":"Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"config.json:   0%|          | 0.00/135 [00:00<?, ?B/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"7d175441ac174c53b64daec5370a14e1"}},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"model.safetensors:   0%|          | 0.00/178M [00:00<?, ?B/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"1c2eac49697c4b15aecd245625e9295e"}},"metadata":{}},{"name":"stdout","text":"Encoder output channels: (64, 128, 320, 512)\nParams: 44.92M total\n\n============================================================\nTRAINING — encoder=mit_b3\n  Decoder: plain FPN, no attention (IDENTICAL across all encoders)\n  Loss   : BCE + Dice only (IDENTICAL across all encoders)\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"  ✓ Saved best (dice=0.8179)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  2/60]  LR=9.97e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.4365    0.2688\n  Dice              0.8227    0.9008\n  IoU               0.7411    0.8399\n  Precision         0.8213    0.9202\n  Recall            0.8798    0.9038\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8969  IoU=0.8406  Prec=0.9005  Rec=0.9114\n    CVC-ClinicDB    n=  61  Dice=0.9140  IoU=0.8506  Prec=0.9266  Rec=0.9141\n    Kvasir-SEG      n= 100  Dice=0.8991  IoU=0.8361  Prec=0.9376  Rec=0.8920\n  ✓ Saved best (dice=0.9008)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  3/60]  LR=9.94e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.3061    0.2005\n  Dice              0.8505    0.9097\n  IoU               0.7772    0.8493\n  Precision         0.8621    0.9106\n  Recall            0.8805    0.9305\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9151  IoU=0.8584  Prec=0.9091  Rec=0.9412\n    CVC-ClinicDB    n=  61  Dice=0.9119  IoU=0.8473  Prec=0.9199  Rec=0.9182\n    Kvasir-SEG      n= 100  Dice=0.9042  IoU=0.8434  Prec=0.9074  Rec=0.9287\n  ✓ Saved best (dice=0.9097)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  4/60]  LR=9.89e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2596    0.1583\n  Dice              0.8594    0.9165\n  IoU               0.7913    0.8604\n  Precision         0.8713    0.9236\n  Recall            0.8832    0.9294\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9229  IoU=0.8704  Prec=0.9094  Rec=0.9530\n    CVC-ClinicDB    n=  61  Dice=0.9200  IoU=0.8602  Prec=0.9303  Rec=0.9231\n    Kvasir-SEG      n= 100  Dice=0.9118  IoU=0.8559  Prec=0.9377  Rec=0.9119\n  ✓ Saved best (dice=0.9165)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  5/60]  LR=9.83e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2406    0.1585\n  Dice              0.8681    0.9117\n  IoU               0.8003    0.8535\n  Precision         0.8889    0.9187\n  Recall            0.8812    0.9279\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9118  IoU=0.8568  Prec=0.9282  Rec=0.9195\n    CVC-ClinicDB    n=  61  Dice=0.9264  IoU=0.8717  Prec=0.9313  Rec=0.9340\n    Kvasir-SEG      n= 100  Dice=0.9058  IoU=0.8434  Prec=0.9048  Rec=0.9340\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  6/60]  LR=9.76e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2273    0.1488\n  Dice              0.8727    0.9169\n  IoU               0.8068    0.8630\n  Precision         0.8912    0.9263\n  Recall            0.8877    0.9292\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9258  IoU=0.8745  Prec=0.9223  Rec=0.9467\n    CVC-ClinicDB    n=  61  Dice=0.9208  IoU=0.8657  Prec=0.9278  Rec=0.9324\n    Kvasir-SEG      n= 100  Dice=0.9096  IoU=0.8547  Prec=0.9335  Rec=0.9128\n  ✓ Saved best (dice=0.9169)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  7/60]  LR=9.67e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2055    0.1393\n  Dice              0.8824    0.9251\n  IoU               0.8193    0.8710\n  Precision         0.8978    0.9392\n  Recall            0.8953    0.9259\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9375  IoU=0.8874  Prec=0.9414  Rec=0.9407\n    CVC-ClinicDB    n=  61  Dice=0.9375  IoU=0.8869  Prec=0.9496  Rec=0.9325\n    Kvasir-SEG      n= 100  Dice=0.9070  IoU=0.8476  Prec=0.9331  Rec=0.9082\n  ✓ Saved best (dice=0.9251)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  8/60]  LR=9.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1926    0.1360\n  Dice              0.8888    0.9247\n  IoU               0.8274    0.8728\n  Precision         0.9052    0.9276\n  Recall            0.8999    0.9400\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9411  IoU=0.8946  Prec=0.9393  Rec=0.9507\n    CVC-ClinicDB    n=  61  Dice=0.9395  IoU=0.8904  Prec=0.9351  Rec=0.9511\n    Kvasir-SEG      n= 100  Dice=0.9029  IoU=0.8450  Prec=0.9158  Rec=0.9237\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  9/60]  LR=9.46e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1959    0.1369\n  Dice              0.8877    0.9255\n  IoU               0.8274    0.8724\n  Precision         0.9023    0.9269\n  Recall            0.9000    0.9406\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9377  IoU=0.8899  Prec=0.9279  Rec=0.9577\n    CVC-ClinicDB    n=  61  Dice=0.9351  IoU=0.8831  Prec=0.9270  Rec=0.9515\n    Kvasir-SEG      n= 100  Dice=0.9107  IoU=0.8527  Prec=0.9293  Rec=0.9184\n  ✓ Saved best (dice=0.9255)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 10/60]  LR=9.34e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1860    0.1379\n  Dice              0.8929    0.9223\n  IoU               0.8350    0.8699\n  Precision         0.9083    0.9305\n  Recall            0.9033    0.9337\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9249  IoU=0.8761  Prec=0.9269  Rec=0.9434\n    CVC-ClinicDB    n=  61  Dice=0.9366  IoU=0.8865  Prec=0.9466  Rec=0.9342\n    Kvasir-SEG      n= 100  Dice=0.9125  IoU=0.8561  Prec=0.9255  Rec=0.9257\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 11/60]  LR=9.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1698    0.1329\n  Dice              0.9006    0.9269\n  IoU               0.8440    0.8756\n  Precision         0.9139    0.9172\n  Recall            0.9085    0.9519\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9333  IoU=0.8855  Prec=0.9177  Rec=0.9623\n    CVC-ClinicDB    n=  61  Dice=0.9424  IoU=0.8945  Prec=0.9324  Rec=0.9575\n    Kvasir-SEG      n= 100  Dice=0.9142  IoU=0.8586  Prec=0.9116  Rec=0.9400\n  ✓ Saved best (dice=0.9269)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 12/60]  LR=9.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1751    0.1176\n  Dice              0.8952    0.9314\n  IoU               0.8386    0.8824\n  Precision         0.9080    0.9383\n  Recall            0.9055    0.9391\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9423  IoU=0.8979  Prec=0.9385  Rec=0.9551\n    CVC-ClinicDB    n=  61  Dice=0.9407  IoU=0.8922  Prec=0.9420  Rec=0.9461\n    Kvasir-SEG      n= 100  Dice=0.9189  IoU=0.8662  Prec=0.9406  Rec=0.9201\n  ✓ Saved best (dice=0.9314)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 13/60]  LR=8.90e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1695    0.1306\n  Dice              0.8991    0.9262\n  IoU               0.8433    0.8779\n  Precision         0.9137    0.9332\n  Recall            0.9062    0.9392\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9348  IoU=0.8902  Prec=0.9344  Rec=0.9508\n    CVC-ClinicDB    n=  61  Dice=0.9468  IoU=0.9014  Prec=0.9403  Rec=0.9568\n    Kvasir-SEG      n= 100  Dice=0.9103  IoU=0.8576  Prec=0.9315  Rec=0.9205\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 14/60]  LR=8.73e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1666    0.1312\n  Dice              0.9003    0.9272\n  IoU               0.8463    0.8759\n  Precision         0.9121    0.9265\n  Recall            0.9092    0.9439\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9392  IoU=0.8923  Prec=0.9174  Rec=0.9712\n    CVC-ClinicDB    n=  61  Dice=0.9360  IoU=0.8834  Prec=0.9226  Rec=0.9563\n    Kvasir-SEG      n= 100  Dice=0.9125  IoU=0.8588  Prec=0.9410  Rec=0.9115\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 15/60]  LR=8.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1627    0.1301\n  Dice              0.9030    0.9307\n  IoU               0.8489    0.8824\n  Precision         0.9149    0.9411\n  Recall            0.9108    0.9360\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9505  IoU=0.9102  Prec=0.9564  Rec=0.9500\n    CVC-ClinicDB    n=  61  Dice=0.9466  IoU=0.9010  Prec=0.9414  Rec=0.9557\n    Kvasir-SEG      n= 100  Dice=0.9056  IoU=0.8486  Prec=0.9291  Rec=0.9135\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 16/60]  LR=8.36e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1580    0.1475\n  Dice              0.9057    0.9276\n  IoU               0.8533    0.8791\n  Precision         0.9161    0.9466\n  Recall            0.9149    0.9243\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9426  IoU=0.8973  Prec=0.9515  Rec=0.9418\n    CVC-ClinicDB    n=  61  Dice=0.9467  IoU=0.9011  Prec=0.9426  Rec=0.9540\n    Kvasir-SEG      n= 100  Dice=0.9023  IoU=0.8497  Prec=0.9452  Rec=0.8902\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 17/60]  LR=8.17e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1512    0.1346\n  Dice              0.9087    0.9276\n  IoU               0.8560    0.8817\n  Precision         0.9196    0.9306\n  Recall            0.9160    0.9436\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9469  IoU=0.9050  Prec=0.9484  Rec=0.9534\n    CVC-ClinicDB    n=  61  Dice=0.9516  IoU=0.9092  Prec=0.9475  Rec=0.9578\n    Kvasir-SEG      n= 100  Dice=0.8996  IoU=0.8482  Prec=0.9078  Rec=0.9295\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 18/60]  LR=7.96e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1458    0.1164\n  Dice              0.9125    0.9339\n  IoU               0.8609    0.8873\n  Precision         0.9224    0.9314\n  Recall            0.9201    0.9512\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9506  IoU=0.9098  Prec=0.9403  Rec=0.9666\n    CVC-ClinicDB    n=  61  Dice=0.9474  IoU=0.9023  Prec=0.9348  Rec=0.9633\n    Kvasir-SEG      n= 100  Dice=0.9118  IoU=0.8594  Prec=0.9223  Rec=0.9311\n  ✓ Saved best (dice=0.9339)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 19/60]  LR=7.75e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1373    0.1298\n  Dice              0.9172    0.9278\n  IoU               0.8664    0.8822\n  Precision         0.9260    0.9310\n  Recall            0.9245    0.9441\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9439  IoU=0.9041  Prec=0.9513  Rec=0.9495\n    CVC-ClinicDB    n=  61  Dice=0.9384  IoU=0.8969  Prec=0.9349  Rec=0.9579\n    Kvasir-SEG      n= 100  Dice=0.9103  IoU=0.8577  Prec=0.9150  Rec=0.9333\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 20/60]  LR=7.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1417    0.1278\n  Dice              0.9126    0.9269\n  IoU               0.8615    0.8789\n  Precision         0.9217    0.9159\n  Recall            0.9195    0.9566\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9398  IoU=0.8969  Prec=0.9235  Rec=0.9702\n    CVC-ClinicDB    n=  61  Dice=0.9340  IoU=0.8876  Prec=0.9173  Rec=0.9661\n    Kvasir-SEG      n= 100  Dice=0.9128  IoU=0.8597  Prec=0.9106  Rec=0.9404\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 21/60]  LR=7.30e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1314    0.1176\n  Dice              0.9211    0.9326\n  IoU               0.8718    0.8878\n  Precision         0.9325    0.9432\n  Recall            0.9253    0.9357\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9524  IoU=0.9132  Prec=0.9564  Rec=0.9537\n    CVC-ClinicDB    n=  61  Dice=0.9348  IoU=0.8912  Prec=0.9327  Rec=0.9532\n    Kvasir-SEG      n= 100  Dice=0.9173  IoU=0.8667  Prec=0.9410  Rec=0.9134\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 22/60]  LR=7.06e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1282    0.1057\n  Dice              0.9236    0.9402\n  IoU               0.8744    0.8976\n  Precision         0.9315    0.9509\n  Recall            0.9300    0.9423\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9555  IoU=0.9208  Prec=0.9541  Rec=0.9641\n    CVC-ClinicDB    n=  61  Dice=0.9515  IoU=0.9098  Prec=0.9537  Rec=0.9523\n    Kvasir-SEG      n= 100  Dice=0.9222  IoU=0.8719  Prec=0.9474  Rec=0.9189\n  ✓ Saved best (dice=0.9402)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 23/60]  LR=6.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1306    0.1150\n  Dice              0.9197    0.9367\n  IoU               0.8711    0.8910\n  Precision         0.9297    0.9316\n  Recall            0.9244    0.9549\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9547  IoU=0.9176  Prec=0.9477  Rec=0.9671\n    CVC-ClinicDB    n=  61  Dice=0.9508  IoU=0.9080  Prec=0.9381  Rec=0.9663\n    Kvasir-SEG      n= 100  Dice=0.9145  IoU=0.8596  Prec=0.9151  Rec=0.9389\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 24/60]  LR=6.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1266    0.1073\n  Dice              0.9235    0.9391\n  IoU               0.8755    0.8960\n  Precision         0.9319    0.9458\n  Recall            0.9285    0.9464\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9499  IoU=0.9127  Prec=0.9549  Rec=0.9549\n    CVC-ClinicDB    n=  61  Dice=0.9512  IoU=0.9088  Prec=0.9476  Rec=0.9571\n    Kvasir-SEG      n= 100  Dice=0.9253  IoU=0.8767  Prec=0.9399  Rec=0.9328\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 25/60]  LR=6.33e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1249    0.1057\n  Dice              0.9238    0.9409\n  IoU               0.8764    0.8970\n  Precision         0.9332    0.9449\n  Recall            0.9288    0.9477\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9552  IoU=0.9185  Prec=0.9422  Rec=0.9738\n    CVC-ClinicDB    n=  61  Dice=0.9510  IoU=0.9086  Prec=0.9423  Rec=0.9626\n    Kvasir-SEG      n= 100  Dice=0.9225  IoU=0.8718  Prec=0.9506  Rec=0.9157\n  ✓ Saved best (dice=0.9409)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 26/60]  LR=6.08e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1262    0.0986\n  Dice              0.9223    0.9424\n  IoU               0.8760    0.8994\n  Precision         0.9306    0.9560\n  Recall            0.9281    0.9389\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9532  IoU=0.9156  Prec=0.9628  Rec=0.9500\n    CVC-ClinicDB    n=  61  Dice=0.9505  IoU=0.9078  Prec=0.9543  Rec=0.9496\n    Kvasir-SEG      n= 100  Dice=0.9307  IoU=0.8834  Prec=0.9541  Rec=0.9239\n  ✓ Saved best (dice=0.9424)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 27/60]  LR=5.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1183    0.1150\n  Dice              0.9222    0.9352\n  IoU               0.8756    0.8912\n  Precision         0.9283    0.9391\n  Recall            0.9292    0.9473\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9458  IoU=0.9072  Prec=0.9446  Rec=0.9593\n    CVC-ClinicDB    n=  61  Dice=0.9530  IoU=0.9127  Prec=0.9536  Rec=0.9556\n    Kvasir-SEG      n= 100  Dice=0.9183  IoU=0.8680  Prec=0.9298  Rec=0.9326\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 28/60]  LR=5.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1121    0.1200\n  Dice              0.9298    0.9356\n  IoU               0.8839    0.8910\n  Precision         0.9375    0.9339\n  Recall            0.9333    0.9528\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9556  IoU=0.9210  Prec=0.9628  Rec=0.9557\n    CVC-ClinicDB    n=  61  Dice=0.9489  IoU=0.9063  Prec=0.9383  Rec=0.9636\n    Kvasir-SEG      n= 100  Dice=0.9118  IoU=0.8574  Prec=0.9072  Rec=0.9450\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 29/60]  LR=5.31e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1142    0.1088\n  Dice              0.9276    0.9411\n  IoU               0.8821    0.8987\n  Precision         0.9374    0.9399\n  Recall            0.9295    0.9543\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9587  IoU=0.9244  Prec=0.9597  Rec=0.9620\n    CVC-ClinicDB    n=  61  Dice=0.9538  IoU=0.9136  Prec=0.9532  Rec=0.9568\n    Kvasir-SEG      n= 100  Dice=0.9188  IoU=0.8682  Prec=0.9150  Rec=0.9476\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 30/60]  LR=5.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1108    0.1055\n  Dice              0.9300    0.9398\n  IoU               0.8853    0.8957\n  Precision         0.9376    0.9333\n  Recall            0.9332    0.9577\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9519  IoU=0.9141  Prec=0.9445  Rec=0.9659\n    CVC-ClinicDB    n=  61  Dice=0.9542  IoU=0.9140  Prec=0.9463  Rec=0.9646\n    Kvasir-SEG      n= 100  Dice=0.9237  IoU=0.8722  Prec=0.9194  Rec=0.9475\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 31/60]  LR=4.79e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1154    0.0995\n  Dice              0.9296    0.9434\n  IoU               0.8855    0.9003\n  Precision         0.9356    0.9455\n  Recall            0.9348    0.9500\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9543  IoU=0.9165  Prec=0.9516  Rec=0.9622\n    CVC-ClinicDB    n=  61  Dice=0.9547  IoU=0.9147  Prec=0.9461  Rec=0.9654\n    Kvasir-SEG      n= 100  Dice=0.9285  IoU=0.8795  Prec=0.9419  Rec=0.9315\n  ✓ Saved best (dice=0.9434)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 32/60]  LR=4.53e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1035    0.1102\n  Dice              0.9337    0.9405\n  IoU               0.8904    0.8979\n  Precision         0.9410    0.9443\n  Recall            0.9364    0.9493\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9527  IoU=0.9176  Prec=0.9519  Rec=0.9631\n    CVC-ClinicDB    n=  61  Dice=0.9540  IoU=0.9133  Prec=0.9523  Rec=0.9576\n    Kvasir-SEG      n= 100  Dice=0.9235  IoU=0.8736  Prec=0.9338  Rec=0.9344\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 33/60]  LR=4.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1074    0.1215\n  Dice              0.9317    0.9396\n  IoU               0.8879    0.8974\n  Precision         0.9373    0.9553\n  Recall            0.9361    0.9378\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9561  IoU=0.9207  Prec=0.9601  Rec=0.9577\n    CVC-ClinicDB    n=  61  Dice=0.9531  IoU=0.9123  Prec=0.9522  Rec=0.9562\n    Kvasir-SEG      n= 100  Dice=0.9172  IoU=0.8684  Prec=0.9540  Rec=0.9095\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 34/60]  LR=4.02e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0994    0.0977\n  Dice              0.9377    0.9453\n  IoU               0.8955    0.9039\n  Precision         0.9438    0.9554\n  Recall            0.9397    0.9444\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9589  IoU=0.9245  Prec=0.9685  Rec=0.9540\n    CVC-ClinicDB    n=  61  Dice=0.9553  IoU=0.9156  Prec=0.9615  Rec=0.9508\n    Kvasir-SEG      n= 100  Dice=0.9285  IoU=0.8804  Prec=0.9414  Rec=0.9336\n  ✓ Saved best (dice=0.9453)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 35/60]  LR=3.77e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1013    0.0974\n  Dice              0.9356    0.9421\n  IoU               0.8924    0.9010\n  Precision         0.9409    0.9420\n  Recall            0.9393    0.9547\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9572  IoU=0.9228  Prec=0.9543  Rec=0.9662\n    CVC-ClinicDB    n=  61  Dice=0.9575  IoU=0.9192  Prec=0.9569  Rec=0.9594\n    Kvasir-SEG      n= 100  Dice=0.9208  IoU=0.8728  Prec=0.9237  Rec=0.9430\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 36/60]  LR=3.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1019    0.1048\n  Dice              0.9352    0.9446\n  IoU               0.8931    0.9033\n  Precision         0.9402    0.9494\n  Recall            0.9402    0.9496\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9619  IoU=0.9297  Prec=0.9627  Rec=0.9646\n    CVC-ClinicDB    n=  61  Dice=0.9531  IoU=0.9123  Prec=0.9522  Rec=0.9567\n    Kvasir-SEG      n= 100  Dice=0.9247  IoU=0.8752  Prec=0.9371  Rec=0.9325\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 37/60]  LR=3.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0993    0.1228\n  Dice              0.9356    0.9405\n  IoU               0.8940    0.8993\n  Precision         0.9405    0.9564\n  Recall            0.9395    0.9387\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9588  IoU=0.9245  Prec=0.9565  Rec=0.9660\n    CVC-ClinicDB    n=  61  Dice=0.9536  IoU=0.9133  Prec=0.9579  Rec=0.9520\n    Kvasir-SEG      n= 100  Dice=0.9167  IoU=0.8691  Prec=0.9577  Rec=0.9061\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 38/60]  LR=3.04e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0967    0.1139\n  Dice              0.9400    0.9418\n  IoU               0.8990    0.9007\n  Precision         0.9447    0.9469\n  Recall            0.9435    0.9496\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9597  IoU=0.9255  Prec=0.9547  Rec=0.9682\n    CVC-ClinicDB    n=  61  Dice=0.9515  IoU=0.9096  Prec=0.9498  Rec=0.9565\n    Kvasir-SEG      n= 100  Dice=0.9203  IoU=0.8740  Prec=0.9392  Rec=0.9295\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 39/60]  LR=2.80e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0904    0.1136\n  Dice              0.9419    0.9424\n  IoU               0.9009    0.9020\n  Precision         0.9457    0.9509\n  Recall            0.9438    0.9471\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9621  IoU=0.9299  Prec=0.9648  Rec=0.9629\n    CVC-ClinicDB    n=  61  Dice=0.9520  IoU=0.9103  Prec=0.9488  Rec=0.9579\n    Kvasir-SEG      n= 100  Dice=0.9194  IoU=0.8730  Prec=0.9405  Rec=0.9274\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 40/60]  LR=2.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0948    0.0928\n  Dice              0.9392    0.9481\n  IoU               0.8985    0.9079\n  Precision         0.9442    0.9532\n  Recall            0.9421    0.9504\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9605  IoU=0.9271  Prec=0.9615  Rec=0.9634\n    CVC-ClinicDB    n=  61  Dice=0.9530  IoU=0.9120  Prec=0.9525  Rec=0.9561\n    Kvasir-SEG      n= 100  Dice=0.9355  IoU=0.8900  Prec=0.9475  Rec=0.9369\n  ✓ Saved best (dice=0.9481)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 41/60]  LR=2.35e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0912    0.0928\n  Dice              0.9418    0.9475\n  IoU               0.9016    0.9073\n  Precision         0.9464    0.9497\n  Recall            0.9452    0.9535\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9635  IoU=0.9324  Prec=0.9680  Rec=0.9624\n    CVC-ClinicDB    n=  61  Dice=0.9543  IoU=0.9139  Prec=0.9500  Rec=0.9609\n    Kvasir-SEG      n= 100  Dice=0.9302  IoU=0.8821  Prec=0.9332  Rec=0.9435\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 42/60]  LR=2.14e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0900    0.0989\n  Dice              0.9419    0.9457\n  IoU               0.9023    0.9050\n  Precision         0.9463    0.9468\n  Recall            0.9440    0.9537\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9605  IoU=0.9271  Prec=0.9544  Rec=0.9704\n    CVC-ClinicDB    n=  61  Dice=0.9532  IoU=0.9125  Prec=0.9506  Rec=0.9585\n    Kvasir-SEG      n= 100  Dice=0.9289  IoU=0.8821  Prec=0.9390  Rec=0.9373\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 43/60]  LR=1.93e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0904    0.0930\n  Dice              0.9431    0.9482\n  IoU               0.9033    0.9087\n  Precision         0.9490    0.9536\n  Recall            0.9443    0.9509\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9623  IoU=0.9301  Prec=0.9631  Rec=0.9649\n    CVC-ClinicDB    n=  61  Dice=0.9525  IoU=0.9114  Prec=0.9538  Rec=0.9542\n    Kvasir-SEG      n= 100  Dice=0.9339  IoU=0.8893  Prec=0.9456  Rec=0.9382\n  ✓ Saved best (dice=0.9482)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 44/60]  LR=1.74e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0893    0.0953\n  Dice              0.9424    0.9472\n  IoU               0.9028    0.9065\n  Precision         0.9454    0.9553\n  Recall            0.9458    0.9469\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9615  IoU=0.9285  Prec=0.9686  Rec=0.9579\n    CVC-ClinicDB    n=  61  Dice=0.9549  IoU=0.9151  Prec=0.9563  Rec=0.9556\n    Kvasir-SEG      n= 100  Dice=0.9311  IoU=0.8832  Prec=0.9435  Rec=0.9339\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 45/60]  LR=1.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0920    0.0912\n  Dice              0.9417    0.9485\n  IoU               0.9021    0.9085\n  Precision         0.9462    0.9569\n  Recall            0.9451    0.9473\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9599  IoU=0.9261  Prec=0.9619  Rec=0.9620\n    CVC-ClinicDB    n=  61  Dice=0.9559  IoU=0.9168  Prec=0.9577  Rec=0.9559\n    Kvasir-SEG      n= 100  Dice=0.9356  IoU=0.8900  Prec=0.9530  Rec=0.9316\n  ✓ Saved best (dice=0.9485)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 46/60]  LR=1.37e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0880    0.0967\n  Dice              0.9438    0.9473\n  IoU               0.9047    0.9070\n  Precision         0.9480    0.9569\n  Recall            0.9461    0.9460\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9599  IoU=0.9262  Prec=0.9636  Rec=0.9605\n    CVC-ClinicDB    n=  61  Dice=0.9540  IoU=0.9139  Prec=0.9556  Rec=0.9548\n    Kvasir-SEG      n= 100  Dice=0.9332  IoU=0.8873  Prec=0.9523  Rec=0.9298\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 47/60]  LR=1.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0825    0.0947\n  Dice              0.9454    0.9481\n  IoU               0.9073    0.9083\n  Precision         0.9485    0.9536\n  Recall            0.9479    0.9505\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9614  IoU=0.9287  Prec=0.9608  Rec=0.9659\n    CVC-ClinicDB    n=  61  Dice=0.9537  IoU=0.9133  Prec=0.9527  Rec=0.9572\n    Kvasir-SEG      n= 100  Dice=0.9340  IoU=0.8885  Prec=0.9484  Rec=0.9347\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 48/60]  LR=1.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0834    0.0966\n  Dice              0.9456    0.9470\n  IoU               0.9070    0.9067\n  Precision         0.9491    0.9558\n  Recall            0.9478    0.9465\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9604  IoU=0.9271  Prec=0.9635  Rec=0.9612\n    CVC-ClinicDB    n=  61  Dice=0.9537  IoU=0.9135  Prec=0.9536  Rec=0.9565\n    Kvasir-SEG      n= 100  Dice=0.9323  IoU=0.8863  Prec=0.9508  Rec=0.9298\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 49/60]  LR=8.99e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0847    0.0987\n  Dice              0.9447    0.9471\n  IoU               0.9062    0.9071\n  Precision         0.9483    0.9556\n  Recall            0.9469    0.9472\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9615  IoU=0.9290  Prec=0.9633  Rec=0.9633\n    CVC-ClinicDB    n=  61  Dice=0.9542  IoU=0.9142  Prec=0.9530  Rec=0.9578\n    Kvasir-SEG      n= 100  Dice=0.9309  IoU=0.8845  Prec=0.9509  Rec=0.9282\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 50/60]  LR=7.63e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0798    0.0974\n  Dice              0.9480    0.9473\n  IoU               0.9098    0.9071\n  Precision         0.9518    0.9496\n  Recall            0.9491    0.9532\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9608  IoU=0.9277  Prec=0.9591  Rec=0.9664\n    CVC-ClinicDB    n=  61  Dice=0.9537  IoU=0.9132  Prec=0.9488  Rec=0.9610\n    Kvasir-SEG      n= 100  Dice=0.9326  IoU=0.8865  Prec=0.9421  Rec=0.9390\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 51/60]  LR=6.40e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0843    0.0949\n  Dice              0.9435    0.9493\n  IoU               0.9062    0.9104\n  Precision         0.9447    0.9575\n  Recall            0.9473    0.9488\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9618  IoU=0.9295  Prec=0.9653  Rec=0.9620\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9148  Prec=0.9556  Rec=0.9557\n    Kvasir-SEG      n= 100  Dice=0.9361  IoU=0.8922  Prec=0.9522  Rec=0.9349\n  ✓ Saved best (dice=0.9493)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 52/60]  LR=5.28e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0828    0.0957\n  Dice              0.9467    0.9485\n  IoU               0.9085    0.9090\n  Precision         0.9508    0.9544\n  Recall            0.9489    0.9504\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9611  IoU=0.9281  Prec=0.9608  Rec=0.9649\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9145  Prec=0.9535  Rec=0.9575\n    Kvasir-SEG      n= 100  Dice=0.9349  IoU=0.8902  Prec=0.9499  Rec=0.9351\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 53/60]  LR=4.29e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0825    0.0971\n  Dice              0.9465    0.9478\n  IoU               0.9085    0.9082\n  Precision         0.9499    0.9530\n  Recall            0.9502    0.9510\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9612  IoU=0.9284  Prec=0.9610  Rec=0.9651\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9145  Prec=0.9521  Rec=0.9590\n    Kvasir-SEG      n= 100  Dice=0.9330  IoU=0.8877  Prec=0.9469  Rec=0.9355\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 54/60]  LR=3.42e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0801    0.0960\n  Dice              0.9485    0.9485\n  IoU               0.9099    0.9092\n  Precision         0.9526    0.9536\n  Recall            0.9503    0.9514\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9618  IoU=0.9294  Prec=0.9631  Rec=0.9641\n    CVC-ClinicDB    n=  61  Dice=0.9542  IoU=0.9143  Prec=0.9526  Rec=0.9583\n    Kvasir-SEG      n= 100  Dice=0.9341  IoU=0.8894  Prec=0.9463  Rec=0.9379\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 55/60]  LR=2.69e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0790    0.0965\n  Dice              0.9487    0.9484\n  IoU               0.9108    0.9088\n  Precision         0.9518    0.9543\n  Recall            0.9511    0.9504\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9617  IoU=0.9291  Prec=0.9619  Rec=0.9651\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9147  Prec=0.9539  Rec=0.9574\n    Kvasir-SEG      n= 100  Dice=0.9338  IoU=0.8885  Prec=0.9483  Rec=0.9349\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 56/60]  LR=2.08e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0831    0.0954\n  Dice              0.9459    0.9486\n  IoU               0.9082    0.9093\n  Precision         0.9523    0.9523\n  Recall            0.9463    0.9530\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9613  IoU=0.9285  Prec=0.9595  Rec=0.9669\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9146  Prec=0.9519  Rec=0.9593\n    Kvasir-SEG      n= 100  Dice=0.9347  IoU=0.8902  Prec=0.9467  Rec=0.9383\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 57/60]  LR=1.61e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0819    0.0947\n  Dice              0.9472    0.9486\n  IoU               0.9099    0.9093\n  Precision         0.9504    0.9544\n  Recall            0.9498    0.9508\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9613  IoU=0.9286  Prec=0.9627  Rec=0.9637\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9146  Prec=0.9540  Rec=0.9572\n    Kvasir-SEG      n= 100  Dice=0.9347  IoU=0.8901  Prec=0.9478  Rec=0.9372\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 58/60]  LR=1.27e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0771    0.0943\n  Dice              0.9494    0.9489\n  IoU               0.9126    0.9098\n  Precision         0.9520    0.9542\n  Recall            0.9509    0.9516\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9617  IoU=0.9293  Prec=0.9619  Rec=0.9653\n    CVC-ClinicDB    n=  61  Dice=0.9546  IoU=0.9149  Prec=0.9535  Rec=0.9579\n    Kvasir-SEG      n= 100  Dice=0.9350  IoU=0.8907  Prec=0.9483  Rec=0.9374\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 59/60]  LR=1.07e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0807    0.0948\n  Dice              0.9472    0.9489\n  IoU               0.9098    0.9098\n  Precision         0.9515    0.9549\n  Recall            0.9483    0.9509\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9618  IoU=0.9294  Prec=0.9628  Rec=0.9644\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9146  Prec=0.9542  Rec=0.9569\n    Kvasir-SEG      n= 100  Dice=0.9352  IoU=0.8909  Prec=0.9489  Rec=0.9369\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 60/60]  LR=1.00e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0804    0.0950\n  Dice              0.9473    0.9487\n  IoU               0.9098    0.9094\n  Precision         0.9512    0.9532\n  Recall            0.9488    0.9522\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9614  IoU=0.9288  Prec=0.9606  Rec=0.9660\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9147  Prec=0.9531  Rec=0.9582\n    Kvasir-SEG      n= 100  Dice=0.9348  IoU=0.8903  Prec=0.9472  Rec=0.9379\n  Loaded best epoch 50 (dice=0.9493)\n\n============================================================\nFINAL EVALUATION — encoder=mit_b3\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                      \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  VALIDATION SET\n============================================================\n  Dice     : 0.9493\n  IoU      : 0.9104\n  Precision: 0.9575\n  Recall   : 0.9488\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9618  IoU=0.9295  Prec=0.9653  Rec=0.9620\n  [CVC-ClinicDB] n=61  Dice=0.9545  IoU=0.9148  Prec=0.9556  Rec=0.9557\n  [Kvasir-SEG] n=100  Dice=0.9361  IoU=0.8922  Prec=0.9522  Rec=0.9349\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  IN-DISTRIBUTION TEST\n============================================================\n  Dice     : 0.9368\n  IoU      : 0.8931\n  Precision: 0.9512\n  Recall   : 0.9380\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9421  IoU=0.8986  Prec=0.9449  Rec=0.9504\n  [CVC-ClinicDB] n=62  Dice=0.9278  IoU=0.8865  Prec=0.9574  Rec=0.9256\n  [Kvasir-SEG] n=100  Dice=0.9372  IoU=0.8920  Prec=0.9544  Rec=0.9328\n","output_type":"stream"},{"name":"stderr","text":"                                                                          ","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  BENCHMARK (UNSEEN)\n============================================================\n  Dice     : 0.8444\n  IoU      : 0.7689\n  Precision: 0.8682\n  Recall   : 0.8609\n\n  PER-DATASET BREAKDOWN\n  [CVC-ColonDB] n=380  Dice=0.8237  IoU=0.7451  Prec=0.8715  Rec=0.8244\n  [ETIS-Larib] n=196  Dice=0.8601  IoU=0.7865  Prec=0.8528  Rec=0.9019\n  [CVC-300] n=60  Dice=0.9224  IoU=0.8618  Prec=0.9021  Rec=0.9519\n\n  Results appended to: checkpoints_encoder_compare/encoder_comparison_results.csv\n","output_type":"stream"},{"name":"stderr","text":"\r","output_type":"stream"}],"execution_count":3},{"cell_type":"code","source":"!zip -rq mit_b3.zip /kaggle/working/checkpoints_encoder_compare/mit_b3/best_model.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-18T16:21:32.751913Z","iopub.execute_input":"2026-08-18T16:21:32.752344Z","iopub.status.idle":"2026-08-18T16:21:59.376745Z","shell.execute_reply.started":"2026-08-18T16:21:32.752309Z","shell.execute_reply":"2026-08-18T16:21:59.375675Z"}},"outputs":[],"execution_count":4},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink(\"mit_b3.zip\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-18T16:22:49.371786Z","iopub.execute_input":"2026-08-18T16:22:49.372629Z","iopub.status.idle":"2026-08-18T16:22:49.378978Z","shell.execute_reply.started":"2026-08-18T16:22:49.372593Z","shell.execute_reply":"2026-08-18T16:22:49.378154Z"}},"outputs":[{"execution_count":5,"output_type":"execute_result","data":{"text/plain":"/kaggle/working/mit_b3.zip","text/html":"<a href='mit_b3.zip' target='_blank'>mit_b3.zip</a><br>"},"metadata":{}}],"execution_count":5},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nPolypSeg — Full 8-Encoder Evaluation & Comparison\n====================================================\nLoads every trained checkpoint, runs each on:\n    - val_split.csv         (validation set)\n    - indist_test.csv       (in-distribution test: BKAI + CVC-ClinicDB + Kvasir)\n    - benchmark_test.csv    (unseen generalization: ColonDB + ETIS + CVC-300)\n\nPrints, per encoder:\n    - gross metrics on each of the 3 splits\n    - per-dataset breakdown within each split\nThen prints a final leaderboard comparison table across all encoders,\nsorted by benchmark-test Dice (the number that matters for judging\ngeneralization, since none of these datasets were trained on).\n\nThis script is self-contained: it re-declares everything needed from\nyour training script (build_encoder, PlainFPNDecoder, dataset classes,\ntransforms, MetricTracker) so it can run standalone against the saved\n.pth checkpoints without importing the training file.\n\nRun this in the same Kaggle environment/session where train_split.csv,\nval_split.csv, indist_test.csv, benchmark_test.csv already exist (i.e.\nafter running the split script), with the model datasets attached.\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\nimport segmentation_models_pytorch as smp\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nEVAL_CFG = {\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n\n    \"img_size\"   : 352,\n    \"mean\"       : [0.485, 0.456, 0.406],\n    \"std\"        : [0.229, 0.224, 0.225],\n    \"batch_size\" : 8,\n    \"num_workers\": 2,\n    \"dec_channels\": 64,\n}\n\n# name -> checkpoint path, exactly as provided\nCHECKPOINT_PATHS = {\n    \"mit_b0\": \"/kaggle/input/models/borshon10/mit-b0-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/mit_b0/best_model.pth\",\n    \"mit_b3\": \"/kaggle/input/models/borshon10/mit-b3-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/mit_b3/best_model.pth\",\n    \"mobilenetv3_large_100\": \"/kaggle/input/models/borshon10/mobilenet3-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/mobilenetv3_large_100/best_model.pth\",\n    \"pvt_v2_b3\": \"/kaggle/input/models/borshon10/pvt-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/pvt_v2_b3/best_model.pth\",\n    \"res2net50_26w_4s\": \"/kaggle/input/models/borshon10/res2net50-26w-4s-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/res2net50_26w_4s/best_model.pth\",\n    \"resnet34\": \"/kaggle/input/models/borshon10/resnet34-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/resnet34/best_model.pth\",\n    \"resnet50\": \"/kaggle/input/models/borshon10/resnet50-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/resnet50/best_model.pth\",\n    \"swin_tiny_patch4_window7_224\": \"/kaggle/input/models/borshon10/swin-tiny-patch4-window-polyp/pytorch/default/1/kaggle/working/checkpoints_encoder_compare/swin_tiny_patch4_window7_224/best_model.pth\",\n    # NOTE: swin_base_patch4_window7_224 was in the original 8-encoder\n    # list/design but no checkpoint path was provided for it. If you\n    # have it, add its path here — the script will pick it up\n    # automatically. Otherwise it's skipped with a warning below.\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\nRESULTS_CSV = \"/kaggle/working/full_encoder_comparison.csv\"\n\n\n# ============================================================\n# DATA (identical to training script)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=EVAL_CFG[\"mean\"], std=EVAL_CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(image.transpose(2,0,1)).float()/255.\n            mask_t  = torch.from_numpy(mask).float().unsqueeze(0)/255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_eval_loaders(cfg):\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    sz, nw, bs = cfg['img_size'], cfg['num_workers'], cfg['batch_size']\n    tf = get_val_transform(sz)\n\n    val_loader = DataLoader(PolypSegDataset(val_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(PolypSegDataset(indist_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(PolypSegDataset(benchmark_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n\n    print(f\"Loaded eval sets — Val:{len(val_df)}  Indist:{len(indist_df)}  Bench:{len(benchmark_df)}\")\n\n    return {\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# ENCODER FACTORY (identical to training script)\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    def __init__(self, backbone, source, needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source\n        self.needs_permute = needs_permute\n\n    def forward(self, x):\n        if self.source == 'timm':\n            feats = self.backbone(x)\n        else:\n            all_feats = self.backbone(x)\n            feats = all_feats[-4:]\n\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n\n        return tuple(feats)\n\n\ndef build_encoder(name, pretrained=False):\n    \"\"\"pretrained=False here since checkpoint weights overwrite this\n    immediately after — no need to download ImageNet weights for eval.\"\"\"\n    if name == \"mobilenetv3_large_100\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"resnet34\", \"resnet50\", \"res2net50_26w_4s\"):\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(1,2,3,4))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name == \"pvt_v2_b3\":\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3))\n        wrapper = EncoderWrapper(m, source='timm')\n\n    elif name in (\"swin_tiny_patch4_window7_224\",\n                  \"swin_base_patch4_window7_224\"):\n        m = timm.create_model(name, pretrained=pretrained,\n                              features_only=True, out_indices=(0,1,2,3),\n                              img_size=EVAL_CFG[\"img_size\"])\n        wrapper = EncoderWrapper(m, source='timm', needs_permute=True)\n\n    elif name in (\"mit_b0\", \"mit_b3\"):\n        m = smp.encoders.get_encoder(name, in_channels=3, depth=5,\n                                     weights=None)\n        wrapper = EncoderWrapper(m, source='smp')\n\n    else:\n        raise ValueError(f\"Unknown encoder_name: {name}\")\n\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, EVAL_CFG[\"img_size\"], EVAL_CFG[\"img_size\"])\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n\n    return wrapper, enc_channels\n\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride, padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass PlainFPNDecoder(nn.Module):\n    def __init__(self, enc_channels, dec_channels=64):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, 1, 1),\n        )\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.lat4(f4)\n        p3 = self.lat3(f3)\n        p2 = self.lat2(f2)\n        p1 = self.lat1(f1)\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.fuse3(torch.cat([up(p4, p3), p3], dim=1))\n        p2f = self.fuse2(torch.cat([up(p3f, p2), p2], dim=1))\n        p1f = self.fuse1(torch.cat([up(p2f, p1), p1], dim=1))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        return self.seg_head(out)\n\n\nclass PolypSegEncoderCompare(nn.Module):\n    def __init__(self, encoder_name, pretrained=False, dec_channels=64):\n        super().__init__()\n        self.encoder, enc_channels = build_encoder(encoder_name, pretrained)\n        self.decoder = PlainFPNDecoder(enc_channels, dec_channels)\n        self.encoder_name = encoder_name\n        self.enc_channels = enc_channels\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        mask_logits = self.decoder(features, (H, W))\n        return {'mask_logits': mask_logits}\n\n\n# ============================================================\n# LOSS (needed only to report eval loss)\n# ============================================================\n\ndef dice_loss(pred, target, smooth=1.0):\n    p = torch.sigmoid(pred)\n    p = p.view(p.size(0), -1)\n    t = target.view(target.size(0), -1)\n    i = (p * t).sum(1)\n    return 1 - (2 * i + smooth) / (p.sum(1) + t.sum(1) + smooth)\n\n\ndef bce_dice_loss(pred, target):\n    bce = F.binary_cross_entropy_with_logits(pred, target, reduction='mean')\n    return bce + dice_loss(pred, target).mean()\n\n\n# ============================================================\n# METRICS (identical to training script)\n# ============================================================\n\nclass MetricTracker:\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses      = []\n        self.dice_scores = []\n        self.iou_scores  = []\n        self.prec_scores = []\n        self.rec_scores  = []\n        self.dataset_results = {}\n\n    @torch.no_grad()\n    def update(self, outputs, batch, loss):\n        self.losses.append(loss.item())\n\n        pred = (torch.sigmoid(outputs['mask_logits']) > 0.5).float()\n        tgt  = batch['mask'].to(pred.device)\n\n        pf = pred.view(pred.size(0), -1)\n        tf = tgt.view(tgt.size(0), -1)\n        tp = (pf * tf).sum(1)\n        fp = pf.sum(1) - tp\n        fn = tf.sum(1) - tp\n\n        dice = (2 * tp / (2 * tp + fp + fn + 1e-6)).mean().item()\n        iou  = (tp / (tp + fp + fn + 1e-6)).mean().item()\n        prec = (tp / (tp + fp + 1e-6)).mean().item()\n        rec  = (tp / (tp + fn + 1e-6)).mean().item()\n\n        self.dice_scores.append(dice)\n        self.iou_scores.append(iou)\n        self.prec_scores.append(prec)\n        self.rec_scores.append(rec)\n\n        for i, ds in enumerate(batch['dataset']):\n            if ds not in self.dataset_results:\n                self.dataset_results[ds] = {\n                    'dice': [], 'iou': [], 'prec': [], 'rec': []}\n            d = self.dataset_results[ds]\n            p_i, t_i = pred[i:i+1].view(-1), tgt[i:i+1].view(-1)\n            tp_i = (p_i * t_i).sum()\n            fp_i = p_i.sum() - tp_i\n            fn_i = t_i.sum() - tp_i\n            d['dice'].append((2*tp_i/(2*tp_i+fp_i+fn_i+1e-6)).item())\n            d['iou'].append((tp_i/(tp_i+fp_i+fn_i+1e-6)).item())\n            d['prec'].append((tp_i/(tp_i+fp_i+1e-6)).item())\n            d['rec'].append((tp_i/(tp_i+fn_i+1e-6)).item())\n\n    def compute(self):\n        gross = dict(\n            loss      = np.mean(self.losses),\n            dice      = np.mean(self.dice_scores),\n            iou       = np.mean(self.iou_scores),\n            precision = np.mean(self.prec_scores),\n            recall    = np.mean(self.rec_scores),\n        )\n        per_dataset = {}\n        for ds_key, d in self.dataset_results.items():\n            per_dataset[ds_key] = dict(\n                n         = len(d['dice']),\n                dice      = np.mean(d['dice']),\n                iou       = np.mean(d['iou']),\n                precision = np.mean(d['prec']),\n                recall    = np.mean(d['rec']),\n            )\n        gross['per_dataset'] = per_dataset\n        return gross\n\n\n@torch.no_grad()\ndef evaluate_single_loader(model, loader, device, split_name, quiet=False):\n    model.eval()\n    tracker = MetricTracker()\n    it = loader if quiet else tqdm(loader, desc=f'  Eval {split_name}', leave=False)\n    for batch in it:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n    return tracker.compute()\n\n\n# ============================================================\n# CHECKPOINT LOADING\n# ============================================================\n\ndef load_model_from_checkpoint(encoder_name, ckpt_path, device):\n    \"\"\"\n    Builds the model architecture for `encoder_name`, then loads the\n    trained weights from ckpt_path. Cross-checks the encoder_name\n    stored inside the checkpoint (saved by CheckpointManager.step\n    during training) against the folder-derived name — if these\n    disagree, something is mismatched (wrong file moved into the\n    wrong folder) and we want to know loudly, not silently eval the\n    wrong architecture.\n    \"\"\"\n    if not os.path.exists(ckpt_path):\n        print(f\"  ✗ MISSING checkpoint file: {ckpt_path}\")\n        return None\n\n    model = PolypSegEncoderCompare(\n        encoder_name=encoder_name, pretrained=False,\n        dec_channels=EVAL_CFG['dec_channels']\n    ).to(device)\n\n    ck = torch.load(ckpt_path, map_location=device, weights_only=False)\n\n    ck_encoder_name = ck.get('encoder_name', None)\n    if ck_encoder_name is not None and ck_encoder_name != encoder_name:\n        print(f\"  ⚠ WARNING: checkpoint says encoder_name=\"\n              f\"'{ck_encoder_name}' but path implies '{encoder_name}' — \"\n              f\"mismatch! Loading anyway with '{encoder_name}' \"\n              f\"architecture, but verify this checkpoint is correct.\")\n\n    model.load_state_dict(ck['model'])\n    model.eval()\n\n    trained_epoch = ck.get('epoch', 'unknown')\n    trained_dice  = ck.get('best_dice', None)\n    dice_str = f\"{trained_dice:.4f}\" if trained_dice is not None else \"unknown\"\n    print(f\"  ✓ Loaded '{encoder_name}' — checkpoint epoch={trained_epoch}, \"\n          f\"train-time best val dice={dice_str}\")\n\n    return model\n\n\n# ============================================================\n# MAIN EVALUATION LOOP — all encoders\n# ============================================================\n\ndef evaluate_all_encoders():\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\\n\")\n\n    print(\"=\"*70)\n    print(\"LOADING EVAL DATA\")\n    print(\"=\"*70)\n    data = create_eval_loaders(EVAL_CFG)\n\n    all_rows = []          # for the flat comparison CSV\n    all_results = {}       # encoder_name -> {split: metrics dict}\n\n    for encoder_name, ckpt_path in CHECKPOINT_PATHS.items():\n        print(\"\\n\" + \"=\"*70)\n        print(f\"ENCODER: {encoder_name}\")\n        print(\"=\"*70)\n\n        model = load_model_from_checkpoint(encoder_name, ckpt_path, device)\n        if model is None:\n            print(f\"  Skipping {encoder_name} (checkpoint not found).\")\n            continue\n\n        total_params = sum(p.numel() for p in model.parameters())\n        print(f\"  Params: {total_params/1e6:.2f}M\")\n\n        results = {}\n        for split_key, split_label in [\n            ('val', 'VALIDATION'),\n            ('indist_test', 'IN-DISTRIBUTION TEST'),\n            ('benchmark_test', 'BENCHMARK (UNSEEN)'),\n        ]:\n            m = evaluate_single_loader(model, data[split_key], device, split_label)\n            results[split_key] = m\n\n            print(f\"\\n  --- {split_label} ---\")\n            print(f\"    Dice={m['dice']:.4f}  IoU={m['iou']:.4f}  \"\n                  f\"Prec={m['precision']:.4f}  Rec={m['recall']:.4f}  \"\n                  f\"Loss={m['loss']:.4f}\")\n            for ds_key, d in m['per_dataset'].items():\n                ds_name = DATASET_INFO.get(ds_key, ds_key)\n                print(f\"      [{ds_name:14s}] n={d['n']:>4}  \"\n                      f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                      f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n            # Flat rows for the CSV / comparison table\n            all_rows.append(dict(\n                encoder=encoder_name, params_M=round(total_params/1e6, 2),\n                split=split_key, dataset='ALL',\n                dice=round(m['dice'],4), iou=round(m['iou'],4),\n                precision=round(m['precision'],4), recall=round(m['recall'],4),\n                loss=round(m['loss'],4),\n            ))\n            for ds_key, d in m['per_dataset'].items():\n                all_rows.append(dict(\n                    encoder=encoder_name, params_M=round(total_params/1e6, 2),\n                    split=split_key, dataset=DATASET_INFO.get(ds_key, ds_key),\n                    dice=round(d['dice'],4), iou=round(d['iou'],4),\n                    precision=round(d['precision'],4), recall=round(d['recall'],4),\n                    loss=None,\n                ))\n\n        all_results[encoder_name] = results\n\n        # free GPU memory before loading the next encoder\n        del model\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n\n    # Flag any encoders from the original design that had no checkpoint\n    # path supplied at all (e.g. swin_base)\n    expected = {\n        \"mobilenetv3_large_100\", \"mit_b0\", \"resnet34\", \"resnet50\",\n        \"res2net50_26w_4s\", \"swin_tiny_patch4_window7_224\", \"mit_b3\",\n        \"pvt_v2_b3\", \"swin_base_patch4_window7_224\",\n    }\n    missing_entirely = expected - set(CHECKPOINT_PATHS.keys())\n    if missing_entirely:\n        print(\"\\n\" + \"=\"*70)\n        print(\"NOTE: encoders with no checkpoint path supplied at all \"\n              \"(not evaluated):\")\n        for e in sorted(missing_entirely):\n            print(f\"  - {e}\")\n\n    # Save flat comparison CSV\n    comp_df = pd.DataFrame(all_rows)\n    comp_df.to_csv(RESULTS_CSV, index=False)\n    print(f\"\\nFull results saved to: {RESULTS_CSV}\")\n\n    return all_results, comp_df\n\n\n# ============================================================\n# FINAL LEADERBOARD TABLE\n# ============================================================\n\ndef print_leaderboard(all_results):\n    \"\"\"\n    One row per encoder, gross metrics per split, sorted by\n    benchmark-test Dice descending — this is the unseen-generalization\n    number and the fairest single figure to rank encoders by, since\n    val/indist performance can be inflated by in-distribution overlap\n    while benchmark (ColonDB/ETIS/CVC-300) was never trained on.\n    \"\"\"\n    rows = []\n    for encoder_name, results in all_results.items():\n        val_m   = results.get('val', {})\n        ind_m   = results.get('indist_test', {})\n        bench_m = results.get('benchmark_test', {})\n        rows.append({\n            'Encoder'        : encoder_name,\n            'Val Dice'       : val_m.get('dice', float('nan')),\n            'Val IoU'        : val_m.get('iou', float('nan')),\n            'Indist Dice'    : ind_m.get('dice', float('nan')),\n            'Indist IoU'     : ind_m.get('iou', float('nan')),\n            'Bench Dice'     : bench_m.get('dice', float('nan')),\n            'Bench IoU'      : bench_m.get('iou', float('nan')),\n            'Bench Precision': bench_m.get('precision', float('nan')),\n            'Bench Recall'   : bench_m.get('recall', float('nan')),\n        })\n\n    lb = pd.DataFrame(rows).sort_values('Bench Dice', ascending=False).reset_index(drop=True)\n    lb.insert(0, 'Rank', range(1, len(lb) + 1))\n\n    print(\"\\n\\n\" + \"=\"*100)\n    print(\"FINAL LEADERBOARD — sorted by Benchmark (unseen) Dice\")\n    print(\"=\"*100)\n    with pd.option_context('display.float_format', '{:.4f}'.format,\n                            'display.width', 140):\n        print(lb.to_string(index=False))\n\n    # Per-benchmark-dataset breakdown table (ColonDB / ETIS / CVC-300\n    # separately) — important given the CVC-300/CVC-ClinicDB overlap\n    # risk flagged in the split script's docstring.\n    print(\"\\n\" + \"=\"*100)\n    print(\"BENCHMARK PER-DATASET DICE (ColonDB / ETIS / CVC-300 separately)\")\n    print(\"  -> If CVC-300 is much higher than ColonDB/ETIS for every\")\n    print(\"     encoder, suspect train/benchmark overlap via CVC-ClinicDB\")\n    print(\"     as noted in the split script docstring — verify before\")\n    print(\"     trusting CVC-300 numbers.\")\n    print(\"=\"*100)\n    bench_rows = []\n    for encoder_name, results in all_results.items():\n        bm = results.get('benchmark_test', {})\n        row = {'Encoder': encoder_name}\n        for ds_key in ['colondb', 'etis', 'cvc300']:\n            d = bm.get('per_dataset', {}).get(ds_key)\n            row[f\"{DATASET_INFO.get(ds_key, ds_key)} Dice\"] = d['dice'] if d else float('nan')\n            row[f\"{DATASET_INFO.get(ds_key, ds_key)} n\"] = d['n'] if d else 0\n        bench_rows.append(row)\n    bdf = pd.DataFrame(bench_rows).sort_values(\n        f\"{DATASET_INFO['colondb']} Dice\", ascending=False).reset_index(drop=True)\n    with pd.option_context('display.float_format', '{:.4f}'.format,\n                            'display.width', 140):\n        print(bdf.to_string(index=False))\n\n    lb_path = \"/kaggle/working/leaderboard_summary.csv\"\n    lb.to_csv(lb_path, index=False)\n    print(f\"\\nLeaderboard saved to: {lb_path}\")\n\n    return lb, bdf\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\nif __name__ == '__main__':\n    all_results, comp_df = evaluate_all_encoders()\n    leaderboard, bench_breakdown = print_leaderboard(all_results)\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"DONE. Outputs:\")\n    print(f\"  - {RESULTS_CSV}  (full per-split, per-dataset rows)\")\n    print(f\"  - /kaggle/working/leaderboard_summary.csv  (ranked summary)\")\n    print(\"=\"*70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T01:22:33.80185Z","iopub.execute_input":"2026-09-08T01:22:33.802323Z","iopub.status.idle":"2026-09-08T01:26:08.131319Z","shell.execute_reply.started":"2026-09-08T01:22:33.802289Z","shell.execute_reply":"2026-09-08T01:26:08.130359Z"}},"outputs":[{"name":"stdout","text":"Device: cuda\n\n======================================================================\nLOADING EVAL DATA\n======================================================================\nLoaded eval sets — Val:261  Indist:262  Bench:636\n\n======================================================================\nENCODER: mit_b0\n======================================================================\n  ✓ Loaded 'mit_b0' — checkpoint epoch=57, train-time best val dice=0.9336\n  Params: 3.87M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9336  IoU=0.8881  Prec=0.9416  Rec=0.9423  Loss=0.1243\n      [BKAI-IGH      ] n= 100  Dice=0.9407  IoU=0.9021  Prec=0.9391  Rec=0.9606\n      [CVC-ClinicDB  ] n=  61  Dice=0.9500  IoU=0.9070  Prec=0.9422  Rec=0.9603\n      [Kvasir-SEG    ] n= 100  Dice=0.9189  IoU=0.8662  Prec=0.9442  Rec=0.9172\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9283  IoU=0.8796  Prec=0.9386  Rec=0.9326  Loss=0.1404\n      [BKAI-IGH      ] n= 100  Dice=0.9274  IoU=0.8807  Prec=0.9329  Rec=0.9346\n      [CVC-ClinicDB  ] n=  62  Dice=0.9299  IoU=0.8820  Prec=0.9403  Rec=0.9358\n      [Kvasir-SEG    ] n= 100  Dice=0.9281  IoU=0.8770  Prec=0.9437  Rec=0.9281\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.8197  IoU=0.7420  Prec=0.8475  Rec=0.8380  Loss=0.3295\n      [CVC-ColonDB   ] n= 380  Dice=0.8033  IoU=0.7226  Prec=0.8536  Rec=0.8085\n      [ETIS-Larib    ] n= 196  Dice=0.8231  IoU=0.7486  Prec=0.8240  Rec=0.8604\n      [CVC-300       ] n=  60  Dice=0.9148  IoU=0.8491  Prec=0.8928  Rec=0.9478\n\n======================================================================\nENCODER: mit_b3\n======================================================================\n  ✓ Loaded 'mit_b3' — checkpoint epoch=50, train-time best val dice=0.9493\n  Params: 44.92M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9493  IoU=0.9104  Prec=0.9575  Rec=0.9488  Loss=0.0949\n      [BKAI-IGH      ] n= 100  Dice=0.9618  IoU=0.9295  Prec=0.9653  Rec=0.9620\n      [CVC-ClinicDB  ] n=  61  Dice=0.9545  IoU=0.9148  Prec=0.9556  Rec=0.9557\n      [Kvasir-SEG    ] n= 100  Dice=0.9361  IoU=0.8922  Prec=0.9522  Rec=0.9349\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9368  IoU=0.8931  Prec=0.9512  Rec=0.9380  Loss=0.1473\n      [BKAI-IGH      ] n= 100  Dice=0.9421  IoU=0.8986  Prec=0.9449  Rec=0.9504\n      [CVC-ClinicDB  ] n=  62  Dice=0.9278  IoU=0.8865  Prec=0.9574  Rec=0.9256\n      [Kvasir-SEG    ] n= 100  Dice=0.9372  IoU=0.8920  Prec=0.9544  Rec=0.9328\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.8444  IoU=0.7689  Prec=0.8682  Rec=0.8609  Loss=0.2696\n      [CVC-ColonDB   ] n= 380  Dice=0.8237  IoU=0.7451  Prec=0.8715  Rec=0.8244\n      [ETIS-Larib    ] n= 196  Dice=0.8601  IoU=0.7865  Prec=0.8528  Rec=0.9019\n      [CVC-300       ] n=  60  Dice=0.9224  IoU=0.8618  Prec=0.9021  Rec=0.9519\n\n======================================================================\nENCODER: mobilenetv3_large_100\n======================================================================\n  ✓ Loaded 'mobilenetv3_large_100' — checkpoint epoch=51, train-time best val dice=0.9109\n  Params: 3.34M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9109  IoU=0.8523  Prec=0.9217  Rec=0.9213  Loss=0.1539\n      [BKAI-IGH      ] n= 100  Dice=0.9109  IoU=0.8556  Prec=0.9117  Rec=0.9326\n      [CVC-ClinicDB  ] n=  61  Dice=0.9315  IoU=0.8769  Prec=0.9264  Rec=0.9435\n      [Kvasir-SEG    ] n= 100  Dice=0.9023  IoU=0.8386  Prec=0.9298  Rec=0.9013\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9088  IoU=0.8500  Prec=0.9183  Rec=0.9204  Loss=0.1636\n      [BKAI-IGH      ] n= 100  Dice=0.9042  IoU=0.8464  Prec=0.9059  Rec=0.9224\n      [CVC-ClinicDB  ] n=  62  Dice=0.9206  IoU=0.8638  Prec=0.9328  Rec=0.9254\n      [Kvasir-SEG    ] n= 100  Dice=0.9056  IoU=0.8446  Prec=0.9218  Rec=0.9146\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.7550  IoU=0.6716  Prec=0.8086  Rec=0.7679  Loss=0.4487\n      [CVC-ColonDB   ] n= 380  Dice=0.7305  IoU=0.6481  Prec=0.8216  Rec=0.7216\n      [ETIS-Larib    ] n= 196  Dice=0.7654  IoU=0.6791  Prec=0.7648  Rec=0.8116\n      [CVC-300       ] n=  60  Dice=0.8823  IoU=0.8069  Prec=0.8890  Rec=0.9053\n\n======================================================================\nENCODER: pvt_v2_b3\n======================================================================\n  ✓ Loaded 'pvt_v2_b3' — checkpoint epoch=49, train-time best val dice=0.9520\n  Params: 45.57M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9520  IoU=0.9143  Prec=0.9584  Rec=0.9529  Loss=0.0975\n      [BKAI-IGH      ] n= 100  Dice=0.9639  IoU=0.9328  Prec=0.9655  Rec=0.9658\n      [CVC-ClinicDB  ] n=  61  Dice=0.9567  IoU=0.9180  Prec=0.9558  Rec=0.9591\n      [Kvasir-SEG    ] n= 100  Dice=0.9388  IoU=0.8961  Prec=0.9540  Rec=0.9382\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9354  IoU=0.8919  Prec=0.9502  Rec=0.9384  Loss=0.1592\n      [BKAI-IGH      ] n= 100  Dice=0.9418  IoU=0.8993  Prec=0.9449  Rec=0.9518\n      [CVC-ClinicDB  ] n=  62  Dice=0.9272  IoU=0.8859  Prec=0.9601  Rec=0.9225\n      [Kvasir-SEG    ] n= 100  Dice=0.9342  IoU=0.8885  Prec=0.9499  Rec=0.9343\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.8444  IoU=0.7693  Prec=0.8582  Rec=0.8712  Loss=0.2836\n      [CVC-ColonDB   ] n= 380  Dice=0.8263  IoU=0.7499  Prec=0.8605  Rec=0.8391\n      [ETIS-Larib    ] n= 196  Dice=0.8595  IoU=0.7847  Prec=0.8400  Rec=0.9147\n      [CVC-300       ] n=  60  Dice=0.9107  IoU=0.8465  Prec=0.9111  Rec=0.9267\n\n======================================================================\nENCODER: res2net50_26w_4s\n======================================================================\n  ✓ Loaded 'res2net50_26w_4s' — checkpoint epoch=53, train-time best val dice=0.9267\n  Params: 26.12M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9267  IoU=0.8818  Prec=0.9452  Rec=0.9306  Loss=0.1357\n      [BKAI-IGH      ] n= 100  Dice=0.9320  IoU=0.8947  Prec=0.9376  Rec=0.9526\n      [CVC-ClinicDB  ] n=  61  Dice=0.9560  IoU=0.9169  Prec=0.9545  Rec=0.9591\n      [Kvasir-SEG    ] n= 100  Dice=0.9086  IoU=0.8538  Prec=0.9469  Rec=0.8982\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9299  IoU=0.8816  Prec=0.9382  Rec=0.9393  Loss=0.1349\n      [BKAI-IGH      ] n= 100  Dice=0.9327  IoU=0.8846  Prec=0.9325  Rec=0.9476\n      [CVC-ClinicDB  ] n=  62  Dice=0.9282  IoU=0.8822  Prec=0.9405  Rec=0.9384\n      [Kvasir-SEG    ] n= 100  Dice=0.9281  IoU=0.8781  Prec=0.9430  Rec=0.9311\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.8173  IoU=0.7432  Prec=0.8404  Rec=0.8383  Loss=0.3270\n      [CVC-ColonDB   ] n= 380  Dice=0.7906  IoU=0.7154  Prec=0.8414  Rec=0.7957\n      [ETIS-Larib    ] n= 196  Dice=0.8348  IoU=0.7585  Prec=0.8176  Rec=0.8851\n      [CVC-300       ] n=  60  Dice=0.9266  IoU=0.8677  Prec=0.9117  Rec=0.9480\n\n======================================================================\nENCODER: resnet34\n======================================================================\n  ✓ Loaded 'resnet34' — checkpoint epoch=43, train-time best val dice=0.9088\n  Params: 22.10M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9088  IoU=0.8527  Prec=0.9188  Rec=0.9194  Loss=0.1633\n      [BKAI-IGH      ] n= 100  Dice=0.9174  IoU=0.8670  Prec=0.9190  Rec=0.9324\n      [CVC-ClinicDB  ] n=  61  Dice=0.9297  IoU=0.8761  Prec=0.9249  Rec=0.9438\n      [Kvasir-SEG    ] n= 100  Dice=0.8909  IoU=0.8281  Prec=0.9181  Rec=0.8958\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9223  IoU=0.8684  Prec=0.9385  Rec=0.9251  Loss=0.1559\n      [BKAI-IGH      ] n= 100  Dice=0.9236  IoU=0.8711  Prec=0.9341  Rec=0.9328\n      [CVC-ClinicDB  ] n=  62  Dice=0.9137  IoU=0.8600  Prec=0.9337  Rec=0.9216\n      [Kvasir-SEG    ] n= 100  Dice=0.9262  IoU=0.8707  Prec=0.9465  Rec=0.9188\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.7795  IoU=0.7004  Prec=0.8258  Rec=0.7867  Loss=0.3647\n      [CVC-ColonDB   ] n= 380  Dice=0.7558  IoU=0.6752  Prec=0.8262  Rec=0.7462\n      [ETIS-Larib    ] n= 196  Dice=0.7873  IoU=0.7080  Prec=0.8097  Rec=0.8139\n      [CVC-300       ] n=  60  Dice=0.9028  IoU=0.8365  Prec=0.8830  Rec=0.9447\n\n======================================================================\nENCODER: resnet50\n======================================================================\n  ✓ Loaded 'resnet50' — checkpoint epoch=49, train-time best val dice=0.9171\n  Params: 25.98M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9171  IoU=0.8653  Prec=0.9302  Rec=0.9239  Loss=0.1444\n      [BKAI-IGH      ] n= 100  Dice=0.9253  IoU=0.8820  Prec=0.9223  Rec=0.9452\n      [CVC-ClinicDB  ] n=  61  Dice=0.9464  IoU=0.9009  Prec=0.9455  Rec=0.9507\n      [Kvasir-SEG    ] n= 100  Dice=0.8960  IoU=0.8329  Prec=0.9297  Rec=0.8932\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9174  IoU=0.8618  Prec=0.9318  Rec=0.9195  Loss=0.1509\n      [BKAI-IGH      ] n= 100  Dice=0.9173  IoU=0.8653  Prec=0.9236  Rec=0.9258\n      [CVC-ClinicDB  ] n=  62  Dice=0.9235  IoU=0.8701  Prec=0.9388  Rec=0.9255\n      [Kvasir-SEG    ] n= 100  Dice=0.9136  IoU=0.8528  Prec=0.9360  Rec=0.9087\n","output_type":"stream"},{"name":"stderr","text":"                                                                          \r","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.7813  IoU=0.7048  Prec=0.8219  Rec=0.7977  Loss=0.3788\n      [CVC-ColonDB   ] n= 380  Dice=0.7558  IoU=0.6755  Prec=0.8239  Rec=0.7600\n      [ETIS-Larib    ] n= 196  Dice=0.7923  IoU=0.7216  Prec=0.7998  Rec=0.8258\n      [CVC-300       ] n=  60  Dice=0.9057  IoU=0.8372  Prec=0.8905  Rec=0.9344\n\n======================================================================\nENCODER: swin_tiny_patch4_window7_224\n======================================================================\n  ✓ Loaded 'swin_tiny_patch4_window7_224' — checkpoint epoch=55, train-time best val dice=0.9386\n  Params: 28.61M\n","output_type":"stream"},{"name":"stderr","text":"                                                                  \r","output_type":"stream"},{"name":"stdout","text":"\n  --- VALIDATION ---\n    Dice=0.9386  IoU=0.8945  Prec=0.9462  Rec=0.9435  Loss=0.1116\n      [BKAI-IGH      ] n= 100  Dice=0.9452  IoU=0.9058  Prec=0.9421  Rec=0.9605\n      [CVC-ClinicDB  ] n=  61  Dice=0.9547  IoU=0.9142  Prec=0.9506  Rec=0.9603\n      [Kvasir-SEG    ] n= 100  Dice=0.9255  IoU=0.8755  Prec=0.9483  Rec=0.9212\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n  --- IN-DISTRIBUTION TEST ---\n    Dice=0.9357  IoU=0.8883  Prec=0.9397  Rec=0.9445  Loss=0.1301\n      [BKAI-IGH      ] n= 100  Dice=0.9401  IoU=0.8937  Prec=0.9351  Rec=0.9546\n      [CVC-ClinicDB  ] n=  62  Dice=0.9349  IoU=0.8873  Prec=0.9473  Rec=0.9363\n      [Kvasir-SEG    ] n= 100  Dice=0.9321  IoU=0.8836  Prec=0.9400  Rec=0.9391\n","output_type":"stream"},{"name":"stderr","text":"                                                                          ","output_type":"stream"},{"name":"stdout","text":"\n  --- BENCHMARK (UNSEEN) ---\n    Dice=0.8084  IoU=0.7331  Prec=0.8459  Rec=0.8195  Loss=0.3340\n      [CVC-ColonDB   ] n= 380  Dice=0.7892  IoU=0.7128  Prec=0.8494  Rec=0.7903\n      [ETIS-Larib    ] n= 196  Dice=0.8145  IoU=0.7388  Prec=0.8296  Rec=0.8332\n      [CVC-300       ] n=  60  Dice=0.9130  IoU=0.8492  Prec=0.8890  Rec=0.9511\n\n======================================================================\nNOTE: encoders with no checkpoint path supplied at all (not evaluated):\n  - swin_base_patch4_window7_224\n\nFull results saved to: /kaggle/working/full_encoder_comparison.csv\n\n\n====================================================================================================\nFINAL LEADERBOARD — sorted by Benchmark (unseen) Dice\n====================================================================================================\n Rank                      Encoder  Val Dice  Val IoU  Indist Dice  Indist IoU  Bench Dice  Bench IoU  Bench Precision  Bench Recall\n    1                       mit_b3    0.9493   0.9104       0.9368      0.8931      0.8444     0.7689           0.8682        0.8609\n    2                    pvt_v2_b3    0.9520   0.9143       0.9354      0.8919      0.8444     0.7693           0.8582        0.8712\n    3                       mit_b0    0.9336   0.8881       0.9283      0.8796      0.8197     0.7420           0.8475        0.8380\n    4             res2net50_26w_4s    0.9267   0.8818       0.9299      0.8816      0.8173     0.7432           0.8404        0.8383\n    5 swin_tiny_patch4_window7_224    0.9386   0.8945       0.9357      0.8883      0.8084     0.7331           0.8459        0.8195\n    6                     resnet50    0.9171   0.8653       0.9174      0.8618      0.7813     0.7048           0.8219        0.7977\n    7                     resnet34    0.9088   0.8527       0.9223      0.8684      0.7795     0.7004           0.8258        0.7867\n    8        mobilenetv3_large_100    0.9109   0.8523       0.9088      0.8500      0.7550     0.6716           0.8086        0.7679\n\n====================================================================================================\nBENCHMARK PER-DATASET DICE (ColonDB / ETIS / CVC-300 separately)\n  -> If CVC-300 is much higher than ColonDB/ETIS for every\n     encoder, suspect train/benchmark overlap via CVC-ClinicDB\n     as noted in the split script docstring — verify before\n     trusting CVC-300 numbers.\n====================================================================================================\n                     Encoder  CVC-ColonDB Dice  CVC-ColonDB n  ETIS-Larib Dice  ETIS-Larib n  CVC-300 Dice  CVC-300 n\n                   pvt_v2_b3            0.8263            380           0.8595           196        0.9107         60\n                      mit_b3            0.8237            380           0.8601           196        0.9224         60\n                      mit_b0            0.8033            380           0.8231           196        0.9148         60\n            res2net50_26w_4s            0.7906            380           0.8348           196        0.9266         60\nswin_tiny_patch4_window7_224            0.7892            380           0.8145           196        0.9130         60\n                    resnet34            0.7558            380           0.7873           196        0.9028         60\n                    resnet50            0.7558            380           0.7923           196        0.9057         60\n       mobilenetv3_large_100            0.7305            380           0.7654           196        0.8823         60\n\nLeaderboard saved to: /kaggle/working/leaderboard_summary.csv\n\n======================================================================\nDONE. Outputs:\n  - /kaggle/working/full_encoder_comparison.csv  (full per-split, per-dataset rows)\n  - /kaggle/working/leaderboard_summary.csv  (ranked summary)\n======================================================================\n","output_type":"stream"},{"name":"stderr","text":"\r","output_type":"stream"}],"execution_count":9},{"cell_type":"code","source":"!pip install -q segmentation-models-pytorch\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T01:18:53.261394Z","iopub.execute_input":"2026-09-08T01:18:53.261933Z","iopub.status.idle":"2026-09-08T01:19:00.02328Z","shell.execute_reply.started":"2026-09-08T01:18:53.261904Z","shell.execute_reply":"2026-09-08T01:19:00.022542Z"}},"outputs":[{"name":"stdout","text":"\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m154.8/154.8 kB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m\n\u001b[?25h","output_type":"stream"}],"execution_count":8},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##uncertainty quantification","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T17:17:31.536556Z","iopub.execute_input":"2026-08-25T17:17:31.536847Z","iopub.status.idle":"2026-08-25T17:17:31.54139Z","shell.execute_reply.started":"2026-08-25T17:17:31.536814Z","shell.execute_reply":"2026-08-25T17:17:31.540745Z"}},"outputs":[],"execution_count":1},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##uncertainty quantification","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##PVT ,C DROPOUT","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T17:19:15.744503Z","iopub.execute_input":"2026-08-25T17:19:15.744985Z","iopub.status.idle":"2026-08-25T17:19:15.748695Z","shell.execute_reply.started":"2026-08-25T17:19:15.744951Z","shell.execute_reply":"2026-08-25T17:19:15.747881Z"}},"outputs":[],"execution_count":2},{"cell_type":"code","source":"\"\"\"\nPolypSeg — PVTv2-b3 Checkpoint A: MC-Dropout-Ready Retrain\n=============================================================\nPurpose: retrain the PVTv2-b3 baseline with dropout added to the\nDECODER only, so MC Dropout produces a controlled, deliberately-tuned\nstochastic signal at inference time -- rather than relying on\nPVTv2-b3's internal timm dropout/drop-path defaults (tuned for\ntraining regularization, not for inference-time uncertainty\ndiversity).\n\nThis checkpoint is used for THREE uncertainty methods, at zero extra\ntraining cost beyond this one run:\n    - Single       (1 - max prediction probability)\n    - TTA          (variance across augmented-input forward passes)\n    - MC Dropout   (variance across T stochastic forward passes,\n                    decoder dropout kept active at inference)\n\nEverything else is IDENTICAL to the original 8-encoder ablation\nscript (baseline_segmentation-style config, decoder skeleton, loss,\noptimizer, splits, augmentation, seed) -- the ONLY change is:\n    nn.Dropout2d(p=0.2) inserted after every ConvBNReLU block in the\n    decoder (lat1-4, fuse1-3), NOT inside the final seg_head conv.\n\nRationale for placement: dropout right before the raw output layer\njust injects noise into logits rather than diversifying the\nsegmentation DECISION itself. Keeping it in the feature-fusion path\n(laterals + fuses) gives MC sampling something structurally\nmeaningful to vary.\n\nLoss function: UNCHANGED (BCE + Dice) -- this checkpoint is NOT the\nevidential checkpoint. See train_pvt_evidential.py for that one.\n\nRun this in the same Kaggle environment/session where train_split.csv,\nval_split.csv, indist_test.csv, benchmark_test.csv already exist.\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCFG = {\n    # Paths\n    \"train_csv\"         : \"train_split.csv\",\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n    \"save_dir\"          : \"checkpoints_uncertainty\",\n\n    # Fixed encoder for this checkpoint\n    \"encoder_name\"       : \"pvt_v2_b3\",\n\n    # Image — identical to original ablation\n    \"img_size\"          : 352,\n    \"mean\"              : [0.485, 0.456, 0.406],\n    \"std\"               : [0.229, 0.224, 0.225],\n\n    # Training — identical to original ablation\n    \"batch_size\"        : 8,\n    \"num_workers\"       : 2,\n    \"seed\"              : 42,\n    \"num_epochs\"        : 60,\n\n    # Optimizer — identical to original ablation\n    \"encoder_lr\"        : 1e-4,\n    \"decoder_lr\"        : 3e-4,\n    \"weight_decay\"      : 1e-4,\n    \"grad_clip\"         : 1.0,\n    \"min_lr\"            : 1e-6,\n\n    # Early stopping — identical to original ablation\n    \"patience\"          : 12,\n\n    # Decoder — identical width to original ablation\n    \"dec_channels\"      : 64,\n\n    # ---- THE ONLY NEW HYPERPARAMETER FOR THIS CHECKPOINT ----\n    \"decoder_dropout_p\" : 0.2,\n    # Matches the 0.2 rate already used elsewhere in the project's\n    # MC Dropout config, for consistency across the pipeline.\n    # -----------------------------------------------------------\n\n    # MC Dropout inference-time setting (used later at eval time,\n    # kept here so it travels with the checkpoint's config)\n    \"mc_passes_T\"        : 4,\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\n\n# ============================================================\n# REPRODUCIBILITY\n# ============================================================\n\ndef set_seed(seed):\n    import random\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark     = False\n\n\n# ============================================================\n# DATA  (identical to the 8-encoder ablation script)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_train_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.ShiftScaleRotate(\n            shift_limit=0.05, scale_limit=0.1,\n            rotate_limit=30,\n            border_mode=cv2.BORDER_CONSTANT, value=0, p=0.5),\n        A.RandomBrightnessContrast(\n            brightness_limit=0.2, contrast_limit=0.2, p=0.4),\n        A.HueSaturationValue(\n            hue_shift_limit=10, sat_shift_limit=20,\n            val_shift_limit=10, p=0.3),\n        A.GaussNoise(var_limit=(10., 50.), p=0.2),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(\n                image.transpose(2, 0, 1)).float() / 255.\n            mask_t = torch.from_numpy(mask).float().unsqueeze(0) / 255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_dataloaders(cfg):\n    print(\"Loading CSVs...\")\n    train_df     = pd.read_csv(cfg['train_csv'])\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    print(f\"  Split fingerprint -> \"\n          f\"train={len(train_df)} (hash={pd.util.hash_pandas_object(train_df['stem']).sum()}) \"\n          f\"val={len(val_df)} indist={len(indist_df)} bench={len(benchmark_df)}\")\n\n    sz = cfg['img_size']\n    nw = cfg['num_workers']\n    bs = cfg['batch_size']\n\n    train_loader = DataLoader(\n        PolypSegDataset(train_df, get_train_transform(sz)),\n        batch_size=bs, shuffle=True,\n        num_workers=nw, pin_memory=True, drop_last=True)\n    val_loader = DataLoader(\n        PolypSegDataset(val_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(\n        PolypSegDataset(indist_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(\n        PolypSegDataset(benchmark_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n\n    print(f\"  Train:{len(train_df)} | Val:{len(val_df)} | \"\n          f\"Indist:{len(indist_df)} | Bench:{len(benchmark_df)}\")\n\n    return {\n        'train'         : train_loader,\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# ENCODER — PVTv2-b3 only (fixed for this checkpoint)\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    def __init__(self, backbone, source='timm', needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source\n        self.needs_permute = needs_permute\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n        return tuple(feats)\n\n\ndef build_pvt_encoder(pretrained=True, img_size=352):\n    m = timm.create_model(\n        \"pvt_v2_b3\", pretrained=pretrained,\n        features_only=True, out_indices=(0, 1, 2, 3))\n    wrapper = EncoderWrapper(m, source='timm')\n\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, img_size, img_size)\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n    wrapper.train()\n\n    return wrapper, enc_channels\n\n\n# ============================================================\n# DECODER — WITH DROPOUT ADDED (the one change for Checkpoint A)\n# ============================================================\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride,\n                      padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass DropoutFPNDecoder(nn.Module):\n    \"\"\"\n    Same skeleton as PlainFPNDecoder (lateral -> top-down fuse ->\n    single head, width 64 throughout), with ONE addition:\n    nn.Dropout2d(p=dropout_p) after every ConvBNReLU block in the\n    lateral and fuse paths.\n\n    Dropout is DELIBERATELY NOT placed inside seg_head's final conv --\n    dropout immediately before the raw logit output just adds noise\n    to the output rather than diversifying the segmentation decision\n    upstream of it. Keeping dropout in the feature-fusion path gives\n    MC sampling something structurally meaningful to vary between\n    passes.\n    \"\"\"\n    def __init__(self, enc_channels, dec_channels=64, dropout_p=0.2):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        # One shared dropout module definition, applied after every\n        # lateral/fuse block. Kept as separate named modules (not one\n        # reused instance) so `model.apply()` targeting can reliably\n        # find every nn.Dropout2d in the decoder, and so behavior is\n        # unambiguous under DataParallel / checkpoint loading.\n        self.drop_lat4  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat3  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat2  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat1  = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse3 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse2 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse1 = nn.Dropout2d(p=dropout_p)\n\n        # Seg head: NO dropout here, deliberately (see docstring).\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, 1, 1),\n        )\n\n        self.dropout_p = dropout_p\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.drop_lat4(self.lat4(f4))\n        p3 = self.drop_lat3(self.lat3(f3))\n        p2 = self.drop_lat2(self.lat2(f2))\n        p1 = self.drop_lat1(self.lat1(f1))\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.drop_fuse3(self.fuse3(torch.cat([up(p4, p3), p3], dim=1)))\n        p2f = self.drop_fuse2(self.fuse2(torch.cat([up(p3f, p2), p2], dim=1)))\n        p1f = self.drop_fuse1(self.fuse1(torch.cat([up(p2f, p1), p1], dim=1)))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        mask_logits = self.seg_head(out)\n        return mask_logits\n\n\nclass PolypSegMCDropout(nn.Module):\n    \"\"\"\n    PVTv2-b3 encoder + dropout-augmented FPN decoder.\n    Architecturally identical to the original PolypSegEncoderCompare\n    EXCEPT for the decoder's added Dropout2d layers.\n    \"\"\"\n    def __init__(self, pretrained=True, dec_channels=64,\n                 dropout_p=0.2, img_size=352):\n        super().__init__()\n        self.encoder, enc_channels = build_pvt_encoder(pretrained, img_size)\n        self.decoder = DropoutFPNDecoder(enc_channels, dec_channels, dropout_p)\n        self.encoder_name = \"pvt_v2_b3_mcdropout\"\n        self.enc_channels = enc_channels\n        self.dropout_p = dropout_p\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        mask_logits = self.decoder(features, (H, W))\n        return {'mask_logits': mask_logits}\n\n    def enable_mc_dropout(self):\n        \"\"\"\n        Forces every nn.Dropout2d module in the decoder into\n        train-mode behavior (i.e. actually dropping units), while\n        the rest of the model (BatchNorm running stats, encoder,\n        etc.) stays in eval mode. Call this AFTER model.eval() at\n        inference time, immediately before running the T stochastic\n        MC Dropout passes.\n        \"\"\"\n        self.eval()\n        for m in self.modules():\n            if isinstance(m, nn.Dropout2d):\n                m.train()\n\n\n# ============================================================\n# LOSS — identical to original ablation (BCE + Dice, unchanged)\n# ============================================================\n\ndef dice_loss(pred, target, smooth=1.0):\n    p = torch.sigmoid(pred)\n    p = p.view(p.size(0), -1)\n    t = target.view(target.size(0), -1)\n    i = (p * t).sum(1)\n    return 1 - (2 * i + smooth) / (p.sum(1) + t.sum(1) + smooth)\n\n\ndef bce_dice_loss(pred, target):\n    bce = F.binary_cross_entropy_with_logits(\n        pred, target, reduction='mean')\n    return bce + dice_loss(pred, target).mean()\n\n\n# ============================================================\n# METRICS — identical to original ablation\n# ============================================================\n\nclass MetricTracker:\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses      = []\n        self.dice_scores = []\n        self.iou_scores  = []\n        self.prec_scores = []\n        self.rec_scores  = []\n        self.dataset_results = {}\n\n    @torch.no_grad()\n    def update(self, outputs, batch, loss):\n        self.losses.append(loss.item())\n\n        pred = (torch.sigmoid(outputs['mask_logits']) > 0.5).float()\n        tgt  = batch['mask'].to(pred.device)\n\n        pf = pred.view(pred.size(0), -1)\n        tf = tgt.view(tgt.size(0), -1)\n        tp = (pf * tf).sum(1)\n        fp = pf.sum(1) - tp\n        fn = tf.sum(1) - tp\n\n        dice = (2 * tp / (2 * tp + fp + fn + 1e-6)).mean().item()\n        iou  = (tp / (tp + fp + fn + 1e-6)).mean().item()\n        prec = (tp / (tp + fp + 1e-6)).mean().item()\n        rec  = (tp / (tp + fn + 1e-6)).mean().item()\n\n        self.dice_scores.append(dice)\n        self.iou_scores.append(iou)\n        self.prec_scores.append(prec)\n        self.rec_scores.append(rec)\n\n        for i, ds in enumerate(batch['dataset']):\n            if ds not in self.dataset_results:\n                self.dataset_results[ds] = {\n                    'dice': [], 'iou': [], 'prec': [], 'rec': []}\n            d = self.dataset_results[ds]\n            p_i, t_i = pred[i:i+1].view(-1), tgt[i:i+1].view(-1)\n            tp_i = (p_i * t_i).sum()\n            fp_i = p_i.sum() - tp_i\n            fn_i = t_i.sum() - tp_i\n            d['dice'].append((2*tp_i/(2*tp_i+fp_i+fn_i+1e-6)).item())\n            d['iou'].append((tp_i/(tp_i+fp_i+fn_i+1e-6)).item())\n            d['prec'].append((tp_i/(tp_i+fp_i+1e-6)).item())\n            d['rec'].append((tp_i/(tp_i+fn_i+1e-6)).item())\n\n    def compute(self):\n        gross = dict(\n            loss      = np.mean(self.losses),\n            dice      = np.mean(self.dice_scores),\n            iou       = np.mean(self.iou_scores),\n            precision = np.mean(self.prec_scores),\n            recall    = np.mean(self.rec_scores),\n        )\n        per_dataset = {}\n        for ds_key, d in self.dataset_results.items():\n            per_dataset[ds_key] = dict(\n                n         = len(d['dice']),\n                dice      = np.mean(d['dice']),\n                iou       = np.mean(d['iou']),\n                precision = np.mean(d['prec']),\n                recall    = np.mean(d['rec']),\n            )\n        gross['per_dataset'] = per_dataset\n        return gross\n\n\n# ============================================================\n# OPTIMIZER — identical to original ablation\n# ============================================================\n\ndef build_optimizer(model, cfg):\n    enc_params = list(model.encoder.parameters())\n    enc_ids    = {id(p) for p in enc_params}\n    other      = [p for p in model.parameters() if id(p) not in enc_ids]\n    opt = torch.optim.AdamW([\n        {'params': enc_params, 'lr': cfg['encoder_lr']},\n        {'params': other,      'lr': cfg['decoder_lr']},\n    ], weight_decay=cfg['weight_decay'])\n    sch = torch.optim.lr_scheduler.CosineAnnealingLR(\n        opt, T_max=cfg['num_epochs'], eta_min=cfg['min_lr'])\n    return opt, sch\n\n\n# ============================================================\n# TRAIN / VALIDATE\n# ============================================================\n\ndef train_one_epoch(model, loader, optimizer, device, cfg):\n    model.train()   # decoder dropout naturally active here (standard training)\n    tracker = MetricTracker()\n    pbar = tqdm(loader, desc='  Train', leave=False, ncols=100)\n    for batch in pbar:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n\n        optimizer.zero_grad()\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(\n            model.parameters(), cfg['grad_clip'])\n        optimizer.step()\n\n        tracker.update(outputs, batch, loss)\n        pbar.set_postfix(\n            loss=f\"{loss.item():.4f}\",\n            dice=f\"{tracker.dice_scores[-1]:.4f}\")\n    return tracker.compute()\n\n\n@torch.no_grad()\ndef validate(model, loader, device):\n    \"\"\"\n    Standard validation: dropout OFF (model.eval() disables Dropout2d\n    normally), giving a clean, deterministic Dice for model selection\n    / early stopping. MC Dropout's STOCHASTIC inference is a separate,\n    later step (enable_mc_dropout()) done only at final uncertainty\n    evaluation time, not during training-time validation.\n    \"\"\"\n    model.eval()\n    tracker = MetricTracker()\n    for batch in loader:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n    return tracker.compute()\n\n\n# ============================================================\n# CHECKPOINTING\n# ============================================================\n\nclass CheckpointManager:\n    def __init__(self, save_dir, patience):\n        self.save_dir   = Path(save_dir)\n        self.save_dir.mkdir(parents=True, exist_ok=True)\n        self.patience   = patience\n        self.best_dice  = 0.\n        self.best_epoch = 0\n        self.counter    = 0\n\n    def step(self, model, optimizer, scheduler, epoch, metrics, extra_cfg=None):\n        dice     = metrics['dice']\n        improved = dice > self.best_dice\n        if improved:\n            self.best_dice  = dice\n            self.best_epoch = epoch\n            self.counter    = 0\n            payload = dict(\n                epoch=epoch, model=model.state_dict(),\n                optimizer=optimizer.state_dict(),\n                scheduler=scheduler.state_dict(),\n                best_dice=self.best_dice,\n                encoder_name=model.encoder_name,\n                dropout_p=getattr(model, 'dropout_p', None),\n                checkpoint_type='mc_dropout',\n            )\n            if extra_cfg:\n                payload['cfg'] = extra_cfg\n            torch.save(payload, self.save_dir / 'best_model.pth')\n            print(f\"  \\u2713 Saved best (dice={dice:.4f})\")\n        else:\n            self.counter += 1\n        return self.counter >= self.patience\n\n    def load_best(self, model, device):\n        ck = torch.load(\n            self.save_dir / 'best_model.pth',\n            map_location=device, weights_only=False)\n        model.load_state_dict(ck['model'])\n        print(f\"  Loaded best epoch {ck['epoch']} \"\n              f\"(dice={ck['best_dice']:.4f})\")\n        return model\n\n\n# ============================================================\n# LOGGING\n# ============================================================\n\ndef print_epoch_summary(epoch, num_epochs, train_m, val_m, lr):\n    print(f\"\\nEpoch [{epoch+1:>3}/{num_epochs}]  LR={lr:.2e}\")\n    print(f\"  {'GROSS METRIC':14s}  {'Train':>8}  {'Val':>8}\")\n    print(f\"  {'-'*36}\")\n    for key, label in [\n        ('loss',      'Loss'),\n        ('dice',      'Dice'),\n        ('iou',       'IoU'),\n        ('precision', 'Precision'),\n        ('recall',    'Recall'),\n    ]:\n        print(f\"  {label:14s}  {train_m.get(key,0):8.4f}  {val_m.get(key,0):8.4f}\")\n\n    if val_m.get('per_dataset'):\n        print(f\"  {'-'*36}\")\n        print(f\"  VAL PER-DATASET:\")\n        for ds_key, d in val_m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"    {ds_name:15s} n={d['n']:>4}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n\ndef append_epoch_log(log_path, epoch, tag, train_m, val_m, lr):\n    rows = []\n    for split_name, m in [('train', train_m), ('val', val_m)]:\n        rows.append(dict(\n            checkpoint=tag, epoch=epoch+1, split=split_name,\n            dataset='ALL', lr=lr,\n            loss=m.get('loss'), dice=m.get('dice'), iou=m.get('iou'),\n            precision=m.get('precision'), recall=m.get('recall'),\n        ))\n        for ds_key, d in m.get('per_dataset', {}).items():\n            rows.append(dict(\n                checkpoint=tag, epoch=epoch+1, split=split_name,\n                dataset=DATASET_INFO.get(ds_key, ds_key), lr=lr,\n                loss=None, dice=d['dice'], iou=d['iou'],\n                precision=d['precision'], recall=d['recall'],\n            ))\n    df = pd.DataFrame(rows)\n    header = not Path(log_path).exists()\n    df.to_csv(log_path, mode='a', header=header, index=False)\n\n\n# ============================================================\n# FINAL EVALUATION (deterministic pass — dropout OFF)\n# For MC Dropout's actual stochastic uncertainty extraction, use a\n# separate downstream script that calls enable_mc_dropout() and runs\n# T forward passes; this evaluation here is the standard single-pass\n# accuracy check used to GATE the checkpoint (Step 4 of the plan --\n# confirm segmentation quality hasn't regressed before trusting any\n# uncertainty numbers built on top of it).\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_single_loader(model, loader, device, split_name):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in tqdm(loader, desc=f'  Eval {split_name}', leave=False):\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        loss = bce_dice_loss(outputs['mask_logits'], target)\n        tracker.update(outputs, batch, loss)\n\n    m = tracker.compute()\n\n    print(f\"\\n{'='*60}\\n  {split_name.upper()}\\n{'='*60}\")\n    print(f\"  Dice     : {m['dice']:.4f}\")\n    print(f\"  IoU      : {m['iou']:.4f}\")\n    print(f\"  Precision: {m['precision']:.4f}\")\n    print(f\"  Recall   : {m['recall']:.4f}\")\n\n    if m['per_dataset']:\n        print(f\"\\n  PER-DATASET BREAKDOWN\")\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"  [{ds_name}] n={d['n']}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n    return m\n\n\n@torch.no_grad()\ndef evaluate_all_datasets(model, data, device, save_dir, tag,\n                          reference_dice=0.8444):\n    print(\"\\n\" + \"=\"*60)\n    print(f\"FINAL EVALUATION (deterministic, dropout OFF) — {tag}\")\n    print(\"=\"*60)\n\n    results = {}\n    results['val']       = evaluate_single_loader(model, data['val'], device, 'VALIDATION SET')\n    results['indist']    = evaluate_single_loader(model, data['indist_test'], device, 'IN-DISTRIBUTION TEST')\n    results['benchmark'] = evaluate_single_loader(model, data['benchmark_test'], device, 'BENCHMARK (UNSEEN)')\n\n    # --- Step 4 gate: has accuracy regressed vs. the original\n    # (no-dropout) PVTv2-b3 benchmark Dice of 0.8444? ---\n    bench_dice = results['benchmark']['dice']\n    delta = bench_dice - reference_dice\n    print(f\"\\n{'='*60}\")\n    print(f\"REGRESSION GATE CHECK\")\n    print(f\"  Original PVTv2-b3 benchmark Dice : {reference_dice:.4f}\")\n    print(f\"  This checkpoint benchmark Dice   : {bench_dice:.4f}\")\n    print(f\"  Delta                            : {delta:+.4f}\")\n    if delta < -0.02:\n        print(f\"  \\u26a0 WARNING: benchmark Dice dropped by more than 0.02 \"\n              f\"vs. original. Consider reducing decoder_dropout_p \"\n              f\"before trusting downstream uncertainty results.\")\n    else:\n        print(f\"  \\u2713 Within acceptable range — safe to proceed to \"\n              f\"MC Dropout / TTA / Single uncertainty extraction.\")\n    print(f\"{'='*60}\")\n\n    rows = []\n    total_params = sum(p.numel() for p in model.parameters())\n    for split, m in results.items():\n        rows.append(dict(\n            checkpoint=tag, params=total_params, split=split,\n            dice=round(m['dice'],4), iou=round(m['iou'],4),\n            precision=round(m['precision'],4), recall=round(m['recall'],4),\n        ))\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            rows.append(dict(\n                checkpoint=tag, params=total_params,\n                split=f\"{split}_{ds_name}\",\n                dice=round(d['dice'],4), iou=round(d['iou'],4),\n                precision=round(d['precision'],4), recall=round(d['recall'],4),\n            ))\n\n    comparison_path = Path(save_dir) / 'uncertainty_checkpoint_results.csv'\n    df = pd.DataFrame(rows)\n    header = not comparison_path.exists()\n    df.to_csv(comparison_path, mode='a', header=header, index=False)\n    print(f\"\\n  Results appended to: {comparison_path}\")\n\n    return results\n\n\n# ============================================================\n# MAIN TRAINING\n# ============================================================\n\ndef train(cfg):\n    set_seed(cfg['seed'])\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n    print(f\"Checkpoint: PVTv2-b3 + decoder dropout (p={cfg['decoder_dropout_p']})\")\n    print(f\"Unlocks: Single, TTA, MC Dropout (T={cfg['mc_passes_T']})\")\n\n    data = create_dataloaders(cfg)\n\n    model = PolypSegMCDropout(\n        pretrained=True,\n        dec_channels=cfg['dec_channels'],\n        dropout_p=cfg['decoder_dropout_p'],\n        img_size=cfg['img_size'],\n    ).to(device)\n\n    total = sum(p.numel() for p in model.parameters())\n    n_dropout_modules = sum(1 for m in model.modules() if isinstance(m, nn.Dropout2d))\n    print(f\"Encoder output channels: {model.enc_channels}\")\n    print(f\"Params: {total/1e6:.2f}M total\")\n    print(f\"Dropout2d modules in decoder: {n_dropout_modules} \"\n          f\"(expected 7: lat1-4 + fuse1-3)\")\n    assert n_dropout_modules == 7, \\\n        \"Expected exactly 7 Dropout2d modules in decoder — check DropoutFPNDecoder.\"\n\n    optimizer, scheduler = build_optimizer(model, cfg)\n    save_dir = Path(cfg['save_dir'])\n    tag = \"pvt_v2_b3_mcdropout\"\n    ckpt = CheckpointManager(save_dir / tag, cfg['patience'])\n    epoch_log_path = save_dir / f\"epoch_log_{tag}.csv\"\n\n    print(\"\\n\" + \"=\"*60)\n    print(f\"TRAINING — {tag}\")\n    print(\"  Decoder: FPN + Dropout2d(p={:.2f}) after every lateral/fuse block\"\n          .format(cfg['decoder_dropout_p']))\n    print(\"  Loss   : BCE + Dice (UNCHANGED from original ablation)\")\n    print(\"=\"*60)\n\n    for epoch in range(cfg['num_epochs']):\n        train_m = train_one_epoch(model, data['train'], optimizer, device, cfg)\n        val_m   = validate(model, data['val'], device)\n\n        scheduler.step()\n        lr = optimizer.param_groups[0]['lr']\n\n        print_epoch_summary(epoch, cfg['num_epochs'], train_m, val_m, lr)\n        append_epoch_log(epoch_log_path, epoch, tag, train_m, val_m, lr)\n\n        stop = ckpt.step(model, optimizer, scheduler, epoch, val_m, extra_cfg=cfg)\n        if stop:\n            print(f\"\\nEarly stopping at epoch {epoch+1}. \"\n                  f\"Best: epoch {ckpt.best_epoch+1} (dice={ckpt.best_dice:.4f})\")\n            break\n\n    model = ckpt.load_best(model, device)\n    results = evaluate_all_datasets(model, data, device, cfg['save_dir'], tag)\n\n    return model, results\n\n\n# ============================================================\n# MC DROPOUT INFERENCE — the payoff of this checkpoint\n# ============================================================\n\n@torch.no_grad()\ndef mc_dropout_predict(model, image, T=4):\n    \"\"\"\n    Runs T stochastic forward passes with decoder dropout ACTIVE,\n    returns (mean_prob, uncertainty_map) where uncertainty is the\n    per-pixel variance across the T passes -- matching Eq. (3) of\n    the base-paper formulation: U = (1/T) * sum_i (Y_hat_i - Y_bar)^2\n\n    model must already be loaded with trained weights.\n    \"\"\"\n    model.enable_mc_dropout()  # decoder dropout active, rest in eval mode\n\n    probs = []\n    for _ in range(T):\n        logits = model(image)['mask_logits']\n        probs.append(torch.sigmoid(logits))\n    probs = torch.stack(probs, dim=0)          # [T, B, 1, H, W]\n\n    mean_prob = probs.mean(dim=0)               # [B, 1, H, W]\n    uncertainty = probs.var(dim=0)               # [B, 1, H, W]  <- Eq. (3)\n\n    model.eval()  # restore full eval mode after MC sampling\n    return mean_prob, uncertainty\n\n\n@torch.no_grad()\ndef tta_predict(model, image, cfg):\n    \"\"\"\n    TTA uncertainty: same trained checkpoint, dropout OFF (standard\n    eval mode), predictions varied by flipping/rotating the INPUT\n    instead of the weights. Matches Eq. (4) of the base-paper\n    formulation. Uses the same flip/rotate family the model was\n    already trained to be invariant to (see get_train_transform),\n    which reduces risk of TTA producing degenerate predictions on\n    augmented views it never saw analogues of during training.\n    \"\"\"\n    model.eval()  # dropout OFF — TTA varies the input, not the weights\n\n    transforms = [\n        lambda x: x,                                  # identity\n        lambda x: torch.flip(x, dims=[3]),             # horizontal flip\n        lambda x: torch.flip(x, dims=[2]),             # vertical flip\n        lambda x: torch.rot90(x, k=1, dims=[2, 3]),    # 90-degree rotation\n    ]\n    inverse_transforms = [\n        lambda x: x,\n        lambda x: torch.flip(x, dims=[3]),\n        lambda x: torch.flip(x, dims=[2]),\n        lambda x: torch.rot90(x, k=-1, dims=[2, 3]),\n    ]\n\n    probs = []\n    for fwd, inv in zip(transforms, inverse_transforms):\n        aug_img = fwd(image)\n        logits = model(aug_img)['mask_logits']\n        prob = torch.sigmoid(logits)\n        prob = inv(prob)  # map prediction back to original orientation\n        probs.append(prob)\n    probs = torch.stack(probs, dim=0)\n\n    mean_prob = probs.mean(dim=0)\n    uncertainty = probs.var(dim=0)\n    return mean_prob, uncertainty\n\n\n@torch.no_grad()\ndef single_predict(model, image):\n    \"\"\"\n    Single deterministic method: uncertainty = 1 - max(prediction\n    probability). Matches Eq. (1) of the base-paper formulation,\n    adapted for binary segmentation (max of p and 1-p per pixel).\n    \"\"\"\n    model.eval()\n    logits = model(image)['mask_logits']\n    prob = torch.sigmoid(logits)\n    max_prob = torch.maximum(prob, 1 - prob)\n    uncertainty = 1 - max_prob\n    return prob, uncertainty\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\nif __name__ == '__main__':\n    print(\"=\"*60)\n    print(\"SANITY CHECK — PVTv2-b3 MC-Dropout-ready decoder\")\n    print(\"=\"*60)\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    model  = PolypSegMCDropout(\n        pretrained=False, dropout_p=CFG['decoder_dropout_p'],\n        img_size=CFG['img_size']).to(device)\n\n    dummy_img = torch.randn(2, 3, CFG['img_size'], CFG['img_size']).to(device)\n\n    # 1. Standard forward pass (eval mode, dropout off)\n    model.eval()\n    with torch.no_grad():\n        out = model(dummy_img)\n    print(\"Standard forward pass (dropout OFF):\")\n    print(f\"  mask_logits shape: {list(out['mask_logits'].shape)}\")\n\n    # 2. MC Dropout sanity check: two stochastic passes should DIFFER\n    model.enable_mc_dropout()\n    with torch.no_grad():\n        out_a = torch.sigmoid(model(dummy_img)['mask_logits'])\n        out_b = torch.sigmoid(model(dummy_img)['mask_logits'])\n    diff = (out_a - out_b).abs().mean().item()\n    print(f\"\\nMC Dropout sanity check (two stochastic passes on same input):\")\n    print(f\"  Mean absolute difference: {diff:.6f}\")\n    assert diff > 1e-6, \\\n        \"MC Dropout passes are IDENTICAL — dropout is not actually active. Check enable_mc_dropout().\"\n    print(f\"  \\u2713 Passes differ as expected — dropout is active during MC sampling.\")\n\n    # 3. Confirm eval() after MC sampling correctly restores determinism\n    model.eval()\n    with torch.no_grad():\n        out_c = torch.sigmoid(model(dummy_img)['mask_logits'])\n        out_d = torch.sigmoid(model(dummy_img)['mask_logits'])\n    diff2 = (out_c - out_d).abs().mean().item()\n    print(f\"\\nPost-MC eval() restoration check:\")\n    print(f\"  Mean absolute difference: {diff2:.8f}\")\n    assert diff2 < 1e-6, \\\n        \"Model is NOT deterministic after calling eval() post-MC-sampling — dropout leaked into eval mode.\"\n    print(f\"  \\u2713 Standard eval mode correctly restored (deterministic).\")\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"\\nTotal params: {total/1e6:.2f}M\")\n    print(\"\\nSanity check passed \\u2713\")\n    print(\"=\"*60)\n    print(\"\\nStarting training...\")\n    model, results = train(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T17:24:33.737706Z","iopub.execute_input":"2026-08-25T17:24:33.738152Z","iopub.status.idle":"2026-08-25T21:55:22.089074Z","shell.execute_reply.started":"2026-08-25T17:24:33.73812Z","shell.execute_reply":"2026-08-25T21:55:22.088038Z"}},"outputs":[{"name":"stdout","text":"============================================================\nSANITY CHECK — PVTv2-b3 MC-Dropout-ready decoder\n============================================================\nStandard forward pass (dropout OFF):\n  mask_logits shape: [2, 1, 352, 352]\n\nMC Dropout sanity check (two stochastic passes on same input):\n  Mean absolute difference: 0.005391\n  ✓ Passes differ as expected — dropout is active during MC sampling.\n\nPost-MC eval() restoration check:\n  Mean absolute difference: 0.00000000\n  ✓ Standard eval mode correctly restored (deterministic).\n\nTotal params: 45.57M\n\nSanity check passed ✓\n============================================================\n\nStarting training...\nDevice: cuda\nCheckpoint: PVTv2-b3 + decoder dropout (p=0.2)\nUnlocks: Single, TTA, MC Dropout (T=4)\nLoading CSVs...\n  Split fingerprint -> train=2089 (hash=6780967082095866867) val=261 indist=262 bench=636\n  Train:2089 | Val:261 | Indist:262 | Bench:636\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"model.safetensors:   0%|          | 0.00/181M [00:00<?, ?B/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"046aa82df15a42509358096294ea996f"}},"metadata":{}},{"name":"stdout","text":"Encoder output channels: (64, 128, 320, 512)\nParams: 45.57M total\nDropout2d modules in decoder: 7 (expected 7: lat1-4 + fuse1-3)\n\n============================================================\nTRAINING — pvt_v2_b3_mcdropout\n  Decoder: FPN + Dropout2d(p=0.20) after every lateral/fuse block\n  Loss   : BCE + Dice (UNCHANGED from original ablation)\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  1/60]  LR=9.99e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.9063    0.5668\n  Dice              0.7184    0.8555\n  IoU               0.6129    0.7722\n  Precision         0.6837    0.8392\n  Recall            0.8651    0.9092\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8295  IoU=0.7397  Prec=0.7738  Rec=0.9297\n    CVC-ClinicDB    n=  61  Dice=0.8717  IoU=0.7851  Prec=0.8558  Rec=0.9178\n    Kvasir-SEG      n= 100  Dice=0.8761  IoU=0.8016  Prec=0.8983  Rec=0.8881\n  ✓ Saved best (dice=0.8555)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  2/60]  LR=9.97e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.5099    0.3228\n  Dice              0.8144    0.8808\n  IoU               0.7295    0.8064\n  Precision         0.8088    0.8763\n  Recall            0.8781    0.9108\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8733  IoU=0.8010  Prec=0.8517  Rec=0.9184\n    CVC-ClinicDB    n=  61  Dice=0.8831  IoU=0.8030  Prec=0.8803  Rec=0.9122\n    Kvasir-SEG      n= 100  Dice=0.8883  IoU=0.8156  Prec=0.9003  Rec=0.9035\n  ✓ Saved best (dice=0.8808)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  3/60]  LR=9.94e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.3365    0.2114\n  Dice              0.8500    0.9088\n  IoU               0.7728    0.8483\n  Precision         0.8612    0.8932\n  Recall            0.8817    0.9462\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9054  IoU=0.8453  Prec=0.8773  Rec=0.9599\n    CVC-ClinicDB    n=  61  Dice=0.9181  IoU=0.8573  Prec=0.9185  Rec=0.9286\n    Kvasir-SEG      n= 100  Dice=0.9086  IoU=0.8489  Prec=0.8962  Rec=0.9453\n  ✓ Saved best (dice=0.9088)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  4/60]  LR=9.89e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2795    0.1924\n  Dice              0.8552    0.9009\n  IoU               0.7818    0.8368\n  Precision         0.8705    0.8956\n  Recall            0.8804    0.9311\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8977  IoU=0.8331  Prec=0.8664  Rec=0.9546\n    CVC-ClinicDB    n=  61  Dice=0.9200  IoU=0.8592  Prec=0.9106  Rec=0.9398\n    Kvasir-SEG      n= 100  Dice=0.8963  IoU=0.8321  Prec=0.9177  Rec=0.9063\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  5/60]  LR=9.83e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2561    0.1760\n  Dice              0.8610    0.9024\n  IoU               0.7925    0.8372\n  Precision         0.8803    0.8802\n  Recall            0.8784    0.9482\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9035  IoU=0.8395  Prec=0.8636  Rec=0.9691\n    CVC-ClinicDB    n=  61  Dice=0.9122  IoU=0.8466  Prec=0.9041  Rec=0.9336\n    Kvasir-SEG      n= 100  Dice=0.8983  IoU=0.8328  Prec=0.8859  Rec=0.9378\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  6/60]  LR=9.76e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2300    0.1583\n  Dice              0.8721    0.9137\n  IoU               0.8047    0.8563\n  Precision         0.8859    0.9385\n  Recall            0.8927    0.9102\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9156  IoU=0.8610  Prec=0.9196  Rec=0.9338\n    CVC-ClinicDB    n=  61  Dice=0.9315  IoU=0.8783  Prec=0.9533  Rec=0.9195\n    Kvasir-SEG      n= 100  Dice=0.9044  IoU=0.8431  Prec=0.9502  Rec=0.8854\n  ✓ Saved best (dice=0.9137)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  7/60]  LR=9.67e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2257    0.1383\n  Dice              0.8727    0.9224\n  IoU               0.8087    0.8673\n  Precision         0.8895    0.9364\n  Recall            0.8879    0.9250\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9308  IoU=0.8795  Prec=0.9527  Rec=0.9219\n    CVC-ClinicDB    n=  61  Dice=0.9374  IoU=0.8859  Prec=0.9383  Rec=0.9426\n    Kvasir-SEG      n= 100  Dice=0.9080  IoU=0.8482  Prec=0.9232  Rec=0.9187\n  ✓ Saved best (dice=0.9224)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  8/60]  LR=9.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.2039    0.1465\n  Dice              0.8825    0.9226\n  IoU               0.8200    0.8700\n  Precision         0.8953    0.9234\n  Recall            0.8977    0.9405\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9302  IoU=0.8796  Prec=0.9251  Rec=0.9490\n    CVC-ClinicDB    n=  61  Dice=0.9359  IoU=0.8847  Prec=0.9283  Rec=0.9509\n    Kvasir-SEG      n= 100  Dice=0.9095  IoU=0.8550  Prec=0.9216  Rec=0.9275\n  ✓ Saved best (dice=0.9226)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  9/60]  LR=9.46e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1897    0.1264\n  Dice              0.8904    0.9271\n  IoU               0.8294    0.8757\n  Precision         0.9028    0.9396\n  Recall            0.9045    0.9297\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9322  IoU=0.8842  Prec=0.9331  Rec=0.9439\n    CVC-ClinicDB    n=  61  Dice=0.9368  IoU=0.8852  Prec=0.9445  Rec=0.9360\n    Kvasir-SEG      n= 100  Dice=0.9179  IoU=0.8639  Prec=0.9448  Rec=0.9134\n  ✓ Saved best (dice=0.9271)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 10/60]  LR=9.34e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1815    0.1309\n  Dice              0.8925    0.9271\n  IoU               0.8318    0.8766\n  Precision         0.9038    0.9384\n  Recall            0.9044    0.9330\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9444  IoU=0.8992  Prec=0.9601  Rec=0.9346\n    CVC-ClinicDB    n=  61  Dice=0.9394  IoU=0.8900  Prec=0.9495  Rec=0.9362\n    Kvasir-SEG      n= 100  Dice=0.9051  IoU=0.8499  Prec=0.9135  Rec=0.9304\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 11/60]  LR=9.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1797    0.1422\n  Dice              0.8966    0.9206\n  IoU               0.8375    0.8644\n  Precision         0.9107    0.9420\n  Recall            0.9058    0.9153\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9312  IoU=0.8786  Prec=0.9319  Rec=0.9410\n    CVC-ClinicDB    n=  61  Dice=0.9248  IoU=0.8685  Prec=0.9420  Rec=0.9180\n    Kvasir-SEG      n= 100  Dice=0.9091  IoU=0.8501  Prec=0.9541  Rec=0.8895\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 12/60]  LR=9.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1828    0.1372\n  Dice              0.8945    0.9256\n  IoU               0.8365    0.8745\n  Precision         0.9091    0.9360\n  Recall            0.9065    0.9310\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9368  IoU=0.8919  Prec=0.9405  Rec=0.9478\n    CVC-ClinicDB    n=  61  Dice=0.9286  IoU=0.8774  Prec=0.9426  Rec=0.9266\n    Kvasir-SEG      n= 100  Dice=0.9158  IoU=0.8593  Prec=0.9312  Rec=0.9194\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 13/60]  LR=8.90e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1612    0.1364\n  Dice              0.9042    0.9244\n  IoU               0.8482    0.8715\n  Precision         0.9163    0.9443\n  Recall            0.9110    0.9208\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9427  IoU=0.8960  Prec=0.9590  Rec=0.9331\n    CVC-ClinicDB    n=  61  Dice=0.9389  IoU=0.8889  Prec=0.9479  Rec=0.9372\n    Kvasir-SEG      n= 100  Dice=0.9016  IoU=0.8418  Prec=0.9329  Rec=0.9004\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 14/60]  LR=8.73e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1605    0.1440\n  Dice              0.9077    0.9230\n  IoU               0.8528    0.8716\n  Precision         0.9204    0.9202\n  Recall            0.9133    0.9453\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9462  IoU=0.9040  Prec=0.9363  Rec=0.9645\n    CVC-ClinicDB    n=  61  Dice=0.9319  IoU=0.8820  Prec=0.9211  Rec=0.9545\n    Kvasir-SEG      n= 100  Dice=0.9006  IoU=0.8415  Prec=0.9103  Rec=0.9240\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 15/60]  LR=8.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1637    0.1178\n  Dice              0.9031    0.9350\n  IoU               0.8494    0.8876\n  Precision         0.9172    0.9344\n  Recall            0.9112    0.9475\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9489  IoU=0.9068  Prec=0.9389  Rec=0.9639\n    CVC-ClinicDB    n=  61  Dice=0.9487  IoU=0.9034  Prec=0.9314  Rec=0.9687\n    Kvasir-SEG      n= 100  Dice=0.9157  IoU=0.8627  Prec=0.9332  Rec=0.9225\n  ✓ Saved best (dice=0.9350)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 16/60]  LR=8.36e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1511    0.1288\n  Dice              0.9115    0.9324\n  IoU               0.8570    0.8832\n  Precision         0.9228    0.9308\n  Recall            0.9163    0.9456\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9480  IoU=0.9050  Prec=0.9311  Rec=0.9698\n    CVC-ClinicDB    n=  61  Dice=0.9471  IoU=0.9009  Prec=0.9339  Rec=0.9628\n    Kvasir-SEG      n= 100  Dice=0.9112  IoU=0.8551  Prec=0.9314  Rec=0.9150\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 17/60]  LR=8.17e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1456    0.1202\n  Dice              0.9139    0.9330\n  IoU               0.8608    0.8845\n  Precision         0.9241    0.9314\n  Recall            0.9196    0.9466\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9482  IoU=0.9067  Prec=0.9457  Rec=0.9562\n    CVC-ClinicDB    n=  61  Dice=0.9434  IoU=0.8962  Prec=0.9297  Rec=0.9619\n    Kvasir-SEG      n= 100  Dice=0.9150  IoU=0.8598  Prec=0.9216  Rec=0.9306\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 18/60]  LR=7.96e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1507    0.1153\n  Dice              0.9106    0.9333\n  IoU               0.8577    0.8883\n  Precision         0.9209    0.9403\n  Recall            0.9191    0.9403\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9371  IoU=0.8958  Prec=0.9293  Rec=0.9612\n    CVC-ClinicDB    n=  61  Dice=0.9518  IoU=0.9090  Prec=0.9485  Rec=0.9568\n    Kvasir-SEG      n= 100  Dice=0.9231  IoU=0.8740  Prec=0.9515  Rec=0.9131\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 19/60]  LR=7.75e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1407    0.1264\n  Dice              0.9144    0.9332\n  IoU               0.8623    0.8856\n  Precision         0.9235    0.9387\n  Recall            0.9208    0.9416\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9484  IoU=0.9093  Prec=0.9458  Rec=0.9589\n    CVC-ClinicDB    n=  61  Dice=0.9456  IoU=0.8993  Prec=0.9388  Rec=0.9570\n    Kvasir-SEG      n= 100  Dice=0.9128  IoU=0.8571  Prec=0.9339  Rec=0.9174\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 20/60]  LR=7.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1298    0.1058\n  Dice              0.9196    0.9387\n  IoU               0.8689    0.8938\n  Precision         0.9270    0.9517\n  Recall            0.9252    0.9375\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9486  IoU=0.9088  Prec=0.9611  Rec=0.9435\n    CVC-ClinicDB    n=  61  Dice=0.9473  IoU=0.9026  Prec=0.9497  Rec=0.9492\n    Kvasir-SEG      n= 100  Dice=0.9263  IoU=0.8774  Prec=0.9442  Rec=0.9284\n  ✓ Saved best (dice=0.9387)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 21/60]  LR=7.30e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1345    0.1063\n  Dice              0.9189    0.9387\n  IoU               0.8689    0.8953\n  Precision         0.9280    0.9475\n  Recall            0.9251    0.9435\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9451  IoU=0.9077  Prec=0.9462  Rec=0.9585\n    CVC-ClinicDB    n=  61  Dice=0.9530  IoU=0.9113  Prec=0.9492  Rec=0.9586\n    Kvasir-SEG      n= 100  Dice=0.9260  IoU=0.8766  Prec=0.9483  Rec=0.9230\n  ✓ Saved best (dice=0.9387)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 22/60]  LR=7.06e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1310    0.1090\n  Dice              0.9206    0.9388\n  IoU               0.8709    0.8935\n  Precision         0.9316    0.9503\n  Recall            0.9254    0.9372\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9494  IoU=0.9090  Prec=0.9544  Rec=0.9494\n    CVC-ClinicDB    n=  61  Dice=0.9521  IoU=0.9099  Prec=0.9558  Rec=0.9506\n    Kvasir-SEG      n= 100  Dice=0.9229  IoU=0.8721  Prec=0.9447  Rec=0.9206\n  ✓ Saved best (dice=0.9388)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 23/60]  LR=6.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1341    0.1164\n  Dice              0.9180    0.9330\n  IoU               0.8683    0.8871\n  Precision         0.9272    0.9441\n  Recall            0.9226    0.9357\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9489  IoU=0.9082  Prec=0.9574  Rec=0.9469\n    CVC-ClinicDB    n=  61  Dice=0.9471  IoU=0.9018  Prec=0.9491  Rec=0.9486\n    Kvasir-SEG      n= 100  Dice=0.9151  IoU=0.8644  Prec=0.9343  Rec=0.9207\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 24/60]  LR=6.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1304    0.1161\n  Dice              0.9217    0.9385\n  IoU               0.8722    0.8935\n  Precision         0.9309    0.9513\n  Recall            0.9264    0.9383\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9528  IoU=0.9140  Prec=0.9692  Rec=0.9417\n    CVC-ClinicDB    n=  61  Dice=0.9496  IoU=0.9060  Prec=0.9398  Rec=0.9626\n    Kvasir-SEG      n= 100  Dice=0.9215  IoU=0.8707  Prec=0.9439  Rec=0.9225\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 25/60]  LR=6.33e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1231    0.1155\n  Dice              0.9258    0.9358\n  IoU               0.8770    0.8923\n  Precision         0.9342    0.9331\n  Recall            0.9310    0.9552\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9455  IoU=0.9091  Prec=0.9418  Rec=0.9645\n    CVC-ClinicDB    n=  61  Dice=0.9524  IoU=0.9105  Prec=0.9366  Rec=0.9710\n    Kvasir-SEG      n= 100  Dice=0.9205  IoU=0.8700  Prec=0.9264  Rec=0.9386\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 26/60]  LR=6.08e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1127    0.1253\n  Dice              0.9308    0.9316\n  IoU               0.8836    0.8865\n  Precision         0.9374    0.9433\n  Recall            0.9350    0.9379\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9408  IoU=0.9006  Prec=0.9504  Rec=0.9470\n    CVC-ClinicDB    n=  61  Dice=0.9550  IoU=0.9150  Prec=0.9529  Rec=0.9588\n    Kvasir-SEG      n= 100  Dice=0.9140  IoU=0.8618  Prec=0.9356  Rec=0.9194\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 27/60]  LR=5.82e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1104    0.1145\n  Dice              0.9330    0.9400\n  IoU               0.8871    0.8966\n  Precision         0.9393    0.9520\n  Recall            0.9366    0.9404\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9492  IoU=0.9107  Prec=0.9528  Rec=0.9548\n    CVC-ClinicDB    n=  61  Dice=0.9567  IoU=0.9178  Prec=0.9547  Rec=0.9601\n    Kvasir-SEG      n= 100  Dice=0.9233  IoU=0.8735  Prec=0.9525  Rec=0.9159\n  ✓ Saved best (dice=0.9400)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 28/60]  LR=5.57e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1179    0.1204\n  Dice              0.9294    0.9327\n  IoU               0.8836    0.8864\n  Precision         0.9366    0.9357\n  Recall            0.9334    0.9462\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9432  IoU=0.9029  Prec=0.9480  Rec=0.9511\n    CVC-ClinicDB    n=  61  Dice=0.9515  IoU=0.9092  Prec=0.9480  Rec=0.9575\n    Kvasir-SEG      n= 100  Dice=0.9150  IoU=0.8615  Prec=0.9195  Rec=0.9371\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 29/60]  LR=5.31e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1044    0.1198\n  Dice              0.9346    0.9368\n  IoU               0.8907    0.8919\n  Precision         0.9392    0.9432\n  Recall            0.9378    0.9436\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9414  IoU=0.8997  Prec=0.9380  Rec=0.9574\n    CVC-ClinicDB    n=  61  Dice=0.9541  IoU=0.9142  Prec=0.9505  Rec=0.9601\n    Kvasir-SEG      n= 100  Dice=0.9245  IoU=0.8745  Prec=0.9459  Rec=0.9229\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 30/60]  LR=5.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1071    0.1116\n  Dice              0.9335    0.9414\n  IoU               0.8891    0.8984\n  Precision         0.9394    0.9454\n  Recall            0.9371    0.9480\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9494  IoU=0.9120  Prec=0.9464  Rec=0.9624\n    CVC-ClinicDB    n=  61  Dice=0.9557  IoU=0.9164  Prec=0.9476  Rec=0.9654\n    Kvasir-SEG      n= 100  Dice=0.9274  IoU=0.8777  Prec=0.9446  Rec=0.9267\n  ✓ Saved best (dice=0.9414)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 31/60]  LR=4.79e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1083    0.1119\n  Dice              0.9326    0.9403\n  IoU               0.8888    0.8969\n  Precision         0.9383    0.9424\n  Recall            0.9366    0.9495\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9522  IoU=0.9156  Prec=0.9460  Rec=0.9666\n    CVC-ClinicDB    n=  61  Dice=0.9555  IoU=0.9159  Prec=0.9501  Rec=0.9626\n    Kvasir-SEG      n= 100  Dice=0.9224  IoU=0.8714  Prec=0.9377  Rec=0.9269\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 32/60]  LR=4.53e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1010    0.1139\n  Dice              0.9359    0.9399\n  IoU               0.8928    0.8964\n  Precision         0.9414    0.9427\n  Recall            0.9379    0.9491\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9547  IoU=0.9182  Prec=0.9543  Rec=0.9603\n    CVC-ClinicDB    n=  61  Dice=0.9552  IoU=0.9156  Prec=0.9500  Rec=0.9620\n    Kvasir-SEG      n= 100  Dice=0.9196  IoU=0.8680  Prec=0.9304  Rec=0.9323\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 33/60]  LR=4.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0990    0.1085\n  Dice              0.9375    0.9435\n  IoU               0.8945    0.9016\n  Precision         0.9429    0.9490\n  Recall            0.9404    0.9483\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9574  IoU=0.9223  Prec=0.9584  Rec=0.9613\n    CVC-ClinicDB    n=  61  Dice=0.9552  IoU=0.9160  Prec=0.9562  Rec=0.9563\n    Kvasir-SEG      n= 100  Dice=0.9253  IoU=0.8760  Prec=0.9385  Rec=0.9322\n  ✓ Saved best (dice=0.9435)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 34/60]  LR=4.02e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1024    0.1091\n  Dice              0.9362    0.9401\n  IoU               0.8934    0.8965\n  Precision         0.9419    0.9465\n  Recall            0.9393    0.9450\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9527  IoU=0.9153  Prec=0.9564  Rec=0.9562\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9149  Prec=0.9590  Rec=0.9526\n    Kvasir-SEG      n= 100  Dice=0.9217  IoU=0.8707  Prec=0.9305  Rec=0.9334\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 35/60]  LR=3.77e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0994    0.1130\n  Dice              0.9379    0.9403\n  IoU               0.8953    0.8982\n  Precision         0.9434    0.9476\n  Recall            0.9410    0.9449\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9591  IoU=0.9242  Prec=0.9580  Rec=0.9632\n    CVC-ClinicDB    n=  61  Dice=0.9503  IoU=0.9087  Prec=0.9421  Rec=0.9617\n    Kvasir-SEG      n= 100  Dice=0.9184  IoU=0.8702  Prec=0.9431  Rec=0.9201\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 36/60]  LR=3.52e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.1021    0.1109\n  Dice              0.9371    0.9412\n  IoU               0.8942    0.8988\n  Precision         0.9422    0.9541\n  Recall            0.9422    0.9403\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9534  IoU=0.9170  Prec=0.9562  Rec=0.9583\n    CVC-ClinicDB    n=  61  Dice=0.9567  IoU=0.9177  Prec=0.9633  Rec=0.9513\n    Kvasir-SEG      n= 100  Dice=0.9240  IoU=0.8748  Prec=0.9509  Rec=0.9179\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 37/60]  LR=3.28e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0953    0.1140\n  Dice              0.9401    0.9408\n  IoU               0.8981    0.8980\n  Precision         0.9444    0.9358\n  Recall            0.9429    0.9580\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9521  IoU=0.9148  Prec=0.9393  Rec=0.9728\n    CVC-ClinicDB    n=  61  Dice=0.9549  IoU=0.9147  Prec=0.9412  Rec=0.9708\n    Kvasir-SEG      n= 100  Dice=0.9255  IoU=0.8770  Prec=0.9337  Rec=0.9380\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 38/60]  LR=3.04e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0964    0.1114\n  Dice              0.9395    0.9415\n  IoU               0.8971    0.8998\n  Precision         0.9430    0.9456\n  Recall            0.9433    0.9504\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9516  IoU=0.9150  Prec=0.9451  Rec=0.9675\n    CVC-ClinicDB    n=  61  Dice=0.9563  IoU=0.9174  Prec=0.9467  Rec=0.9680\n    Kvasir-SEG      n= 100  Dice=0.9268  IoU=0.8796  Prec=0.9496  Rec=0.9252\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 39/60]  LR=2.80e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0945    0.1071\n  Dice              0.9390    0.9415\n  IoU               0.8984    0.8984\n  Precision         0.9435    0.9452\n  Recall            0.9421    0.9487\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9507  IoU=0.9132  Prec=0.9487  Rec=0.9617\n    CVC-ClinicDB    n=  61  Dice=0.9537  IoU=0.9141  Prec=0.9486  Rec=0.9617\n    Kvasir-SEG      n= 100  Dice=0.9289  IoU=0.8796  Prec=0.9435  Rec=0.9308\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 40/60]  LR=2.58e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0899    0.1057\n  Dice              0.9435    0.9426\n  IoU               0.9031    0.9005\n  Precision         0.9467    0.9448\n  Recall            0.9465    0.9510\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9525  IoU=0.9164  Prec=0.9514  Rec=0.9629\n    CVC-ClinicDB    n=  61  Dice=0.9535  IoU=0.9138  Prec=0.9474  Rec=0.9628\n    Kvasir-SEG      n= 100  Dice=0.9295  IoU=0.8811  Prec=0.9397  Rec=0.9353\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 41/60]  LR=2.35e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0900    0.1051\n  Dice              0.9418    0.9436\n  IoU               0.9008    0.9025\n  Precision         0.9443    0.9461\n  Recall            0.9455    0.9519\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9527  IoU=0.9172  Prec=0.9490  Rec=0.9661\n    CVC-ClinicDB    n=  61  Dice=0.9571  IoU=0.9186  Prec=0.9499  Rec=0.9659\n    Kvasir-SEG      n= 100  Dice=0.9292  IoU=0.8821  Prec=0.9425  Rec=0.9335\n  ✓ Saved best (dice=0.9436)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 42/60]  LR=2.14e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0924    0.1089\n  Dice              0.9409    0.9448\n  IoU               0.9013    0.9034\n  Precision         0.9448    0.9585\n  Recall            0.9433    0.9406\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9570  IoU=0.9221  Prec=0.9627  Rec=0.9573\n    CVC-ClinicDB    n=  61  Dice=0.9563  IoU=0.9174  Prec=0.9591  Rec=0.9546\n    Kvasir-SEG      n= 100  Dice=0.9288  IoU=0.8806  Prec=0.9551  Rec=0.9202\n  ✓ Saved best (dice=0.9448)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 43/60]  LR=1.93e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0928    0.1131\n  Dice              0.9398    0.9424\n  IoU               0.9012    0.8995\n  Precision         0.9435    0.9460\n  Recall            0.9424    0.9486\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9545  IoU=0.9175  Prec=0.9567  Rec=0.9581\n    CVC-ClinicDB    n=  61  Dice=0.9557  IoU=0.9161  Prec=0.9507  Rec=0.9621\n    Kvasir-SEG      n= 100  Dice=0.9252  IoU=0.8755  Prec=0.9335  Rec=0.9353\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 44/60]  LR=1.74e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0901    0.1043\n  Dice              0.9431    0.9458\n  IoU               0.9034    0.9050\n  Precision         0.9486    0.9498\n  Recall            0.9447    0.9505\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9590  IoU=0.9247  Prec=0.9577  Rec=0.9649\n    CVC-ClinicDB    n=  61  Dice=0.9562  IoU=0.9170  Prec=0.9526  Rec=0.9615\n    Kvasir-SEG      n= 100  Dice=0.9295  IoU=0.8825  Prec=0.9422  Rec=0.9339\n  ✓ Saved best (dice=0.9458)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 45/60]  LR=1.55e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0861    0.1080\n  Dice              0.9457    0.9456\n  IoU               0.9065    0.9043\n  Precision         0.9485    0.9537\n  Recall            0.9484    0.9459\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9581  IoU=0.9225  Prec=0.9593  Rec=0.9605\n    CVC-ClinicDB    n=  61  Dice=0.9571  IoU=0.9185  Prec=0.9558  Rec=0.9598\n    Kvasir-SEG      n= 100  Dice=0.9292  IoU=0.8816  Prec=0.9486  Rec=0.9269\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 46/60]  LR=1.37e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0877    0.1097\n  Dice              0.9441    0.9450\n  IoU               0.9051    0.9037\n  Precision         0.9483    0.9518\n  Recall            0.9472    0.9476\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9584  IoU=0.9235  Prec=0.9542  Rec=0.9671\n    CVC-ClinicDB    n=  61  Dice=0.9583  IoU=0.9205  Prec=0.9577  Rec=0.9600\n    Kvasir-SEG      n= 100  Dice=0.9268  IoU=0.8784  Prec=0.9469  Rec=0.9256\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 47/60]  LR=1.20e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0918    0.1093\n  Dice              0.9416    0.9448\n  IoU               0.9026    0.9032\n  Precision         0.9464    0.9502\n  Recall            0.9441    0.9484\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9572  IoU=0.9218  Prec=0.9526  Rec=0.9669\n    CVC-ClinicDB    n=  61  Dice=0.9578  IoU=0.9196  Prec=0.9557  Rec=0.9612\n    Kvasir-SEG      n= 100  Dice=0.9277  IoU=0.8789  Prec=0.9460  Rec=0.9267\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 48/60]  LR=1.05e-05\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0809    0.1106\n  Dice              0.9485    0.9451\n  IoU               0.9101    0.9037\n  Precision         0.9516    0.9557\n  Recall            0.9503    0.9435\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9583  IoU=0.9235  Prec=0.9609  Rec=0.9603\n    CVC-ClinicDB    n=  61  Dice=0.9580  IoU=0.9201  Prec=0.9605  Rec=0.9569\n    Kvasir-SEG      n= 100  Dice=0.9273  IoU=0.8785  Prec=0.9491  Rec=0.9233\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 49/60]  LR=8.99e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0839    0.1102\n  Dice              0.9458    0.9446\n  IoU               0.9077    0.9033\n  Precision         0.9489    0.9476\n  Recall            0.9474    0.9512\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9575  IoU=0.9228  Prec=0.9516  Rec=0.9689\n    CVC-ClinicDB    n=  61  Dice=0.9585  IoU=0.9210  Prec=0.9542  Rec=0.9641\n    Kvasir-SEG      n= 100  Dice=0.9263  IoU=0.8771  Prec=0.9409  Rec=0.9302\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 50/60]  LR=7.63e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0838    0.1066\n  Dice              0.9446    0.9466\n  IoU               0.9066    0.9063\n  Precision         0.9486    0.9523\n  Recall            0.9467    0.9499\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9611  IoU=0.9282  Prec=0.9580  Rec=0.9681\n    CVC-ClinicDB    n=  61  Dice=0.9591  IoU=0.9221  Prec=0.9585  Rec=0.9610\n    Kvasir-SEG      n= 100  Dice=0.9276  IoU=0.8791  Prec=0.9439  Rec=0.9295\n  ✓ Saved best (dice=0.9466)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 51/60]  LR=6.40e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0806    0.1060\n  Dice              0.9476    0.9461\n  IoU               0.9104    0.9055\n  Precision         0.9504    0.9529\n  Recall            0.9487    0.9483\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9605  IoU=0.9275  Prec=0.9574  Rec=0.9680\n    CVC-ClinicDB    n=  61  Dice=0.9587  IoU=0.9213  Prec=0.9586  Rec=0.9601\n    Kvasir-SEG      n= 100  Dice=0.9274  IoU=0.8785  Prec=0.9462  Rec=0.9264\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 52/60]  LR=5.28e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0799    0.1074\n  Dice              0.9486    0.9469\n  IoU               0.9114    0.9067\n  Precision         0.9514    0.9552\n  Recall            0.9501    0.9473\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9612  IoU=0.9282  Prec=0.9611  Rec=0.9649\n    CVC-ClinicDB    n=  61  Dice=0.9581  IoU=0.9202  Prec=0.9570  Rec=0.9606\n    Kvasir-SEG      n= 100  Dice=0.9291  IoU=0.8815  Prec=0.9495  Rec=0.9263\n  ✓ Saved best (dice=0.9469)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 53/60]  LR=4.29e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0815    0.1088\n  Dice              0.9473    0.9465\n  IoU               0.9102    0.9063\n  Precision         0.9506    0.9552\n  Recall            0.9492    0.9470\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9595  IoU=0.9261  Prec=0.9592  Rec=0.9648\n    CVC-ClinicDB    n=  61  Dice=0.9580  IoU=0.9202  Prec=0.9571  Rec=0.9605\n    Kvasir-SEG      n= 100  Dice=0.9293  IoU=0.8821  Prec=0.9513  Rec=0.9253\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 54/60]  LR=3.42e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0771    0.1087\n  Dice              0.9500    0.9462\n  IoU               0.9124    0.9059\n  Precision         0.9534    0.9529\n  Recall            0.9507    0.9489\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9588  IoU=0.9252  Prec=0.9563  Rec=0.9669\n    CVC-ClinicDB    n=  61  Dice=0.9574  IoU=0.9191  Prec=0.9544  Rec=0.9620\n    Kvasir-SEG      n= 100  Dice=0.9298  IoU=0.8828  Prec=0.9499  Rec=0.9274\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 55/60]  LR=2.69e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0778    0.1084\n  Dice              0.9500    0.9462\n  IoU               0.9129    0.9058\n  Precision         0.9527    0.9556\n  Recall            0.9519    0.9461\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9590  IoU=0.9252  Prec=0.9591  Rec=0.9639\n    CVC-ClinicDB    n=  61  Dice=0.9578  IoU=0.9199  Prec=0.9574  Rec=0.9599\n    Kvasir-SEG      n= 100  Dice=0.9295  IoU=0.8824  Prec=0.9523  Rec=0.9246\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 56/60]  LR=2.08e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0781    0.1089\n  Dice              0.9490    0.9458\n  IoU               0.9120    0.9052\n  Precision         0.9517    0.9542\n  Recall            0.9500    0.9467\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9594  IoU=0.9258  Prec=0.9588  Rec=0.9648\n    CVC-ClinicDB    n=  61  Dice=0.9578  IoU=0.9200  Prec=0.9575  Rec=0.9598\n    Kvasir-SEG      n= 100  Dice=0.9282  IoU=0.8800  Prec=0.9489  Rec=0.9255\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 57/60]  LR=1.61e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0791    0.1080\n  Dice              0.9487    0.9461\n  IoU               0.9109    0.9056\n  Precision         0.9512    0.9529\n  Recall            0.9508    0.9485\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9594  IoU=0.9259  Prec=0.9564  Rec=0.9674\n    CVC-ClinicDB    n=  61  Dice=0.9576  IoU=0.9195  Prec=0.9552  Rec=0.9616\n    Kvasir-SEG      n= 100  Dice=0.9291  IoU=0.8816  Prec=0.9494  Rec=0.9265\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 58/60]  LR=1.27e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0822    0.1085\n  Dice              0.9479    0.9460\n  IoU               0.9107    0.9054\n  Precision         0.9508    0.9522\n  Recall            0.9499    0.9489\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9596  IoU=0.9261  Prec=0.9567  Rec=0.9672\n    CVC-ClinicDB    n=  61  Dice=0.9574  IoU=0.9192  Prec=0.9544  Rec=0.9622\n    Kvasir-SEG      n= 100  Dice=0.9287  IoU=0.8809  Prec=0.9478  Rec=0.9274\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 59/60]  LR=1.07e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0763    0.1089\n  Dice              0.9502    0.9455\n  IoU               0.9131    0.9046\n  Precision         0.9530    0.9518\n  Recall            0.9519    0.9485\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9584  IoU=0.9244  Prec=0.9558  Rec=0.9664\n    CVC-ClinicDB    n=  61  Dice=0.9575  IoU=0.9193  Prec=0.9547  Rec=0.9619\n    Kvasir-SEG      n= 100  Dice=0.9284  IoU=0.8803  Prec=0.9473  Rec=0.9272\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 60/60]  LR=1.00e-06\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Loss              0.0772    0.1080\n  Dice              0.9499    0.9454\n  IoU               0.9133    0.9046\n  Precision         0.9537    0.9533\n  Recall            0.9515    0.9471\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9584  IoU=0.9245  Prec=0.9574  Rec=0.9649\n    CVC-ClinicDB    n=  61  Dice=0.9578  IoU=0.9198  Prec=0.9567  Rec=0.9605\n    Kvasir-SEG      n= 100  Dice=0.9281  IoU=0.8799  Prec=0.9484  Rec=0.9259\n  Loaded best epoch 51 (dice=0.9469)\n\n============================================================\nFINAL EVALUATION (deterministic, dropout OFF) — pvt_v2_b3_mcdropout\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                      \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  VALIDATION SET\n============================================================\n  Dice     : 0.9469\n  IoU      : 0.9067\n  Precision: 0.9552\n  Recall   : 0.9473\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9612  IoU=0.9282  Prec=0.9611  Rec=0.9649\n  [CVC-ClinicDB] n=61  Dice=0.9581  IoU=0.9202  Prec=0.9570  Rec=0.9606\n  [Kvasir-SEG] n=100  Dice=0.9291  IoU=0.8815  Prec=0.9495  Rec=0.9263\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  IN-DISTRIBUTION TEST\n============================================================\n  Dice     : 0.9347\n  IoU      : 0.8897\n  Precision: 0.9465\n  Recall   : 0.9389\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9363  IoU=0.8901  Prec=0.9372  Rec=0.9478\n  [CVC-ClinicDB] n=62  Dice=0.9357  IoU=0.8939  Prec=0.9590  Rec=0.9318\n  [Kvasir-SEG] n=100  Dice=0.9326  IoU=0.8869  Prec=0.9488  Rec=0.9340\n","output_type":"stream"},{"name":"stderr","text":"                                                                          ","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  BENCHMARK (UNSEEN)\n============================================================\n  Dice     : 0.8301\n  IoU      : 0.7542\n  Precision: 0.8514\n  Recall   : 0.8555\n\n  PER-DATASET BREAKDOWN\n  [CVC-ColonDB] n=380  Dice=0.8029  IoU=0.7231  Prec=0.8491  Rec=0.8157\n  [ETIS-Larib] n=196  Dice=0.8555  IoU=0.7832  Prec=0.8441  Rec=0.9003\n  [CVC-300] n=60  Dice=0.9191  IoU=0.8587  Prec=0.8971  Rec=0.9543\n\n============================================================\nREGRESSION GATE CHECK\n  Original PVTv2-b3 benchmark Dice : 0.8444\n  This checkpoint benchmark Dice   : 0.8301\n  Delta                            : -0.0143\n  ✓ Within acceptable range — safe to proceed to MC Dropout / TTA / Single uncertainty extraction.\n============================================================\n\n  Results appended to: checkpoints_uncertainty/uncertainty_checkpoint_results.csv\n","output_type":"stream"},{"name":"stderr","text":"\r","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"print(\"kuet\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T22:17:20.661393Z","iopub.execute_input":"2026-08-25T22:17:20.662138Z","iopub.status.idle":"2026-08-25T22:17:20.666954Z","shell.execute_reply.started":"2026-08-25T22:17:20.662096Z","shell.execute_reply":"2026-08-25T22:17:20.666169Z"}},"outputs":[{"name":"stdout","text":"kuet\n","output_type":"stream"}],"execution_count":3},{"cell_type":"code","source":"!zip -rq pvt_dropout.zip /kaggle/working/checkpoints_uncertainty/pvt_v2_b3_mcdropout/best_model.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T22:18:53.65205Z","iopub.execute_input":"2026-08-25T22:18:53.652575Z","iopub.status.idle":"2026-08-25T22:19:24.934843Z","shell.execute_reply.started":"2026-08-25T22:18:53.652494Z","shell.execute_reply":"2026-08-25T22:19:24.933348Z"}},"outputs":[],"execution_count":4},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink(\"pvt_dropout.zip\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-25T22:20:24.446491Z","iopub.execute_input":"2026-08-25T22:20:24.447078Z","iopub.status.idle":"2026-08-25T22:20:24.454404Z","shell.execute_reply.started":"2026-08-25T22:20:24.447038Z","shell.execute_reply":"2026-08-25T22:20:24.453645Z"}},"outputs":[{"execution_count":5,"output_type":"execute_result","data":{"text/plain":"/kaggle/working/pvt_dropout.zip","text/html":"<a href='pvt_dropout.zip' target='_blank'>pvt_dropout.zip</a><br>"},"metadata":{}}],"execution_count":5},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nPolypSeg — PVTv2-b3 Hybrid: MC Dropout + Evidential (Combined Model)\n========================================================================\nLiterature grounding for this combination (NOT an untested invention):\n\n  - DET-MCD (2026): \"deep evidential Transformer with Monte Carlo\n    Dropout... enabling joint estimation of aleatoric and epistemic\n    uncertainties in a unified model.\"\n  - EMC / \"Evidential MC dropout\" (bioactivity modeling): \"combines\n    evidential learning with stochastic dropout sampling to capture\n    variance in model outputs\" -- Bayesian (MC dropout) and evidential\n    paradigms treated as COMPLEMENTARY, excelling in \"complementary\n    settings,\" not mutually exclusive.\n  - Lymphoma PET-CT evidential segmentation (arxiv 2201.13078): applies\n    MC dropout directly ON TOP OF an already-evidential segmentation\n    model, dropout rate 0.5, T=20 passes, found MCD \"enhances the\n    segmentation performance... and improves the calibration\" for\n    most evidential/baseline methods tested. This is the closest\n    direct precedent to what this script does.\n\nWHAT THIS MODEL IS:\n  Same PVTv2-b3 encoder. Decoder = DropoutFPNDecoder (same dropout\n  placement/rate validated as stable in Checkpoint A: p=0.2, after\n  every lateral/fuse ConvBNReLU block, NOT in the final seg_head conv).\n  Head = evidential (Conv2d -> softplus -> evidence, 2 channels).\n  Loss = Dirichlet expected loss + RETUNED annealed KL (same fix as\n  train_pvt_evidential_v2.py: kl_target_weight=0.05, kl_anneal_epochs=20),\n  applied here from the start since dropout could plausibly interact\n  with KL dynamics too -- gate-checking the FINAL architecture directly\n  rather than fixing evidential alone and combining second.\n\nTWO SEPARATE, DISTINGUISHABLE UNCERTAINTY SIGNALS EXTRACTED FROM ONE\nTRAINED MODEL (this is the important design point -- NOT one collapsed\nnumber):\n\n  1. EVIDENTIAL signal (aleatoric-leaning):\n     U_evidential = C / alpha_0, computed from a SINGLE forward pass\n     with dropout OFF (standard eval mode). This is the same quantity\n     as the standalone evidential checkpoint would give you.\n\n  2. HYBRID EPISTEMIC signal (MC-variance-of-evidential-output):\n     Run T stochastic passes with decoder dropout ACTIVE (matching\n     Checkpoint A's enable_mc_dropout() mechanism). For each pass,\n     compute the expected probability p_hat = alpha/alpha_0. Variance\n     of p_hat ACROSS the T passes is the epistemic component -- this\n     matches the DET-MCD / Lymphoma-paper pattern of layering MC\n     sampling on top of an evidential model's output, distinct from\n     (not merged with) the evidential uncertainty itself.\n\nBoth signals are stored as SEPARATE columns downstream (see Step 2 of\nthe project plan) -- never averaged/combined into one number. This is\nwhat preserves the ability to attribute detection/correction results\nto one mechanism vs. the other, which the earlier, more naive version\nof \"just combine them\" would have lost.\n\nRun this in the same Kaggle environment/session where train_split.csv,\nval_split.csv, indist_test.csv, benchmark_test.csv already exist.\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCFG = {\n    # Paths\n    \"train_csv\"         : \"train_split.csv\",\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n    \"save_dir\"          : \"checkpoints_uncertainty\",\n\n    # Fixed encoder for this checkpoint\n    \"encoder_name\"       : \"pvt_v2_b3\",\n\n    # Image — identical to original ablation\n    \"img_size\"          : 352,\n    \"mean\"              : [0.485, 0.456, 0.406],\n    \"std\"               : [0.229, 0.224, 0.225],\n\n    # Training — identical to original ablation\n    \"batch_size\"        : 8,\n    \"num_workers\"       : 2,\n    \"seed\"              : 42,\n    \"num_epochs\"        : 60,\n\n    # Optimizer — identical to original ablation\n    \"encoder_lr\"        : 1e-4,\n    \"decoder_lr\"        : 3e-4,\n    \"weight_decay\"      : 1e-4,\n    \"grad_clip\"         : 1.0,\n    \"min_lr\"            : 1e-6,\n\n    # Early stopping — identical to original ablation\n    \"patience\"          : 12,\n\n    # Decoder — identical width to original ablation\n    \"dec_channels\"      : 64,\n\n    # ---- Dropout (validated stable via Checkpoint A) ----\n    \"decoder_dropout_p\" : 0.2,\n    \"mc_passes_T\"        : 4,   # matches Checkpoint A's T for consistency\n\n    # ---- Evidential hyperparameters (RETUNED from the start) ----\n    \"num_classes\"        : 2,\n    \"kl_target_weight\"   : 0.05,\n    \"kl_anneal_epochs\"   : 20,\n    # -------------------------------------------------------------\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\n\n# ============================================================\n# REPRODUCIBILITY\n# ============================================================\n\ndef set_seed(seed):\n    import random\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark     = False\n\n\n# ============================================================\n# DATA (identical to prior scripts)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_train_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.ShiftScaleRotate(\n            shift_limit=0.05, scale_limit=0.1,\n            rotate_limit=30,\n            border_mode=cv2.BORDER_CONSTANT, value=0, p=0.5),\n        A.RandomBrightnessContrast(\n            brightness_limit=0.2, contrast_limit=0.2, p=0.4),\n        A.HueSaturationValue(\n            hue_shift_limit=10, sat_shift_limit=20,\n            val_shift_limit=10, p=0.3),\n        A.GaussNoise(var_limit=(10., 50.), p=0.2),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(\n                image.transpose(2, 0, 1)).float() / 255.\n            mask_t = torch.from_numpy(mask).float().unsqueeze(0) / 255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_dataloaders(cfg):\n    print(\"Loading CSVs...\")\n    train_df     = pd.read_csv(cfg['train_csv'])\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    print(f\"  Split fingerprint -> \"\n          f\"train={len(train_df)} (hash={pd.util.hash_pandas_object(train_df['stem']).sum()}) \"\n          f\"val={len(val_df)} indist={len(indist_df)} bench={len(benchmark_df)}\")\n\n    sz = cfg['img_size']\n    nw = cfg['num_workers']\n    bs = cfg['batch_size']\n\n    train_loader = DataLoader(\n        PolypSegDataset(train_df, get_train_transform(sz)),\n        batch_size=bs, shuffle=True,\n        num_workers=nw, pin_memory=True, drop_last=True)\n    val_loader = DataLoader(\n        PolypSegDataset(val_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(\n        PolypSegDataset(indist_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(\n        PolypSegDataset(benchmark_df, get_val_transform(sz)),\n        batch_size=bs, shuffle=False,\n        num_workers=nw, pin_memory=True)\n\n    print(f\"  Train:{len(train_df)} | Val:{len(val_df)} | \"\n          f\"Indist:{len(indist_df)} | Bench:{len(benchmark_df)}\")\n\n    return {\n        'train'         : train_loader,\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# ENCODER (identical, unchanged)\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    def __init__(self, backbone, source='timm', needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source\n        self.needs_permute = needs_permute\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n        return tuple(feats)\n\n\ndef build_pvt_encoder(pretrained=True, img_size=352):\n    m = timm.create_model(\n        \"pvt_v2_b3\", pretrained=pretrained,\n        features_only=True, out_indices=(0, 1, 2, 3))\n    wrapper = EncoderWrapper(m, source='timm')\n\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, img_size, img_size)\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n    wrapper.train()\n\n    return wrapper, enc_channels\n\n\n# ============================================================\n# DECODER — DROPOUT (from Checkpoint A) + EVIDENTIAL HEAD\n# (from Checkpoint B), COMBINED. This is the one place the two\n# lines of work actually merge architecturally.\n# ============================================================\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride,\n                      padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass HybridDropoutEvidentialDecoder(nn.Module):\n    \"\"\"\n    Lateral/fuse structure + dropout placement IDENTICAL to\n    Checkpoint A's DropoutFPNDecoder. Only the seg_head differs,\n    matching Checkpoint B's EvidentialFPNDecoder (softplus evidence,\n    num_classes channels instead of a single sigmoid channel).\n\n    Dropout stays OUT of the seg_head for the same reason as\n    Checkpoint A: dropout immediately before the evidence output\n    would inject raw noise into the Dirichlet parameters rather than\n    diversifying the underlying segmentation decision upstream.\n    \"\"\"\n    def __init__(self, enc_channels, dec_channels=64,\n                 dropout_p=0.2, num_classes=2):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n        self.num_classes = num_classes\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.drop_lat4  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat3  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat2  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat1  = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse3 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse2 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse1 = nn.Dropout2d(p=dropout_p)\n\n        # Evidential head — no dropout here, deliberately.\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, num_classes, 1),\n        )\n\n        self.dropout_p = dropout_p\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.drop_lat4(self.lat4(f4))\n        p3 = self.drop_lat3(self.lat3(f3))\n        p2 = self.drop_lat2(self.lat2(f2))\n        p1 = self.drop_lat1(self.lat1(f1))\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.drop_fuse3(self.fuse3(torch.cat([up(p4, p3), p3], dim=1)))\n        p2f = self.drop_fuse2(self.fuse2(torch.cat([up(p3f, p2), p2], dim=1)))\n        p1f = self.drop_fuse1(self.fuse1(torch.cat([up(p2f, p1), p1], dim=1)))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        raw_output = self.seg_head(out)\n        evidence = F.softplus(raw_output)\n        return evidence\n\n\nclass PolypSegHybrid(nn.Module):\n    \"\"\"\n    PVTv2-b3 encoder + hybrid (dropout + evidential) decoder.\n    Training uses standard evidential loss with dropout naturally\n    active (model.train() enables Dropout2d as usual). Inference\n    supports BOTH the standalone evidential extraction (dropout off)\n    AND the MC-variance-of-evidential-output extraction (dropout on,\n    T passes) -- see evidential_predict() and hybrid_mc_predict()\n    at the bottom of this file.\n    \"\"\"\n    def __init__(self, pretrained=True, dec_channels=64,\n                 dropout_p=0.2, num_classes=2, img_size=352):\n        super().__init__()\n        self.encoder, enc_channels = build_pvt_encoder(pretrained, img_size)\n        self.decoder = HybridDropoutEvidentialDecoder(\n            enc_channels, dec_channels, dropout_p, num_classes)\n        self.encoder_name = \"pvt_v2_b3_hybrid_mcdropout_evidential\"\n        self.enc_channels = enc_channels\n        self.num_classes = num_classes\n        self.dropout_p = dropout_p\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        evidence = self.decoder(features, (H, W))\n        return {'evidence': evidence}\n\n    def enable_mc_dropout(self):\n        \"\"\"Same mechanism as Checkpoint A: only Dropout2d modules\n        go to train-mode behavior, rest of the model stays in eval.\"\"\"\n        self.eval()\n        for m in self.modules():\n            if isinstance(m, nn.Dropout2d):\n                m.train()\n\n\n# ============================================================\n# EVIDENTIAL LOSS (same retuned schedule as v2 standalone)\n# ============================================================\n\ndef one_hot_targets(mask, num_classes=2):\n    mask_long = mask.long().squeeze(1)\n    one_hot = F.one_hot(mask_long, num_classes=num_classes)\n    one_hot = one_hot.permute(0, 3, 1, 2).float()\n    return one_hot\n\n\ndef evidential_dirichlet_loss(evidence, target_one_hot):\n    alpha = evidence + 1.0\n    S = alpha.sum(dim=1, keepdim=True)\n    p_hat = alpha / S\n    term_err = (target_one_hot - p_hat).pow(2)\n    term_var = alpha * (S - alpha) / (S.pow(2) * (S + 1.0))\n    per_pixel_loss = (term_err + term_var).sum(dim=1)\n    return per_pixel_loss.mean()\n\n\ndef kl_divergence_regularizer(evidence, target_one_hot, num_classes=2):\n    alpha = evidence + 1.0\n    alpha_tilde = target_one_hot + (1.0 - target_one_hot) * alpha\n\n    S_tilde = alpha_tilde.sum(dim=1, keepdim=True)\n    C = num_classes\n\n    lgamma_S_tilde = torch.lgamma(S_tilde)\n    lgamma_C = torch.lgamma(torch.tensor(float(C), device=evidence.device))\n    lgamma_alpha_tilde = torch.lgamma(alpha_tilde)\n\n    term1 = lgamma_S_tilde - lgamma_C - lgamma_alpha_tilde.sum(dim=1, keepdim=True)\n    digamma_alpha_tilde = torch.digamma(alpha_tilde)\n    digamma_S_tilde = torch.digamma(S_tilde)\n    term2 = ((alpha_tilde - 1.0) * (digamma_alpha_tilde - digamma_S_tilde)).sum(dim=1, keepdim=True)\n\n    kl = (term1 + term2).squeeze(1)\n    return kl.mean()\n\n\ndef evidential_loss(evidence, mask, epoch, cfg):\n    target_one_hot = one_hot_targets(mask, num_classes=cfg['num_classes'])\n    main_loss = evidential_dirichlet_loss(evidence, target_one_hot)\n    kl_weight = min(1.0, epoch / max(1, cfg['kl_anneal_epochs'])) * cfg['kl_target_weight']\n    kl_loss = kl_divergence_regularizer(evidence, target_one_hot, cfg['num_classes'])\n    total_loss = main_loss + kl_weight * kl_loss\n    return total_loss, main_loss, kl_loss, kl_weight\n\n\n@torch.no_grad()\ndef evidential_expected_prob(evidence):\n    alpha = evidence + 1.0\n    S = alpha.sum(dim=1, keepdim=True)\n    p_hat = alpha / S\n    return p_hat\n\n\n# ============================================================\n# METRICS\n# ============================================================\n\nclass MetricTracker:\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses      = []\n        self.main_losses = []\n        self.kl_losses   = []\n        self.dice_scores = []\n        self.iou_scores  = []\n        self.prec_scores = []\n        self.rec_scores  = []\n        self.dataset_results = {}\n\n    @torch.no_grad()\n    def update(self, evidence, batch, total_loss, main_loss, kl_loss):\n        self.losses.append(total_loss.item())\n        self.main_losses.append(main_loss.item())\n        self.kl_losses.append(kl_loss.item())\n\n        p_hat = evidential_expected_prob(evidence)\n        polyp_prob = p_hat[:, 1:2, :, :]\n        pred = (polyp_prob > 0.5).float()\n        tgt  = batch['mask'].to(pred.device)\n\n        pf = pred.view(pred.size(0), -1)\n        tf = tgt.view(tgt.size(0), -1)\n        tp = (pf * tf).sum(1)\n        fp = pf.sum(1) - tp\n        fn = tf.sum(1) - tp\n\n        dice = (2 * tp / (2 * tp + fp + fn + 1e-6)).mean().item()\n        iou  = (tp / (tp + fp + fn + 1e-6)).mean().item()\n        prec = (tp / (tp + fp + 1e-6)).mean().item()\n        rec  = (tp / (tp + fn + 1e-6)).mean().item()\n\n        self.dice_scores.append(dice)\n        self.iou_scores.append(iou)\n        self.prec_scores.append(prec)\n        self.rec_scores.append(rec)\n\n        for i, ds in enumerate(batch['dataset']):\n            if ds not in self.dataset_results:\n                self.dataset_results[ds] = {\n                    'dice': [], 'iou': [], 'prec': [], 'rec': []}\n            d = self.dataset_results[ds]\n            p_i, t_i = pred[i:i+1].view(-1), tgt[i:i+1].view(-1)\n            tp_i = (p_i * t_i).sum()\n            fp_i = p_i.sum() - tp_i\n            fn_i = t_i.sum() - tp_i\n            d['dice'].append((2*tp_i/(2*tp_i+fp_i+fn_i+1e-6)).item())\n            d['iou'].append((tp_i/(tp_i+fp_i+fn_i+1e-6)).item())\n            d['prec'].append((tp_i/(tp_i+fp_i+1e-6)).item())\n            d['rec'].append((tp_i/(tp_i+fn_i+1e-6)).item())\n\n    def compute(self):\n        gross = dict(\n            loss      = np.mean(self.losses),\n            main_loss = np.mean(self.main_losses),\n            kl_loss   = np.mean(self.kl_losses),\n            dice      = np.mean(self.dice_scores),\n            iou       = np.mean(self.iou_scores),\n            precision = np.mean(self.prec_scores),\n            recall    = np.mean(self.rec_scores),\n        )\n        per_dataset = {}\n        for ds_key, d in self.dataset_results.items():\n            per_dataset[ds_key] = dict(\n                n         = len(d['dice']),\n                dice      = np.mean(d['dice']),\n                iou       = np.mean(d['iou']),\n                precision = np.mean(d['prec']),\n                recall    = np.mean(d['rec']),\n            )\n        gross['per_dataset'] = per_dataset\n        return gross\n\n\n# ============================================================\n# OPTIMIZER\n# ============================================================\n\ndef build_optimizer(model, cfg):\n    enc_params = list(model.encoder.parameters())\n    enc_ids    = {id(p) for p in enc_params}\n    other      = [p for p in model.parameters() if id(p) not in enc_ids]\n    opt = torch.optim.AdamW([\n        {'params': enc_params, 'lr': cfg['encoder_lr']},\n        {'params': other,      'lr': cfg['decoder_lr']},\n    ], weight_decay=cfg['weight_decay'])\n    sch = torch.optim.lr_scheduler.CosineAnnealingLR(\n        opt, T_max=cfg['num_epochs'], eta_min=cfg['min_lr'])\n    return opt, sch\n\n\n# ============================================================\n# TRAIN / VALIDATE\n# ============================================================\n\ndef train_one_epoch(model, loader, optimizer, device, cfg, epoch):\n    model.train()  # dropout naturally active during training, as usual\n    tracker = MetricTracker()\n    pbar = tqdm(loader, desc='  Train', leave=False, ncols=100)\n    for batch in pbar:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n\n        optimizer.zero_grad()\n        outputs = model(image)\n        total_loss, main_loss, kl_loss, kl_w = evidential_loss(\n            outputs['evidence'], target, epoch, cfg)\n        total_loss.backward()\n        torch.nn.utils.clip_grad_norm_(\n            model.parameters(), cfg['grad_clip'])\n        optimizer.step()\n\n        tracker.update(outputs['evidence'], batch, total_loss, main_loss, kl_loss)\n        pbar.set_postfix(\n            loss=f\"{total_loss.item():.4f}\",\n            kl_w=f\"{kl_w:.3f}\",\n            dice=f\"{tracker.dice_scores[-1]:.4f}\")\n    return tracker.compute()\n\n\n@torch.no_grad()\ndef validate(model, loader, device, cfg, epoch):\n    \"\"\"Standard validation: dropout OFF (model.eval()), deterministic\n    Dice for model selection/early stopping. MC-variance extraction\n    is a separate, later step -- same pattern as Checkpoint A.\"\"\"\n    model.eval()\n    tracker = MetricTracker()\n    for batch in loader:\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        total_loss, main_loss, kl_loss, kl_w = evidential_loss(\n            outputs['evidence'], target, epoch, cfg)\n        tracker.update(outputs['evidence'], batch, total_loss, main_loss, kl_loss)\n    return tracker.compute()\n\n\n# ============================================================\n# CHECKPOINTING\n# ============================================================\n\nclass CheckpointManager:\n    def __init__(self, save_dir, patience):\n        self.save_dir   = Path(save_dir)\n        self.save_dir.mkdir(parents=True, exist_ok=True)\n        self.patience   = patience\n        self.best_dice  = 0.\n        self.best_epoch = 0\n        self.counter    = 0\n\n    def step(self, model, optimizer, scheduler, epoch, metrics, extra_cfg=None):\n        dice     = metrics['dice']\n        improved = dice > self.best_dice\n        if improved:\n            self.best_dice  = dice\n            self.best_epoch = epoch\n            self.counter    = 0\n            payload = dict(\n                epoch=epoch, model=model.state_dict(),\n                optimizer=optimizer.state_dict(),\n                scheduler=scheduler.state_dict(),\n                best_dice=self.best_dice,\n                encoder_name=model.encoder_name,\n                num_classes=model.num_classes,\n                dropout_p=model.dropout_p,\n                checkpoint_type='hybrid_mcdropout_evidential',\n            )\n            if extra_cfg:\n                payload['cfg'] = extra_cfg\n            torch.save(payload, self.save_dir / 'best_model.pth')\n            print(f\"  \\u2713 Saved best (dice={dice:.4f})\")\n        else:\n            self.counter += 1\n        return self.counter >= self.patience\n\n    def load_best(self, model, device):\n        ck = torch.load(\n            self.save_dir / 'best_model.pth',\n            map_location=device, weights_only=False)\n        model.load_state_dict(ck['model'])\n        print(f\"  Loaded best epoch {ck['epoch']} \"\n              f\"(dice={ck['best_dice']:.4f})\")\n        return model\n\n\n# ============================================================\n# LOGGING\n# ============================================================\n\ndef print_epoch_summary(epoch, num_epochs, train_m, val_m, lr, kl_weight):\n    print(f\"\\nEpoch [{epoch+1:>3}/{num_epochs}]  LR={lr:.2e}  KL_weight={kl_weight:.4f}\")\n    print(f\"  {'GROSS METRIC':14s}  {'Train':>8}  {'Val':>8}\")\n    print(f\"  {'-'*36}\")\n    for key, label in [\n        ('loss',      'Total Loss'),\n        ('main_loss', 'Dirichlet L'),\n        ('kl_loss',   'KL term'),\n        ('dice',      'Dice'),\n        ('iou',       'IoU'),\n        ('precision', 'Precision'),\n        ('recall',    'Recall'),\n    ]:\n        print(f\"  {label:14s}  {train_m.get(key,0):8.4f}  {val_m.get(key,0):8.4f}\")\n\n    if val_m.get('per_dataset'):\n        print(f\"  {'-'*36}\")\n        print(f\"  VAL PER-DATASET:\")\n        for ds_key, d in val_m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"    {ds_name:15s} n={d['n']:>4}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n\ndef append_epoch_log(log_path, epoch, tag, train_m, val_m, lr, kl_weight):\n    rows = []\n    for split_name, m in [('train', train_m), ('val', val_m)]:\n        rows.append(dict(\n            checkpoint=tag, epoch=epoch+1, split=split_name,\n            dataset='ALL', lr=lr, kl_weight=kl_weight,\n            loss=m.get('loss'), main_loss=m.get('main_loss'),\n            kl_loss=m.get('kl_loss'),\n            dice=m.get('dice'), iou=m.get('iou'),\n            precision=m.get('precision'), recall=m.get('recall'),\n        ))\n        for ds_key, d in m.get('per_dataset', {}).items():\n            rows.append(dict(\n                checkpoint=tag, epoch=epoch+1, split=split_name,\n                dataset=DATASET_INFO.get(ds_key, ds_key), lr=lr,\n                kl_weight=kl_weight, loss=None, main_loss=None, kl_loss=None,\n                dice=d['dice'], iou=d['iou'],\n                precision=d['precision'], recall=d['recall'],\n            ))\n    df = pd.DataFrame(rows)\n    header = not Path(log_path).exists()\n    df.to_csv(log_path, mode='a', header=header, index=False)\n\n\n# ============================================================\n# FINAL EVALUATION — same dual gate (raw delta + generalization\n# gap) used for the retuned standalone evidential checkpoint\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_single_loader(model, loader, device, split_name, cfg, epoch):\n    model.eval()\n    tracker = MetricTracker()\n    for batch in tqdm(loader, desc=f'  Eval {split_name}', leave=False):\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        outputs = model(image)\n        total_loss, main_loss, kl_loss, _ = evidential_loss(\n            outputs['evidence'], target, epoch, cfg)\n        tracker.update(outputs['evidence'], batch, total_loss, main_loss, kl_loss)\n\n    m = tracker.compute()\n\n    print(f\"\\n{'='*60}\\n  {split_name.upper()}\\n{'='*60}\")\n    print(f\"  Dice     : {m['dice']:.4f}\")\n    print(f\"  IoU      : {m['iou']:.4f}\")\n    print(f\"  Precision: {m['precision']:.4f}\")\n    print(f\"  Recall   : {m['recall']:.4f}\")\n\n    if m['per_dataset']:\n        print(f\"\\n  PER-DATASET BREAKDOWN\")\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            print(f\"  [{ds_name}] n={d['n']}  \"\n                  f\"Dice={d['dice']:.4f}  IoU={d['iou']:.4f}  \"\n                  f\"Prec={d['precision']:.4f}  Rec={d['recall']:.4f}\")\n\n    return m\n\n\n@torch.no_grad()\ndef evaluate_all_datasets(model, data, device, save_dir, tag, cfg,\n                          final_epoch, reference_dice=0.8444,\n                          mcdropout_gap=0.117):\n    print(\"\\n\" + \"=\"*60)\n    print(f\"FINAL EVALUATION — {tag}\")\n    print(\"=\"*60)\n\n    results = {}\n    results['val']       = evaluate_single_loader(model, data['val'], device, 'VALIDATION SET', cfg, final_epoch)\n    results['indist']    = evaluate_single_loader(model, data['indist_test'], device, 'IN-DISTRIBUTION TEST', cfg, final_epoch)\n    results['benchmark'] = evaluate_single_loader(model, data['benchmark_test'], device, 'BENCHMARK (UNSEEN)', cfg, final_epoch)\n\n    val_dice   = results['val']['dice']\n    bench_dice = results['benchmark']['dice']\n    delta      = bench_dice - reference_dice\n    gen_gap    = val_dice - bench_dice\n\n    print(f\"\\n{'='*60}\")\n    print(f\"REGRESSION GATE CHECK — Hybrid model\")\n    print(f\"  Original PVTv2-b3 benchmark Dice : {reference_dice:.4f}\")\n    print(f\"  This checkpoint benchmark Dice   : {bench_dice:.4f}\")\n    print(f\"  Delta                            : {delta:+.4f}\")\n    print(f\"  Val Dice                         : {val_dice:.4f}\")\n    print(f\"  Generalization gap (val - bench) : {gen_gap:.4f}\")\n    print(f\"  MC Dropout checkpoint's gap (ref): {mcdropout_gap:.4f}\")\n\n    gate_pass = delta >= -0.02 and gen_gap <= (mcdropout_gap + 0.015)\n    if not gate_pass:\n        print(f\"\\n  \\u26a0 WARNING: outside acceptable range. Given this \"\n              f\"combines dropout + evidential, if this fails while \"\n              f\"the standalone evidential v2 passes, the interaction \"\n              f\"between dropout and the KL dynamics may need its own \"\n              f\"adjustment (e.g. reduce dropout_p to 0.15, or retune \"\n              f\"KL schedule again specifically for this combined model).\")\n    else:\n        print(f\"\\n  \\u2713 Within acceptable range — safe to proceed to \"\n              f\"BOTH evidential and hybrid-MC-variance extraction.\")\n    print(f\"{'='*60}\")\n\n    rows = []\n    total_params = sum(p.numel() for p in model.parameters())\n    for split, m in results.items():\n        rows.append(dict(\n            checkpoint=tag, params=total_params, split=split,\n            dice=round(m['dice'],4), iou=round(m['iou'],4),\n            precision=round(m['precision'],4), recall=round(m['recall'],4),\n        ))\n        for ds_key, d in m['per_dataset'].items():\n            ds_name = DATASET_INFO.get(ds_key, ds_key)\n            rows.append(dict(\n                checkpoint=tag, params=total_params,\n                split=f\"{split}_{ds_name}\",\n                dice=round(d['dice'],4), iou=round(d['iou'],4),\n                precision=round(d['precision'],4), recall=round(d['recall'],4),\n            ))\n\n    comparison_path = Path(save_dir) / 'uncertainty_checkpoint_results.csv'\n    df = pd.DataFrame(rows)\n    header = not comparison_path.exists()\n    df.to_csv(comparison_path, mode='a', header=header, index=False)\n    print(f\"\\n  Results appended to: {comparison_path}\")\n\n    return results\n\n\n# ============================================================\n# MAIN TRAINING\n# ============================================================\n\ndef train(cfg):\n    set_seed(cfg['seed'])\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n    print(f\"Checkpoint: PVTv2-b3 HYBRID (dropout decoder + evidential head)\")\n    print(f\"  decoder_dropout_p : {cfg['decoder_dropout_p']}\")\n    print(f\"  kl_target_weight  : {cfg['kl_target_weight']}\")\n    print(f\"  kl_anneal_epochs  : {cfg['kl_anneal_epochs']}\")\n\n    data = create_dataloaders(cfg)\n\n    model = PolypSegHybrid(\n        pretrained=True,\n        dec_channels=cfg['dec_channels'],\n        dropout_p=cfg['decoder_dropout_p'],\n        num_classes=cfg['num_classes'],\n        img_size=cfg['img_size'],\n    ).to(device)\n\n    total = sum(p.numel() for p in model.parameters())\n    n_dropout_modules = sum(1 for m in model.modules() if isinstance(m, nn.Dropout2d))\n    print(f\"Encoder output channels: {model.enc_channels}\")\n    print(f\"Params: {total/1e6:.2f}M total\")\n    print(f\"Dropout2d modules in decoder: {n_dropout_modules} (expected 7)\")\n    assert n_dropout_modules == 7, \\\n        \"Expected exactly 7 Dropout2d modules — check HybridDropoutEvidentialDecoder.\"\n\n    optimizer, scheduler = build_optimizer(model, cfg)\n    save_dir = Path(cfg['save_dir'])\n    tag = \"pvt_v2_b3_hybrid_mcdropout_evidential\"\n    ckpt = CheckpointManager(save_dir / tag, cfg['patience'])\n    epoch_log_path = save_dir / f\"epoch_log_{tag}.csv\"\n\n    print(\"\\n\" + \"=\"*60)\n    print(f\"TRAINING — {tag}\")\n    print(\"  Decoder: FPN + Dropout2d(p={:.2f}) after every lateral/fuse block\"\n          .format(cfg['decoder_dropout_p']))\n    print(\"  Head   : Conv2d(c, 2, 1) -> softplus -> evidence\")\n    print(\"  Loss   : Dirichlet expected loss + retuned annealed KL\")\n    print(\"=\"*60)\n\n    for epoch in range(cfg['num_epochs']):\n        train_m = train_one_epoch(model, data['train'], optimizer, device, cfg, epoch)\n        val_m   = validate(model, data['val'], device, cfg, epoch)\n\n        scheduler.step()\n        lr = optimizer.param_groups[0]['lr']\n        kl_weight = min(1.0, epoch / max(1, cfg['kl_anneal_epochs'])) * cfg['kl_target_weight']\n\n        print_epoch_summary(epoch, cfg['num_epochs'], train_m, val_m, lr, kl_weight)\n        append_epoch_log(epoch_log_path, epoch, tag, train_m, val_m, lr, kl_weight)\n\n        stop = ckpt.step(model, optimizer, scheduler, epoch, val_m, extra_cfg=cfg)\n        if stop:\n            print(f\"\\nEarly stopping at epoch {epoch+1}. \"\n                  f\"Best: epoch {ckpt.best_epoch+1} (dice={ckpt.best_dice:.4f})\")\n            break\n\n    final_epoch = ckpt.best_epoch\n    model = ckpt.load_best(model, device)\n    results = evaluate_all_datasets(model, data, device, cfg['save_dir'], tag,\n                                     cfg, final_epoch)\n\n    return model, results\n\n\n# ============================================================\n# UNCERTAINTY EXTRACTION — TWO SEPARATE SIGNALS\n# ============================================================\n\n@torch.no_grad()\ndef evidential_predict(model, image):\n    \"\"\"\n    SIGNAL 1: Evidential uncertainty, U = C/alpha_0.\n    Dropout OFF (standard eval mode) -- single deterministic pass.\n    Same quantity the standalone evidential checkpoint produces.\n    \"\"\"\n    model.eval()\n    outputs = model(image)\n    evidence = outputs['evidence']\n    alpha = evidence + 1.0\n    alpha_0 = alpha.sum(dim=1, keepdim=True)\n\n    expected_prob = alpha / alpha_0\n    C = evidence.shape[1]\n    uncertainty = C / alpha_0\n\n    return expected_prob, uncertainty\n\n\n@torch.no_grad()\ndef hybrid_mc_predict(model, image, T=4):\n    \"\"\"\n    SIGNAL 2: Hybrid epistemic uncertainty (MC-variance-of-evidential-\n    output). Decoder dropout ACTIVE across T stochastic passes; for\n    each pass, compute the expected probability p_hat = alpha/alpha_0\n    from that pass's evidence. Variance of p_hat ACROSS the T passes\n    is the epistemic signal -- matches the DET-MCD / Lymphoma-paper\n    pattern of layering MC sampling on top of an evidential model's\n    output, kept SEPARATE from (not merged with) evidential_predict's\n    aleatoric-leaning signal above.\n\n    Returns (mean_p_hat, epistemic_uncertainty). Caller should also\n    call evidential_predict() separately to get the aleatoric signal\n    -- store BOTH as distinct columns downstream, never combine them\n    into one number.\n    \"\"\"\n    model.enable_mc_dropout()\n\n    p_hats = []\n    for _ in range(T):\n        outputs = model(image)\n        evidence = outputs['evidence']\n        alpha = evidence + 1.0\n        alpha_0 = alpha.sum(dim=1, keepdim=True)\n        p_hat = alpha / alpha_0          # [B, num_classes, H, W]\n        p_hats.append(p_hat)\n    p_hats = torch.stack(p_hats, dim=0)   # [T, B, C, H, W]\n\n    mean_p_hat = p_hats.mean(dim=0)\n    epistemic_uncertainty = p_hats.var(dim=0)   # per-class variance across passes\n\n    model.eval()  # restore full determinism after MC sampling\n    return mean_p_hat, epistemic_uncertainty\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\nif __name__ == '__main__':\n    print(\"=\"*60)\n    print(\"SANITY CHECK — PVTv2-b3 Hybrid (Dropout + Evidential)\")\n    print(\"=\"*60)\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    model  = PolypSegHybrid(\n        pretrained=False, dropout_p=CFG['decoder_dropout_p'],\n        num_classes=CFG['num_classes'], img_size=CFG['img_size']).to(device)\n\n    dummy_img = torch.randn(2, 3, CFG['img_size'], CFG['img_size']).to(device)\n    dummy_mask = torch.randint(0, 2, (2, 1, CFG['img_size'], CFG['img_size'])).float().to(device)\n\n    # 1. Standard forward pass (eval, dropout off)\n    model.eval()\n    with torch.no_grad():\n        out = model(dummy_img)\n    evidence = out['evidence']\n    print(f\"Evidence shape: {list(evidence.shape)}\")\n    assert evidence.min().item() >= 0, \"Evidence has negative values.\"\n    print(\"  \\u2713 Evidence non-negative.\")\n\n    # 2. Loss sanity check\n    total_loss, main_loss, kl_loss, kl_w = evidential_loss(evidence, dummy_mask, epoch=0, cfg=CFG)\n    assert not torch.isnan(total_loss)\n    print(f\"  \\u2713 Loss computes without NaN (epoch 0 KL weight={kl_w:.4f})\")\n\n    # 3. Dropout mechanics check (same test as Checkpoint A)\n    model.enable_mc_dropout()\n    with torch.no_grad():\n        out_a = model(dummy_img)['evidence']\n        out_b = model(dummy_img)['evidence']\n    diff = (out_a - out_b).abs().mean().item()\n    print(f\"\\nMC Dropout sanity check (two stochastic passes on evidence):\")\n    print(f\"  Mean absolute difference: {diff:.6f}\")\n    assert diff > 1e-6, \"MC Dropout passes are IDENTICAL — dropout not active.\"\n    print(f\"  \\u2713 Passes differ as expected.\")\n\n    model.eval()\n    with torch.no_grad():\n        out_c = model(dummy_img)['evidence']\n        out_d = model(dummy_img)['evidence']\n    diff2 = (out_c - out_d).abs().mean().item()\n    print(f\"\\nPost-MC eval() restoration check:\")\n    print(f\"  Mean absolute difference: {diff2:.8f}\")\n    assert diff2 < 1e-6, \"Model NOT deterministic after eval() post-MC-sampling.\"\n    print(f\"  \\u2713 Standard eval mode correctly restored.\")\n\n    # 4. Verify the two extraction functions produce genuinely\n    # DIFFERENT signals (not accidentally identical / redundant)\n    exp_prob, evid_uncertainty = evidential_predict(model, dummy_img)\n    mean_p, epistemic_uncertainty = hybrid_mc_predict(model, dummy_img, T=CFG['mc_passes_T'])\n    print(f\"\\nTwo-signal distinctness check:\")\n    print(f\"  Evidential uncertainty shape : {list(evid_uncertainty.shape)}, \"\n          f\"range [{evid_uncertainty.min().item():.4f}, {evid_uncertainty.max().item():.4f}]\")\n    print(f\"  Epistemic (MC) uncertainty shape: {list(epistemic_uncertainty.shape)}, \"\n          f\"range [{epistemic_uncertainty.min().item():.6f}, {epistemic_uncertainty.max().item():.6f}]\")\n    print(f\"  (Different shapes/ranges are expected -- evidential is \"\n          f\"[B,1,H,W] from C/alpha_0, epistemic is [B,C,H,W] variance \"\n          f\"across passes. These are DELIBERATELY kept as separate \"\n          f\"quantities, not merged.)\")\n\n    total = sum(p.numel() for p in model.parameters())\n    print(f\"\\nTotal params: {total/1e6:.2f}M\")\n    print(\"\\nSanity check passed \\u2713\")\n    print(\"=\"*60)\n    print(\"\\nStarting training...\")\n    model, results = train(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:03:28.151728Z","iopub.execute_input":"2026-08-26T04:03:28.152154Z","iopub.status.idle":"2026-08-26T08:39:57.44204Z","shell.execute_reply.started":"2026-08-26T04:03:28.152126Z","shell.execute_reply":"2026-08-26T08:39:57.441261Z"}},"outputs":[{"name":"stdout","text":"============================================================\nSANITY CHECK — PVTv2-b3 Hybrid (Dropout + Evidential)\n============================================================\nEvidence shape: [2, 2, 352, 352]\n  ✓ Evidence non-negative.\n  ✓ Loss computes without NaN (epoch 0 KL weight=0.0000)\n\nMC Dropout sanity check (two stochastic passes on evidence):\n  Mean absolute difference: 0.006385\n  ✓ Passes differ as expected.\n\nPost-MC eval() restoration check:\n  Mean absolute difference: 0.00000000\n  ✓ Standard eval mode correctly restored.\n\nTwo-signal distinctness check:\n  Evidential uncertainty shape : [2, 1, 352, 352], range [0.6014, 0.6175]\n  Epistemic (MC) uncertainty shape: [2, 2, 352, 352], range [0.000000, 0.000017]\n  (Different shapes/ranges are expected -- evidential is [B,1,H,W] from C/alpha_0, epistemic is [B,C,H,W] variance across passes. These are DELIBERATELY kept as separate quantities, not merged.)\n\nTotal params: 45.57M\n\nSanity check passed ✓\n============================================================\n\nStarting training...\nDevice: cuda\nCheckpoint: PVTv2-b3 HYBRID (dropout decoder + evidential head)\n  decoder_dropout_p : 0.2\n  kl_target_weight  : 0.05\n  kl_anneal_epochs  : 20\nLoading CSVs...\n  Split fingerprint -> train=2089 (hash=6780967082095866867) val=261 indist=262 bench=636\n  Train:2089 | Val:261 | Indist:262 | Bench:636\n","output_type":"stream"},{"name":"stderr","text":"Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"model.safetensors:   0%|          | 0.00/181M [00:00<?, ?B/s]","application/vnd.jupyter.widget-view+json":{"version_major":2,"version_minor":0,"model_id":"3bd6bdd963084d2a9c8cdcc75fa96cc6"}},"metadata":{}},{"name":"stdout","text":"Encoder output channels: (64, 128, 320, 512)\nParams: 45.57M total\nDropout2d modules in decoder: 7 (expected 7)\n\n============================================================\nTRAINING — pvt_v2_b3_hybrid_mcdropout_evidential\n  Decoder: FPN + Dropout2d(p=0.20) after every lateral/fuse block\n  Head   : Conv2d(c, 2, 1) -> softplus -> evidence\n  Loss   : Dirichlet expected loss + retuned annealed KL\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  1/60]  LR=9.99e-05  KL_weight=0.0000\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.3119    0.2039\n  Dirichlet L       0.3119    0.2039\n  KL term           0.0475    0.0329\n  Dice              0.6988    0.8203\n  IoU               0.5958    0.7293\n  Precision         0.6994    0.8147\n  Recall            0.7971    0.8828\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8101  IoU=0.7220  Prec=0.8237  Rec=0.8548\n    CVC-ClinicDB    n=  61  Dice=0.8544  IoU=0.7628  Prec=0.8528  Rec=0.8898\n    Kvasir-SEG      n= 100  Dice=0.8144  IoU=0.7206  Prec=0.7883  Rec=0.9115\n  ✓ Saved best (dice=0.8203)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  2/60]  LR=9.97e-05  KL_weight=0.0025\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.1662    0.1185\n  Dirichlet L       0.1661    0.1184\n  KL term           0.0260    0.0165\n  Dice              0.7844    0.8693\n  IoU               0.6982    0.7954\n  Precision         0.8197    0.8791\n  Recall            0.8094    0.8844\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8590  IoU=0.7846  Prec=0.8618  Rec=0.8766\n    CVC-ClinicDB    n=  61  Dice=0.8803  IoU=0.7983  Prec=0.8875  Rec=0.9009\n    Kvasir-SEG      n= 100  Dice=0.8820  IoU=0.8131  Prec=0.8981  Rec=0.8924\n  ✓ Saved best (dice=0.8693)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  3/60]  LR=9.94e-05  KL_weight=0.0050\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.1138    0.0869\n  Dirichlet L       0.1137    0.0868\n  KL term           0.0240    0.0185\n  Dice              0.8124    0.8820\n  IoU               0.7330    0.8117\n  Precision         0.8481    0.9153\n  Recall            0.8285    0.8785\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8901  IoU=0.8213  Prec=0.8816  Rec=0.9167\n    CVC-ClinicDB    n=  61  Dice=0.8727  IoU=0.8016  Prec=0.9218  Rec=0.8578\n    Kvasir-SEG      n= 100  Dice=0.8836  IoU=0.8136  Prec=0.9453  Rec=0.8592\n  ✓ Saved best (dice=0.8820)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  4/60]  LR=9.89e-05  KL_weight=0.0075\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0922    0.0703\n  Dirichlet L       0.0920    0.0701\n  KL term           0.0272    0.0184\n  Dice              0.8232    0.8947\n  IoU               0.7458    0.8298\n  Precision         0.8595    0.9331\n  Recall            0.8339    0.8838\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8905  IoU=0.8256  Prec=0.9324  Rec=0.8738\n    CVC-ClinicDB    n=  61  Dice=0.9129  IoU=0.8514  Prec=0.9411  Rec=0.9000\n    Kvasir-SEG      n= 100  Dice=0.8917  IoU=0.8248  Prec=0.9290  Rec=0.8882\n  ✓ Saved best (dice=0.8947)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  5/60]  LR=9.83e-05  KL_weight=0.0100\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0742    0.0640\n  Dirichlet L       0.0739    0.0638\n  KL term           0.0263    0.0246\n  Dice              0.8491    0.8817\n  IoU               0.7765    0.8165\n  Precision         0.8766    0.9278\n  Recall            0.8598    0.8697\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8880  IoU=0.8279  Prec=0.9036  Rec=0.8917\n    CVC-ClinicDB    n=  61  Dice=0.8743  IoU=0.8080  Prec=0.9459  Rec=0.8492\n    Kvasir-SEG      n= 100  Dice=0.8824  IoU=0.8142  Prec=0.9426  Rec=0.8639\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  6/60]  LR=9.76e-05  KL_weight=0.0125\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0675    0.0595\n  Dirichlet L       0.0672    0.0592\n  KL term           0.0297    0.0274\n  Dice              0.8443    0.8680\n  IoU               0.7754    0.8021\n  Precision         0.8736    0.9107\n  Recall            0.8513    0.8614\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8575  IoU=0.7949  Prec=0.8838  Rec=0.8735\n    CVC-ClinicDB    n=  61  Dice=0.8869  IoU=0.8160  Prec=0.9364  Rec=0.8717\n    Kvasir-SEG      n= 100  Dice=0.8756  IoU=0.8099  Prec=0.9279  Rec=0.8531\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  7/60]  LR=9.67e-05  KL_weight=0.0150\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0587    0.0458\n  Dirichlet L       0.0583    0.0455\n  KL term           0.0288    0.0216\n  Dice              0.8563    0.9042\n  IoU               0.7895    0.8452\n  Precision         0.8842    0.9204\n  Recall            0.8629    0.9083\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9046  IoU=0.8489  Prec=0.9138  Rec=0.9061\n    CVC-ClinicDB    n=  61  Dice=0.9176  IoU=0.8592  Prec=0.9289  Rec=0.9215\n    Kvasir-SEG      n= 100  Dice=0.8990  IoU=0.8365  Prec=0.9230  Rec=0.9059\n  ✓ Saved best (dice=0.9042)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  8/60]  LR=9.57e-05  KL_weight=0.0175\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0539    0.0428\n  Dirichlet L       0.0533    0.0424\n  KL term           0.0306    0.0248\n  Dice              0.8590    0.9063\n  IoU               0.7919    0.8486\n  Precision         0.8867    0.9225\n  Recall            0.8659    0.9157\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9001  IoU=0.8482  Prec=0.9295  Rec=0.9025\n    CVC-ClinicDB    n=  61  Dice=0.9298  IoU=0.8746  Prec=0.9385  Rec=0.9304\n    Kvasir-SEG      n= 100  Dice=0.9008  IoU=0.8367  Prec=0.9089  Rec=0.9210\n  ✓ Saved best (dice=0.9063)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [  9/60]  LR=9.46e-05  KL_weight=0.0200\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0499    0.0402\n  Dirichlet L       0.0492    0.0396\n  KL term           0.0329    0.0266\n  Dice              0.8652    0.9075\n  IoU               0.7996    0.8503\n  Precision         0.8930    0.9225\n  Recall            0.8698    0.9101\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9050  IoU=0.8512  Prec=0.9127  Rec=0.9118\n    CVC-ClinicDB    n=  61  Dice=0.9195  IoU=0.8591  Prec=0.9311  Rec=0.9225\n    Kvasir-SEG      n= 100  Dice=0.9075  IoU=0.8490  Prec=0.9311  Rec=0.9059\n  ✓ Saved best (dice=0.9075)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 10/60]  LR=9.34e-05  KL_weight=0.0225\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0479    0.0370\n  Dirichlet L       0.0471    0.0364\n  KL term           0.0355    0.0243\n  Dice              0.8701    0.8944\n  IoU               0.8065    0.8404\n  Precision         0.8975    0.9373\n  Recall            0.8741    0.8800\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8797  IoU=0.8259  Prec=0.9178  Rec=0.8714\n    CVC-ClinicDB    n=  61  Dice=0.9227  IoU=0.8662  Prec=0.9479  Rec=0.9128\n    Kvasir-SEG      n= 100  Dice=0.9012  IoU=0.8476  Prec=0.9551  Rec=0.8776\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 11/60]  LR=9.20e-05  KL_weight=0.0250\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0433    0.0443\n  Dirichlet L       0.0425    0.0435\n  KL term           0.0329    0.0346\n  Dice              0.8719    0.9072\n  IoU               0.8098    0.8470\n  Precision         0.9005    0.9089\n  Recall            0.8731    0.9300\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9180  IoU=0.8626  Prec=0.9120  Rec=0.9429\n    CVC-ClinicDB    n=  61  Dice=0.9301  IoU=0.8760  Prec=0.9346  Rec=0.9363\n    Kvasir-SEG      n= 100  Dice=0.8859  IoU=0.8180  Prec=0.8928  Rec=0.9165\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 12/60]  LR=9.05e-05  KL_weight=0.0275\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0401    0.0377\n  Dirichlet L       0.0392    0.0368\n  KL term           0.0323    0.0321\n  Dice              0.8807    0.8949\n  IoU               0.8209    0.8388\n  Precision         0.9048    0.9330\n  Recall            0.8822    0.8828\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.8988  IoU=0.8440  Prec=0.9143  Rec=0.8957\n    CVC-ClinicDB    n=  61  Dice=0.9217  IoU=0.8686  Prec=0.9473  Rec=0.9163\n    Kvasir-SEG      n= 100  Dice=0.8816  IoU=0.8227  Prec=0.9451  Rec=0.8574\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 13/60]  LR=8.90e-05  KL_weight=0.0300\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0418    0.0325\n  Dirichlet L       0.0407    0.0317\n  KL term           0.0369    0.0286\n  Dice              0.8761    0.9159\n  IoU               0.8142    0.8621\n  Precision         0.9028    0.9340\n  Recall            0.8789    0.9165\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9242  IoU=0.8757  Prec=0.9466  Rec=0.9138\n    CVC-ClinicDB    n=  61  Dice=0.9221  IoU=0.8716  Prec=0.9236  Rec=0.9383\n    Kvasir-SEG      n= 100  Dice=0.9079  IoU=0.8484  Prec=0.9306  Rec=0.9102\n  ✓ Saved best (dice=0.9159)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 14/60]  LR=8.73e-05  KL_weight=0.0325\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0387    0.0330\n  Dirichlet L       0.0375    0.0320\n  KL term           0.0361    0.0300\n  Dice              0.8777    0.9128\n  IoU               0.8179    0.8594\n  Precision         0.9023    0.9357\n  Recall            0.8789    0.9119\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9236  IoU=0.8742  Prec=0.9397  Rec=0.9182\n    CVC-ClinicDB    n=  61  Dice=0.9129  IoU=0.8623  Prec=0.9197  Rec=0.9348\n    Kvasir-SEG      n= 100  Dice=0.9052  IoU=0.8470  Prec=0.9438  Rec=0.8953\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 15/60]  LR=8.55e-05  KL_weight=0.0350\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0351    0.0333\n  Dirichlet L       0.0339    0.0322\n  KL term           0.0326    0.0305\n  Dice              0.8852    0.9142\n  IoU               0.8278    0.8612\n  Precision         0.9100    0.9437\n  Recall            0.8831    0.9039\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9261  IoU=0.8782  Prec=0.9481  Rec=0.9154\n    CVC-ClinicDB    n=  61  Dice=0.9405  IoU=0.8916  Prec=0.9479  Rec=0.9394\n    Kvasir-SEG      n= 100  Dice=0.8886  IoU=0.8284  Prec=0.9378  Rec=0.8735\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 16/60]  LR=8.36e-05  KL_weight=0.0375\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0350    0.0319\n  Dirichlet L       0.0338    0.0307\n  KL term           0.0331    0.0320\n  Dice              0.8822    0.9152\n  IoU               0.8256    0.8615\n  Precision         0.9039    0.9213\n  Recall            0.8823    0.9334\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9169  IoU=0.8655  Prec=0.9154  Rec=0.9424\n    CVC-ClinicDB    n=  61  Dice=0.9237  IoU=0.8725  Prec=0.9184  Rec=0.9490\n    Kvasir-SEG      n= 100  Dice=0.9109  IoU=0.8543  Prec=0.9301  Rec=0.9182\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 17/60]  LR=8.17e-05  KL_weight=0.0400\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0361    0.0287\n  Dirichlet L       0.0346    0.0276\n  KL term           0.0357    0.0282\n  Dice              0.8882    0.9223\n  IoU               0.8307    0.8725\n  Precision         0.9115    0.9503\n  Recall            0.8878    0.9100\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9267  IoU=0.8772  Prec=0.9548  Rec=0.9072\n    CVC-ClinicDB    n=  61  Dice=0.9410  IoU=0.8932  Prec=0.9593  Rec=0.9299\n    Kvasir-SEG      n= 100  Dice=0.9112  IoU=0.8609  Prec=0.9426  Rec=0.9042\n  ✓ Saved best (dice=0.9223)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 18/60]  LR=7.96e-05  KL_weight=0.0425\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0343    0.0350\n  Dirichlet L       0.0328    0.0336\n  KL term           0.0343    0.0341\n  Dice              0.8864    0.9110\n  IoU               0.8289    0.8536\n  Precision         0.9116    0.9418\n  Recall            0.8881    0.9020\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9188  IoU=0.8633  Prec=0.9662  Rec=0.8910\n    CVC-ClinicDB    n=  61  Dice=0.9358  IoU=0.8849  Prec=0.9612  Rec=0.9201\n    Kvasir-SEG      n= 100  Dice=0.8918  IoU=0.8299  Prec=0.9094  Rec=0.9043\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 19/60]  LR=7.75e-05  KL_weight=0.0450\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0318    0.0273\n  Dirichlet L       0.0304    0.0261\n  KL term           0.0318    0.0267\n  Dice              0.8920    0.9280\n  IoU               0.8356    0.8786\n  Precision         0.9136    0.9392\n  Recall            0.8927    0.9336\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9339  IoU=0.8872  Prec=0.9375  Rec=0.9426\n    CVC-ClinicDB    n=  61  Dice=0.9399  IoU=0.8913  Prec=0.9381  Rec=0.9488\n    Kvasir-SEG      n= 100  Dice=0.9195  IoU=0.8678  Prec=0.9429  Rec=0.9199\n  ✓ Saved best (dice=0.9280)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 20/60]  LR=7.52e-05  KL_weight=0.0475\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0307    0.0306\n  Dirichlet L       0.0292    0.0292\n  KL term           0.0306    0.0293\n  Dice              0.8989    0.9247\n  IoU               0.8431    0.8740\n  Precision         0.9191    0.9355\n  Recall            0.8999    0.9330\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9319  IoU=0.8843  Prec=0.9297  Rec=0.9500\n    CVC-ClinicDB    n=  61  Dice=0.9269  IoU=0.8789  Prec=0.9387  Rec=0.9353\n    Kvasir-SEG      n= 100  Dice=0.9183  IoU=0.8638  Prec=0.9400  Rec=0.9176\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 21/60]  LR=7.30e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0302    0.0264\n  Dirichlet L       0.0287    0.0251\n  KL term           0.0303    0.0264\n  Dice              0.8918    0.9292\n  IoU               0.8366    0.8798\n  Precision         0.9166    0.9458\n  Recall            0.8898    0.9291\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9311  IoU=0.8831  Prec=0.9372  Rec=0.9414\n    CVC-ClinicDB    n=  61  Dice=0.9437  IoU=0.8975  Prec=0.9553  Rec=0.9384\n    Kvasir-SEG      n= 100  Dice=0.9210  IoU=0.8691  Prec=0.9496  Rec=0.9142\n  ✓ Saved best (dice=0.9292)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 22/60]  LR=7.06e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0305    0.0316\n  Dirichlet L       0.0289    0.0300\n  KL term           0.0322    0.0325\n  Dice              0.8964    0.9162\n  IoU               0.8403    0.8639\n  Precision         0.9157    0.9293\n  Recall            0.8972    0.9284\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9286  IoU=0.8801  Prec=0.9417  Rec=0.9324\n    CVC-ClinicDB    n=  61  Dice=0.9426  IoU=0.8959  Prec=0.9491  Rec=0.9423\n    Kvasir-SEG      n= 100  Dice=0.8915  IoU=0.8330  Prec=0.9084  Rec=0.9179\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 23/60]  LR=6.82e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0290    0.0273\n  Dirichlet L       0.0275    0.0258\n  KL term           0.0300    0.0297\n  Dice              0.9004    0.9246\n  IoU               0.8449    0.8722\n  Precision         0.9225    0.9182\n  Recall            0.8992    0.9484\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9322  IoU=0.8843  Prec=0.9216  Rec=0.9573\n    CVC-ClinicDB    n=  61  Dice=0.9383  IoU=0.8883  Prec=0.9264  Rec=0.9575\n    Kvasir-SEG      n= 100  Dice=0.9128  IoU=0.8557  Prec=0.9134  Rec=0.9364\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 24/60]  LR=6.58e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0293    0.0238\n  Dirichlet L       0.0277    0.0226\n  KL term           0.0304    0.0238\n  Dice              0.8998    0.9282\n  IoU               0.8452    0.8812\n  Precision         0.9196    0.9387\n  Recall            0.9008    0.9302\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9325  IoU=0.8894  Prec=0.9389  Rec=0.9349\n    CVC-ClinicDB    n=  61  Dice=0.9292  IoU=0.8831  Prec=0.9339  Rec=0.9315\n    Kvasir-SEG      n= 100  Dice=0.9261  IoU=0.8756  Prec=0.9429  Rec=0.9276\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 25/60]  LR=6.33e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0281    0.0254\n  Dirichlet L       0.0267    0.0241\n  KL term           0.0295    0.0265\n  Dice              0.9014    0.9305\n  IoU               0.8477    0.8815\n  Precision         0.9205    0.9434\n  Recall            0.9022    0.9326\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9400  IoU=0.8949  Prec=0.9407  Rec=0.9493\n    CVC-ClinicDB    n=  61  Dice=0.9393  IoU=0.8911  Prec=0.9427  Rec=0.9434\n    Kvasir-SEG      n= 100  Dice=0.9182  IoU=0.8657  Prec=0.9469  Rec=0.9129\n  ✓ Saved best (dice=0.9305)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 26/60]  LR=6.08e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0257    0.0273\n  Dirichlet L       0.0243    0.0258\n  KL term           0.0274    0.0291\n  Dice              0.9046    0.9229\n  IoU               0.8529    0.8698\n  Precision         0.9206    0.9323\n  Recall            0.9037    0.9314\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9308  IoU=0.8829  Prec=0.9292  Rec=0.9483\n    CVC-ClinicDB    n=  61  Dice=0.9397  IoU=0.8914  Prec=0.9446  Rec=0.9411\n    Kvasir-SEG      n= 100  Dice=0.9069  IoU=0.8465  Prec=0.9287  Rec=0.9111\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 27/60]  LR=5.82e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0251    0.0243\n  Dirichlet L       0.0238    0.0230\n  KL term           0.0268    0.0254\n  Dice              0.9093    0.9312\n  IoU               0.8570    0.8823\n  Precision         0.9253    0.9571\n  Recall            0.9110    0.9211\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9442  IoU=0.8999  Prec=0.9628  Rec=0.9326\n    CVC-ClinicDB    n=  61  Dice=0.9423  IoU=0.8956  Prec=0.9594  Rec=0.9324\n    Kvasir-SEG      n= 100  Dice=0.9153  IoU=0.8614  Prec=0.9518  Rec=0.9059\n  ✓ Saved best (dice=0.9312)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 28/60]  LR=5.57e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0251    0.0249\n  Dirichlet L       0.0238    0.0235\n  KL term           0.0271    0.0273\n  Dice              0.9065    0.9313\n  IoU               0.8550    0.8817\n  Precision         0.9236    0.9512\n  Recall            0.9064    0.9258\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9466  IoU=0.9033  Prec=0.9582  Rec=0.9413\n    CVC-ClinicDB    n=  61  Dice=0.9430  IoU=0.8967  Prec=0.9487  Rec=0.9440\n    Kvasir-SEG      n= 100  Dice=0.9118  IoU=0.8549  Prec=0.9469  Rec=0.9026\n  ✓ Saved best (dice=0.9313)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 29/60]  LR=5.31e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0241    0.0256\n  Dirichlet L       0.0228    0.0242\n  KL term           0.0261    0.0287\n  Dice              0.9060    0.9306\n  IoU               0.8555    0.8811\n  Precision         0.9245    0.9413\n  Recall            0.9035    0.9349\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9482  IoU=0.9063  Prec=0.9514  Rec=0.9515\n    CVC-ClinicDB    n=  61  Dice=0.9417  IoU=0.8946  Prec=0.9485  Rec=0.9414\n    Kvasir-SEG      n= 100  Dice=0.9102  IoU=0.8524  Prec=0.9284  Rec=0.9181\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 30/60]  LR=5.05e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0227    0.0251\n  Dirichlet L       0.0215    0.0237\n  KL term           0.0246    0.0279\n  Dice              0.9131    0.9293\n  IoU               0.8620    0.8809\n  Precision         0.9297    0.9413\n  Recall            0.9109    0.9345\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9379  IoU=0.8937  Prec=0.9399  Rec=0.9491\n    CVC-ClinicDB    n=  61  Dice=0.9461  IoU=0.9020  Prec=0.9555  Rec=0.9425\n    Kvasir-SEG      n= 100  Dice=0.9129  IoU=0.8587  Prec=0.9345  Rec=0.9188\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 31/60]  LR=4.79e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0239    0.0244\n  Dirichlet L       0.0226    0.0230\n  KL term           0.0262    0.0271\n  Dice              0.9134    0.9295\n  IoU               0.8633    0.8795\n  Precision         0.9312    0.9375\n  Recall            0.9121    0.9370\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9420  IoU=0.8963  Prec=0.9463  Rec=0.9447\n    CVC-ClinicDB    n=  61  Dice=0.9455  IoU=0.9006  Prec=0.9471  Rec=0.9498\n    Kvasir-SEG      n= 100  Dice=0.9098  IoU=0.8537  Prec=0.9245  Rec=0.9240\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 32/60]  LR=4.53e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0224    0.0245\n  Dirichlet L       0.0212    0.0232\n  KL term           0.0247    0.0275\n  Dice              0.9115    0.9289\n  IoU               0.8613    0.8791\n  Precision         0.9292    0.9437\n  Recall            0.9099    0.9303\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9424  IoU=0.8965  Prec=0.9365  Rec=0.9557\n    CVC-ClinicDB    n=  61  Dice=0.9362  IoU=0.8897  Prec=0.9441  Rec=0.9409\n    Kvasir-SEG      n= 100  Dice=0.9133  IoU=0.8587  Prec=0.9506  Rec=0.9026\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 33/60]  LR=4.28e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0215    0.0263\n  Dirichlet L       0.0203    0.0249\n  KL term           0.0236    0.0290\n  Dice              0.9125    0.9241\n  IoU               0.8640    0.8728\n  Precision         0.9267    0.9206\n  Recall            0.9111    0.9470\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9369  IoU=0.8902  Prec=0.9267  Rec=0.9588\n    CVC-ClinicDB    n=  61  Dice=0.9440  IoU=0.8980  Prec=0.9376  Rec=0.9566\n    Kvasir-SEG      n= 100  Dice=0.9021  IoU=0.8441  Prec=0.9062  Rec=0.9318\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 34/60]  LR=4.02e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0227    0.0268\n  Dirichlet L       0.0214    0.0252\n  KL term           0.0251    0.0313\n  Dice              0.9170    0.9278\n  IoU               0.8684    0.8792\n  Precision         0.9327    0.9473\n  Recall            0.9153    0.9275\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9471  IoU=0.9051  Prec=0.9427  Rec=0.9585\n    CVC-ClinicDB    n=  61  Dice=0.9453  IoU=0.9002  Prec=0.9481  Rec=0.9483\n    Kvasir-SEG      n= 100  Dice=0.8997  IoU=0.8432  Prec=0.9519  Rec=0.8865\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 35/60]  LR=3.77e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0212    0.0252\n  Dirichlet L       0.0200    0.0237\n  KL term           0.0241    0.0292\n  Dice              0.9167    0.9254\n  IoU               0.8682    0.8769\n  Precision         0.9304    0.9447\n  Recall            0.9184    0.9242\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9364  IoU=0.8963  Prec=0.9492  Rec=0.9343\n    CVC-ClinicDB    n=  61  Dice=0.9458  IoU=0.9014  Prec=0.9591  Rec=0.9383\n    Kvasir-SEG      n= 100  Dice=0.9054  IoU=0.8468  Prec=0.9324  Rec=0.9090\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 36/60]  LR=3.52e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0210    0.0230\n  Dirichlet L       0.0198    0.0216\n  KL term           0.0239    0.0267\n  Dice              0.9180    0.9364\n  IoU               0.8701    0.8899\n  Precision         0.9319    0.9461\n  Recall            0.9171    0.9387\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9477  IoU=0.9066  Prec=0.9442  Rec=0.9571\n    CVC-ClinicDB    n=  61  Dice=0.9461  IoU=0.9014  Prec=0.9442  Rec=0.9535\n    Kvasir-SEG      n= 100  Dice=0.9214  IoU=0.8691  Prec=0.9502  Rec=0.9142\n  ✓ Saved best (dice=0.9364)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 37/60]  LR=3.28e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0199    0.0219\n  Dirichlet L       0.0188    0.0207\n  KL term           0.0224    0.0248\n  Dice              0.9174    0.9357\n  IoU               0.8703    0.8898\n  Precision         0.9332    0.9438\n  Recall            0.9155    0.9413\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9446  IoU=0.9019  Prec=0.9375  Rec=0.9604\n    CVC-ClinicDB    n=  61  Dice=0.9475  IoU=0.9041  Prec=0.9454  Rec=0.9548\n    Kvasir-SEG      n= 100  Dice=0.9231  IoU=0.8731  Prec=0.9499  Rec=0.9182\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 38/60]  LR=3.04e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0186    0.0244\n  Dirichlet L       0.0175    0.0230\n  KL term           0.0211    0.0282\n  Dice              0.9242    0.9271\n  IoU               0.8774    0.8798\n  Precision         0.9380    0.9384\n  Recall            0.9219    0.9369\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9285  IoU=0.8855  Prec=0.9266  Rec=0.9549\n    CVC-ClinicDB    n=  61  Dice=0.9477  IoU=0.9045  Prec=0.9524  Rec=0.9485\n    Kvasir-SEG      n= 100  Dice=0.9169  IoU=0.8633  Prec=0.9420  Rec=0.9164\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 39/60]  LR=2.80e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0202    0.0231\n  Dirichlet L       0.0190    0.0218\n  KL term           0.0231    0.0266\n  Dice              0.9179    0.9318\n  IoU               0.8705    0.8844\n  Precision         0.9331    0.9339\n  Recall            0.9170    0.9461\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9354  IoU=0.8920  Prec=0.9255  Rec=0.9626\n    CVC-ClinicDB    n=  61  Dice=0.9462  IoU=0.9016  Prec=0.9427  Rec=0.9551\n    Kvasir-SEG      n= 100  Dice=0.9221  IoU=0.8697  Prec=0.9373  Rec=0.9278\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 40/60]  LR=2.58e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0178    0.0241\n  Dirichlet L       0.0168    0.0227\n  KL term           0.0203    0.0281\n  Dice              0.9253    0.9330\n  IoU               0.8794    0.8863\n  Precision         0.9407    0.9441\n  Recall            0.9229    0.9380\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9466  IoU=0.9046  Prec=0.9443  Rec=0.9561\n    CVC-ClinicDB    n=  61  Dice=0.9498  IoU=0.9073  Prec=0.9516  Rec=0.9520\n    Kvasir-SEG      n= 100  Dice=0.9122  IoU=0.8590  Prec=0.9400  Rec=0.9148\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 41/60]  LR=2.35e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0176    0.0222\n  Dirichlet L       0.0166    0.0209\n  KL term           0.0200    0.0256\n  Dice              0.9273    0.9390\n  IoU               0.8823    0.8936\n  Precision         0.9395    0.9503\n  Recall            0.9240    0.9393\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9494  IoU=0.9087  Prec=0.9524  Rec=0.9528\n    CVC-ClinicDB    n=  61  Dice=0.9512  IoU=0.9095  Prec=0.9541  Rec=0.9516\n    Kvasir-SEG      n= 100  Dice=0.9236  IoU=0.8723  Prec=0.9467  Rec=0.9215\n  ✓ Saved best (dice=0.9390)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 42/60]  LR=2.14e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0186    0.0226\n  Dirichlet L       0.0175    0.0212\n  KL term           0.0212    0.0274\n  Dice              0.9267    0.9378\n  IoU               0.8813    0.8915\n  Precision         0.9385    0.9430\n  Recall            0.9262    0.9444\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9475  IoU=0.9058  Prec=0.9478  Rec=0.9547\n    CVC-ClinicDB    n=  61  Dice=0.9519  IoU=0.9106  Prec=0.9511  Rec=0.9559\n    Kvasir-SEG      n= 100  Dice=0.9215  IoU=0.8685  Prec=0.9341  Rec=0.9296\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 43/60]  LR=1.93e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0173    0.0223\n  Dirichlet L       0.0164    0.0210\n  KL term           0.0194    0.0263\n  Dice              0.9259    0.9379\n  IoU               0.8810    0.8923\n  Precision         0.9374    0.9536\n  Recall            0.9236    0.9346\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9512  IoU=0.9116  Prec=0.9549  Rec=0.9531\n    CVC-ClinicDB    n=  61  Dice=0.9530  IoU=0.9124  Prec=0.9495  Rec=0.9591\n    Kvasir-SEG      n= 100  Dice=0.9175  IoU=0.8638  Prec=0.9554  Rec=0.9041\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 44/60]  LR=1.74e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0187    0.0216\n  Dirichlet L       0.0176    0.0203\n  KL term           0.0216    0.0252\n  Dice              0.9265    0.9388\n  IoU               0.8809    0.8938\n  Precision         0.9385    0.9512\n  Recall            0.9250    0.9383\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9526  IoU=0.9141  Prec=0.9525  Rec=0.9582\n    CVC-ClinicDB    n=  61  Dice=0.9528  IoU=0.9121  Prec=0.9535  Rec=0.9549\n    Kvasir-SEG      n= 100  Dice=0.9192  IoU=0.8659  Prec=0.9490  Rec=0.9120\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 45/60]  LR=1.55e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0166    0.0204\n  Dirichlet L       0.0156    0.0192\n  KL term           0.0185    0.0237\n  Dice              0.9280    0.9402\n  IoU               0.8829    0.8956\n  Precision         0.9397    0.9504\n  Recall            0.9254    0.9412\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9514  IoU=0.9123  Prec=0.9526  Rec=0.9564\n    CVC-ClinicDB    n=  61  Dice=0.9532  IoU=0.9126  Prec=0.9503  Rec=0.9585\n    Kvasir-SEG      n= 100  Dice=0.9233  IoU=0.8717  Prec=0.9489  Rec=0.9183\n  ✓ Saved best (dice=0.9402)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 46/60]  LR=1.37e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0165    0.0224\n  Dirichlet L       0.0156    0.0211\n  KL term           0.0187    0.0265\n  Dice              0.9321    0.9384\n  IoU               0.8878    0.8927\n  Precision         0.9434    0.9420\n  Recall            0.9300    0.9465\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9515  IoU=0.9123  Prec=0.9498  Rec=0.9590\n    CVC-ClinicDB    n=  61  Dice=0.9534  IoU=0.9129  Prec=0.9495  Rec=0.9597\n    Kvasir-SEG      n= 100  Dice=0.9181  IoU=0.8636  Prec=0.9302  Rec=0.9286\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 47/60]  LR=1.20e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0166    0.0207\n  Dirichlet L       0.0156    0.0195\n  KL term           0.0190    0.0243\n  Dice              0.9288    0.9405\n  IoU               0.8849    0.8962\n  Precision         0.9401    0.9504\n  Recall            0.9272    0.9419\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9527  IoU=0.9141  Prec=0.9538  Rec=0.9569\n    CVC-ClinicDB    n=  61  Dice=0.9546  IoU=0.9152  Prec=0.9548  Rec=0.9568\n    Kvasir-SEG      n= 100  Dice=0.9215  IoU=0.8692  Prec=0.9446  Rec=0.9203\n  ✓ Saved best (dice=0.9405)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 48/60]  LR=1.05e-05  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0162    0.0211\n  Dirichlet L       0.0153    0.0199\n  KL term           0.0184    0.0243\n  Dice              0.9293    0.9404\n  IoU               0.8856    0.8961\n  Precision         0.9405    0.9472\n  Recall            0.9276    0.9449\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9511  IoU=0.9111  Prec=0.9446  Rec=0.9633\n    CVC-ClinicDB    n=  61  Dice=0.9536  IoU=0.9135  Prec=0.9515  Rec=0.9583\n    Kvasir-SEG      n= 100  Dice=0.9236  IoU=0.8732  Prec=0.9478  Rec=0.9210\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 49/60]  LR=8.99e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0153    0.0212\n  Dirichlet L       0.0144    0.0200\n  KL term           0.0173    0.0242\n  Dice              0.9302    0.9396\n  IoU               0.8871    0.8955\n  Precision         0.9406    0.9424\n  Recall            0.9268    0.9492\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9515  IoU=0.9127  Prec=0.9450  Rec=0.9647\n    CVC-ClinicDB    n=  61  Dice=0.9540  IoU=0.9138  Prec=0.9490  Rec=0.9611\n    Kvasir-SEG      n= 100  Dice=0.9212  IoU=0.8702  Prec=0.9362  Rec=0.9298\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 50/60]  LR=7.63e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0154    0.0200\n  Dirichlet L       0.0145    0.0188\n  KL term           0.0175    0.0231\n  Dice              0.9331    0.9407\n  IoU               0.8896    0.8968\n  Precision         0.9443    0.9498\n  Recall            0.9309    0.9431\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9499  IoU=0.9103  Prec=0.9489  Rec=0.9581\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9148  Prec=0.9533  Rec=0.9577\n    Kvasir-SEG      n= 100  Dice=0.9254  IoU=0.8754  Prec=0.9489  Rec=0.9223\n  ✓ Saved best (dice=0.9407)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 51/60]  LR=6.40e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0153    0.0203\n  Dirichlet L       0.0144    0.0191\n  KL term           0.0174    0.0236\n  Dice              0.9309    0.9405\n  IoU               0.8883    0.8963\n  Precision         0.9425    0.9513\n  Recall            0.9274    0.9411\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9504  IoU=0.9108  Prec=0.9502  Rec=0.9575\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9147  Prec=0.9540  Rec=0.9570\n    Kvasir-SEG      n= 100  Dice=0.9245  IoU=0.8737  Prec=0.9512  Rec=0.9184\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 52/60]  LR=5.28e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0158    0.0208\n  Dirichlet L       0.0149    0.0196\n  KL term           0.0182    0.0246\n  Dice              0.9311    0.9409\n  IoU               0.8878    0.8965\n  Precision         0.9432    0.9487\n  Recall            0.9283    0.9439\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9529  IoU=0.9145  Prec=0.9490  Rec=0.9624\n    CVC-ClinicDB    n=  61  Dice=0.9535  IoU=0.9133  Prec=0.9492  Rec=0.9603\n    Kvasir-SEG      n= 100  Dice=0.9232  IoU=0.8711  Prec=0.9482  Rec=0.9185\n  ✓ Saved best (dice=0.9409)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 53/60]  LR=4.29e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0165    0.0207\n  Dirichlet L       0.0155    0.0195\n  KL term           0.0192    0.0243\n  Dice              0.9295    0.9409\n  IoU               0.8858    0.8968\n  Precision         0.9417    0.9526\n  Recall            0.9275    0.9402\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9534  IoU=0.9154  Prec=0.9528  Rec=0.9595\n    CVC-ClinicDB    n=  61  Dice=0.9542  IoU=0.9145  Prec=0.9523  Rec=0.9585\n    Kvasir-SEG      n= 100  Dice=0.9227  IoU=0.8707  Prec=0.9530  Rec=0.9134\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 54/60]  LR=3.42e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0158    0.0207\n  Dirichlet L       0.0149    0.0195\n  KL term           0.0182    0.0248\n  Dice              0.9342    0.9407\n  IoU               0.8908    0.8967\n  Precision         0.9442    0.9505\n  Recall            0.9328    0.9423\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9530  IoU=0.9148  Prec=0.9517  Rec=0.9600\n    CVC-ClinicDB    n=  61  Dice=0.9541  IoU=0.9143  Prec=0.9515  Rec=0.9590\n    Kvasir-SEG      n= 100  Dice=0.9225  IoU=0.8710  Prec=0.9491  Rec=0.9176\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 55/60]  LR=2.69e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0162    0.0205\n  Dirichlet L       0.0153    0.0193\n  KL term           0.0187    0.0239\n  Dice              0.9315    0.9409\n  IoU               0.8881    0.8969\n  Precision         0.9427    0.9507\n  Recall            0.9296    0.9423\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9525  IoU=0.9140  Prec=0.9515  Rec=0.9595\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9148  Prec=0.9527  Rec=0.9583\n    Kvasir-SEG      n= 100  Dice=0.9233  IoU=0.8720  Prec=0.9491  Rec=0.9187\n  ✓ Saved best (dice=0.9409)\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 56/60]  LR=2.08e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0168    0.0204\n  Dirichlet L       0.0158    0.0192\n  KL term           0.0193    0.0240\n  Dice              0.9350    0.9402\n  IoU               0.8911    0.8961\n  Precision         0.9453    0.9482\n  Recall            0.9345    0.9440\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9506  IoU=0.9117  Prec=0.9491  Rec=0.9595\n    CVC-ClinicDB    n=  61  Dice=0.9544  IoU=0.9148  Prec=0.9524  Rec=0.9587\n    Kvasir-SEG      n= 100  Dice=0.9232  IoU=0.8719  Prec=0.9451  Rec=0.9226\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 57/60]  LR=1.61e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0154    0.0206\n  Dirichlet L       0.0145    0.0193\n  KL term           0.0177    0.0246\n  Dice              0.9331    0.9409\n  IoU               0.8907    0.8970\n  Precision         0.9425    0.9512\n  Recall            0.9321    0.9419\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9525  IoU=0.9139  Prec=0.9518  Rec=0.9591\n    CVC-ClinicDB    n=  61  Dice=0.9547  IoU=0.9154  Prec=0.9537  Rec=0.9580\n    Kvasir-SEG      n= 100  Dice=0.9230  IoU=0.8719  Prec=0.9495  Rec=0.9182\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 58/60]  LR=1.27e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0157    0.0211\n  Dirichlet L       0.0147    0.0198\n  KL term           0.0183    0.0249\n  Dice              0.9347    0.9403\n  IoU               0.8920    0.8962\n  Precision         0.9432    0.9530\n  Recall            0.9338    0.9394\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9536  IoU=0.9156  Prec=0.9541  Rec=0.9585\n    CVC-ClinicDB    n=  61  Dice=0.9547  IoU=0.9153  Prec=0.9546  Rec=0.9570\n    Kvasir-SEG      n= 100  Dice=0.9204  IoU=0.8683  Prec=0.9513  Rec=0.9128\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 59/60]  LR=1.07e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0152    0.0208\n  Dirichlet L       0.0143    0.0196\n  KL term           0.0174    0.0248\n  Dice              0.9355    0.9406\n  IoU               0.8924    0.8966\n  Precision         0.9455    0.9498\n  Recall            0.9340    0.9429\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9530  IoU=0.9147  Prec=0.9510  Rec=0.9606\n    CVC-ClinicDB    n=  61  Dice=0.9545  IoU=0.9150  Prec=0.9517  Rec=0.9596\n    Kvasir-SEG      n= 100  Dice=0.9219  IoU=0.8703  Prec=0.9477  Rec=0.9182\n","output_type":"stream"},{"name":"stderr","text":"                                                                                                    \r","output_type":"stream"},{"name":"stdout","text":"\nEpoch [ 60/60]  LR=1.00e-06  KL_weight=0.0500\n  GROSS METRIC       Train       Val\n  ------------------------------------\n  Total Loss        0.0153    0.0209\n  Dirichlet L       0.0144    0.0197\n  KL term           0.0176    0.0246\n  Dice              0.9344    0.9405\n  IoU               0.8912    0.8965\n  Precision         0.9453    0.9494\n  Recall            0.9325    0.9432\n  ------------------------------------\n  VAL PER-DATASET:\n    BKAI-IGH        n= 100  Dice=0.9529  IoU=0.9146  Prec=0.9505  Rec=0.9611\n    CVC-ClinicDB    n=  61  Dice=0.9546  IoU=0.9151  Prec=0.9520  Rec=0.9594\n    Kvasir-SEG      n= 100  Dice=0.9217  IoU=0.8700  Prec=0.9471  Rec=0.9186\n  Loaded best epoch 54 (dice=0.9409)\n\n============================================================\nFINAL EVALUATION — pvt_v2_b3_hybrid_mcdropout_evidential\n============================================================\n","output_type":"stream"},{"name":"stderr","text":"                                                                      \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  VALIDATION SET\n============================================================\n  Dice     : 0.9409\n  IoU      : 0.8969\n  Precision: 0.9507\n  Recall   : 0.9423\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9525  IoU=0.9140  Prec=0.9515  Rec=0.9595\n  [CVC-ClinicDB] n=61  Dice=0.9544  IoU=0.9148  Prec=0.9527  Rec=0.9583\n  [Kvasir-SEG] n=100  Dice=0.9233  IoU=0.8720  Prec=0.9491  Rec=0.9187\n","output_type":"stream"},{"name":"stderr","text":"                                                                            \r","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  IN-DISTRIBUTION TEST\n============================================================\n  Dice     : 0.9372\n  IoU      : 0.8918\n  Precision: 0.9483\n  Recall   : 0.9392\n\n  PER-DATASET BREAKDOWN\n  [BKAI-IGH] n=100  Dice=0.9412  IoU=0.8970  Prec=0.9426  Rec=0.9504\n  [CVC-ClinicDB] n=62  Dice=0.9318  IoU=0.8862  Prec=0.9508  Rec=0.9313\n  [Kvasir-SEG] n=100  Dice=0.9368  IoU=0.8904  Prec=0.9533  Rec=0.9323\n","output_type":"stream"},{"name":"stderr","text":"                                                                          ","output_type":"stream"},{"name":"stdout","text":"\n============================================================\n  BENCHMARK (UNSEEN)\n============================================================\n  Dice     : 0.8222\n  IoU      : 0.7492\n  Precision: 0.8531\n  Recall   : 0.8326\n\n  PER-DATASET BREAKDOWN\n  [CVC-ColonDB] n=380  Dice=0.7960  IoU=0.7212  Prec=0.8474  Rec=0.7950\n  [ETIS-Larib] n=196  Dice=0.8411  IoU=0.7685  Prec=0.8495  Rec=0.8669\n  [CVC-300] n=60  Dice=0.9254  IoU=0.8662  Prec=0.9073  Rec=0.9513\n\n============================================================\nREGRESSION GATE CHECK — Hybrid model\n  Original PVTv2-b3 benchmark Dice : 0.8444\n  This checkpoint benchmark Dice   : 0.8222\n  Delta                            : -0.0222\n  Val Dice                         : 0.9409\n  Generalization gap (val - bench) : 0.1187\n  MC Dropout checkpoint's gap (ref): 0.1170\n\n  ⚠ WARNING: outside acceptable range. Given this combines dropout + evidential, if this fails while the standalone evidential v2 passes, the interaction between dropout and the KL dynamics may need its own adjustment (e.g. reduce dropout_p to 0.15, or retune KL schedule again specifically for this combined model).\n============================================================\n\n  Results appended to: checkpoints_uncertainty/uncertainty_checkpoint_results.csv\n","output_type":"stream"},{"name":"stderr","text":"\r","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"!zip -rq pvt_hybrid_mcd_evi.zip /kaggle/working/checkpoints_uncertainty/pvt_v2_b3_hybrid_mcdropout_evidential/best_model.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T08:57:02.282871Z","iopub.execute_input":"2026-08-26T08:57:02.283302Z","iopub.status.idle":"2026-08-26T08:57:29.278384Z","shell.execute_reply.started":"2026-08-26T08:57:02.283267Z","shell.execute_reply":"2026-08-26T08:57:29.277584Z"}},"outputs":[],"execution_count":3},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink('pvt_hybrid_mcd_evi.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T08:57:34.125962Z","iopub.execute_input":"2026-08-26T08:57:34.12624Z","iopub.status.idle":"2026-08-26T08:57:34.133018Z","shell.execute_reply.started":"2026-08-26T08:57:34.126213Z","shell.execute_reply":"2026-08-26T08:57:34.132318Z"}},"outputs":[{"execution_count":4,"output_type":"execute_result","data":{"text/plain":"/kaggle/working/pvt_hybrid_mcd_evi.zip","text/html":"<a href='pvt_hybrid_mcd_evi.zip' target='_blank'>pvt_hybrid_mcd_evi.zip</a><br>"},"metadata":{}}],"execution_count":4},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#evaluation of the final model including the mc dropout and the evidential methods","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nPolypSeg — Hybrid Model (PVTv2-b3 + Dropout + Evidential) FULL EVALUATION\n==============================================================================\nPurpose: Comprehensive evaluation of the final hybrid checkpoint, going\nbeyond gross Dice to:\n\n  1. Standard eval (val / indist_test / benchmark_test) — gross + per-dataset\n     breakdown, exactly like prior scripts, as a sanity check that the\n     checkpoint is healthy before trusting anything downstream.\n\n  2. PER-SAMPLE metrics (Dice, IoU, Precision, Recall) for every single\n     image in every split — not just the aggregate. This is the raw\n     material needed to find silent failures and later train a no-GT\n     quality estimator.\n\n  3. ALL FOUR uncertainty signals extracted per sample:\n       - Single / Softmax   : 1 - max(p_hat)          [1 pass, dropout OFF]\n       - Evidential         : C / alpha_0              [1 pass, dropout OFF]\n       - MC Dropout         : var(p_hat) across T      [T passes, dropout ON]\n       - TTA                : var(p_hat) across views  [4 passes, dropout OFF]\n     Both per-pixel maps (summarized) and per-image scalars are recorded.\n\n  4. SILENT FAILURE DETECTION: cases where true Dice is low (segmentation\n     is bad) but the model's own uncertainty signals stayed low too (model\n     was confidently wrong). These are the dangerous cases for any\n     downstream no-GT quality estimator or clinical deployment, because\n     they are exactly the failures uncertainty-based screening would MISS.\n\n     Flagged using a simple, transparent quadrant rule per signal:\n       - failure_dice_thresh   : Dice below this = \"bad segmentation\"\n       - low_uncertainty_pctile: uncertainty below this percentile (computed\n         per-signal, per-split, over ALL samples in that split) = \"model\n         looked confident\"\n     A sample is a \"silent failure\" for a given signal if BOTH hold.\n\n  5. Everything is saved to CSV (per-sample, all signals, flags) plus a\n     printed summary, so this feeds directly into:\n       - Goal 1: correlation / sparsification analysis (does uncertainty\n         track error?)\n       - Goal 2: no-GT quality estimator training data\n       - Goal 3: severe-failure classifier training data\n\nRun this in the same Kaggle environment/session where val_split.csv,\nindist_test.csv, benchmark_test.csv already exist, with the hybrid\ncheckpoint available at CHECKPOINT_PATH below.\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nEVAL_CFG = {\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n\n    \"img_size\"    : 352,\n    \"mean\"        : [0.485, 0.456, 0.406],\n    \"std\"         : [0.229, 0.224, 0.225],\n    \"batch_size\"  : 1,     # per-sample metrics -> batch_size=1 keeps\n                           # bookkeeping trivial and avoids any risk of\n                           # cross-sample leakage in the per-sample loop.\n                           # (Model/GPU throughput doesn't matter here —\n                           # this is an analysis pass, not training.)\n    \"num_workers\" : 2,\n    \"dec_channels\": 64,\n    \"num_classes\" : 2,\n    \"dropout_p\"   : 0.2,   # must match the trained checkpoint's decoder\n\n    # Uncertainty extraction settings\n    \"mc_passes_T\" : 4,     # match Checkpoint A / hybrid training-time T\n\n    # Silent failure detection thresholds\n    \"failure_dice_thresh\"    : 0.5,   # Dice below this = bad segmentation\n    \"low_uncertainty_pctile\" : 25,    # uncertainty below this percentile\n                                       # (within its own split) = \"model\n                                       # looked confident\" for that signal\n\n    \"checkpoint_path\": (\n        \"/kaggle/input/models/borshon10/pvt-mcd-effi/pytorch/default/1/\"\n        \"kaggle/working/checkpoints_uncertainty/\"\n        \"pvt_v2_b3_hybrid_mcdropout_evidential/best_model.pth\"\n    ),\n\n    \"output_dir\": \"/kaggle/working/hybrid_eval\",\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\n\n# ============================================================\n# DATA (identical to prior scripts)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=EVAL_CFG[\"mean\"], std=EVAL_CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(image.transpose(2,0,1)).float()/255.\n            mask_t  = torch.from_numpy(mask).float().unsqueeze(0)/255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_eval_loaders(cfg):\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    sz, nw, bs = cfg['img_size'], cfg['num_workers'], cfg['batch_size']\n    tf = get_val_transform(sz)\n\n    val_loader = DataLoader(PolypSegDataset(val_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(PolypSegDataset(indist_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(PolypSegDataset(benchmark_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n\n    print(f\"Loaded eval sets — Val:{len(val_df)}  Indist:{len(indist_df)}  \"\n          f\"Bench:{len(benchmark_df)}\")\n\n    return {\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# MODEL — identical to train_pvt_hybrid.py (re-declared so this\n# script is standalone and doesn't depend on importing training code)\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    def __init__(self, backbone, source='timm', needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source\n        self.needs_permute = needs_permute\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n        return tuple(feats)\n\n\ndef build_pvt_encoder(pretrained=False, img_size=352):\n    m = timm.create_model(\n        \"pvt_v2_b3\", pretrained=pretrained,\n        features_only=True, out_indices=(0, 1, 2, 3))\n    wrapper = EncoderWrapper(m, source='timm')\n\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, img_size, img_size)\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n\n    return wrapper, enc_channels\n\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride, padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass HybridDropoutEvidentialDecoder(nn.Module):\n    def __init__(self, enc_channels, dec_channels=64,\n                 dropout_p=0.2, num_classes=2):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n        self.num_classes = num_classes\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.drop_lat4  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat3  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat2  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat1  = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse3 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse2 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse1 = nn.Dropout2d(p=dropout_p)\n\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, num_classes, 1),\n        )\n\n        self.dropout_p = dropout_p\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.drop_lat4(self.lat4(f4))\n        p3 = self.drop_lat3(self.lat3(f3))\n        p2 = self.drop_lat2(self.lat2(f2))\n        p1 = self.drop_lat1(self.lat1(f1))\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.drop_fuse3(self.fuse3(torch.cat([up(p4, p3), p3], dim=1)))\n        p2f = self.drop_fuse2(self.fuse2(torch.cat([up(p3f, p2), p2], dim=1)))\n        p1f = self.drop_fuse1(self.fuse1(torch.cat([up(p2f, p1), p1], dim=1)))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        raw_output = self.seg_head(out)\n        evidence = F.softplus(raw_output)\n        return evidence\n\n\nclass PolypSegHybrid(nn.Module):\n    def __init__(self, pretrained=False, dec_channels=64,\n                 dropout_p=0.2, num_classes=2, img_size=352):\n        super().__init__()\n        self.encoder, enc_channels = build_pvt_encoder(pretrained, img_size)\n        self.decoder = HybridDropoutEvidentialDecoder(\n            enc_channels, dec_channels, dropout_p, num_classes)\n        self.encoder_name = \"pvt_v2_b3_hybrid_mcdropout_evidential\"\n        self.enc_channels = enc_channels\n        self.num_classes = num_classes\n        self.dropout_p = dropout_p\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        evidence = self.decoder(features, (H, W))\n        return {'evidence': evidence}\n\n    def enable_mc_dropout(self):\n        self.eval()\n        for m in self.modules():\n            if isinstance(m, nn.Dropout2d):\n                m.train()\n\n\ndef load_hybrid_checkpoint(ckpt_path, cfg, device):\n    if not os.path.exists(ckpt_path):\n        raise FileNotFoundError(f\"Checkpoint not found: {ckpt_path}\")\n\n    model = PolypSegHybrid(\n        pretrained=False,\n        dec_channels=cfg['dec_channels'],\n        dropout_p=cfg['dropout_p'],\n        num_classes=cfg['num_classes'],\n        img_size=cfg['img_size'],\n    ).to(device)\n\n    ck = torch.load(ckpt_path, map_location=device, weights_only=False)\n    model.load_state_dict(ck['model'])\n    model.eval()\n\n    print(f\"  \\u2713 Loaded hybrid checkpoint — epoch={ck.get('epoch','?')}, \"\n          f\"train-time best val dice={ck.get('best_dice', float('nan')):.4f}, \"\n          f\"dropout_p={ck.get('dropout_p', cfg['dropout_p'])}, \"\n          f\"type={ck.get('checkpoint_type','?')}\")\n\n    return model\n\n\n# ============================================================\n# EVIDENTIAL HELPERS\n# ============================================================\n\n@torch.no_grad()\ndef evidence_to_phat_uncertainty(evidence):\n    \"\"\"\n    evidence: [B, C, H, W] (C=2: background, polyp)\n    Returns:\n      p_hat_polyp : [B, H, W]  expected probability of polyp class\n      evid_unc    : [B, H, W]  evidential uncertainty C/alpha_0\n    \"\"\"\n    alpha = evidence + 1.0\n    alpha_0 = alpha.sum(dim=1, keepdim=True)\n    p_hat = alpha / alpha_0\n    C = evidence.shape[1]\n    evid_unc = (C / alpha_0).squeeze(1)\n    return p_hat[:, 1, :, :], evid_unc\n\n\n# ============================================================\n# PER-SAMPLE SEGMENTATION METRICS\n# ============================================================\n\ndef compute_sample_metrics(pred_binary, target_binary, eps=1e-6):\n    \"\"\"\n    pred_binary, target_binary: numpy arrays or tensors, same shape,\n    values in {0,1}. Returns dict of scalar metrics for ONE sample.\n    \"\"\"\n    p = pred_binary.reshape(-1).astype(np.float32)\n    t = target_binary.reshape(-1).astype(np.float32)\n    tp = float((p * t).sum())\n    fp = float(p.sum() - tp)\n    fn = float(t.sum() - tp)\n    tn = float(len(p) - tp - fp - fn)\n\n    dice = (2*tp + eps) / (2*tp + fp + fn + eps)\n    iou  = (tp + eps) / (tp + fp + fn + eps)\n    prec = (tp + eps) / (tp + fp + eps)\n    rec  = (tp + eps) / (tp + fn + eps)\n\n    gt_empty   = t.sum() == 0\n    pred_empty = p.sum() == 0\n\n    return dict(\n        dice=dice, iou=iou, precision=prec, recall=rec,\n        tp=tp, fp=fp, fn=fn, tn=tn,\n        gt_polyp_px=float(t.sum()), pred_polyp_px=float(p.sum()),\n        gt_empty=gt_empty, pred_empty=pred_empty,\n        # Special case flags: GT empty but model predicted something\n        # (false positive on a polyp-free image), or GT has a polyp\n        # but model predicted nothing at all (complete miss) --\n        # these read as Dice=0 in the standard formula's edge cases\n        # and deserve separate visibility rather than being buried\n        # inside the aggregate.\n        complete_miss = bool(t.sum() > 0 and p.sum() == 0),\n        false_alarm_on_empty = bool(t.sum() == 0 and p.sum() > 0),\n    )\n\n\n# ============================================================\n# UNCERTAINTY MAP SUMMARIZATION\n# ============================================================\n\ndef summarize_map(u_map, pred_binary):\n    \"\"\"\n    Reduces a per-pixel uncertainty map [H,W] to a handful of scalar\n    summary stats, split by region (whole image / predicted-foreground\n    / predicted-background), since a no-GT quality estimator can only\n    ever use pixel statistics available WITHOUT the ground truth mask.\n    Predicted foreground is a valid conditioning region because it's\n    derived only from the model's own output, not the GT.\n    \"\"\"\n    u = u_map.reshape(-1)\n    pred = pred_binary.reshape(-1).astype(bool)\n\n    out = dict(\n        mean_all=float(u.mean()),\n        std_all=float(u.std()),\n        max_all=float(u.max()),\n        p90_all=float(np.percentile(u, 90)),\n    )\n\n    if pred.sum() > 0:\n        out['mean_fg'] = float(u[pred].mean())\n        out['std_fg']  = float(u[pred].std())\n    else:\n        out['mean_fg'] = float('nan')\n        out['std_fg']  = float('nan')\n\n    if (~pred).sum() > 0:\n        out['mean_bg'] = float(u[~pred].mean())\n    else:\n        out['mean_bg'] = float('nan')\n\n    return out\n\n\n# ============================================================\n# MAIN PER-SAMPLE EVALUATION LOOP\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_split_full(model, loader, device, split_name, cfg):\n    \"\"\"\n    For every sample in the loader:\n      1. Deterministic pass (dropout OFF)  -> prediction, Dice/IoU/etc,\n         Single-method uncertainty, Evidential uncertainty\n      2. T stochastic passes (dropout ON)  -> MC Dropout uncertainty\n      3. 4 TTA passes (dropout OFF)        -> TTA uncertainty\n    All per-sample scalars + summarized uncertainty stats are collected\n    into one row per sample.\n    \"\"\"\n    model.eval()\n    rows = []\n\n    tta_transforms = [\n        lambda x: x,\n        lambda x: torch.flip(x, dims=[3]),\n        lambda x: torch.flip(x, dims=[2]),\n        lambda x: torch.rot90(x, k=1, dims=[2, 3]),\n    ]\n    tta_inverse = [\n        lambda x: x,\n        lambda x: torch.flip(x, dims=[3]),\n        lambda x: torch.flip(x, dims=[2]),\n        lambda x: torch.rot90(x, k=-1, dims=[2, 3]),\n    ]\n\n    for batch in tqdm(loader, desc=f'  {split_name}', ncols=100):\n        image  = batch['image'].to(device)   # [1,3,H,W]\n        target = batch['mask'].to(device)    # [1,1,H,W]\n        dataset = batch['dataset'][0]\n        stem    = batch['stem'][0]\n\n        target_np = (target[0, 0].cpu().numpy() > 0.5).astype(np.uint8)\n\n        # ---- 1. Deterministic pass: prediction + Single + Evidential ----\n        model.eval()  # dropout OFF\n        evidence = model(image)['evidence']\n        p_hat_polyp, evid_unc = evidence_to_phat_uncertainty(evidence)\n        p_hat_np   = p_hat_polyp[0].cpu().numpy()\n        evid_np    = evid_unc[0].cpu().numpy()\n        pred_np    = (p_hat_np > 0.5).astype(np.uint8)\n\n        single_unc_np = 1.0 - np.maximum(p_hat_np, 1.0 - p_hat_np)\n\n        seg_metrics = compute_sample_metrics(pred_np, target_np)\n\n        # ---- 2. MC Dropout: T stochastic passes ----\n        model.enable_mc_dropout()\n        mc_phats = []\n        for _ in range(cfg['mc_passes_T']):\n            ev_t = model(image)['evidence']\n            p_t, _ = evidence_to_phat_uncertainty(ev_t)\n            mc_phats.append(p_t[0].cpu().numpy())\n        mc_phats = np.stack(mc_phats, axis=0)          # [T,H,W]\n        mc_mean_np = mc_phats.mean(axis=0)\n        mc_var_np  = mc_phats.var(axis=0)\n        model.eval()  # restore determinism\n\n        # ---- 3. TTA: 4 augmented-view passes (dropout OFF) ----\n        tta_phats = []\n        for fwd, inv in zip(tta_transforms, tta_inverse):\n            aug_img = fwd(image)\n            ev_a = model(aug_img)['evidence']\n            p_a, _ = evidence_to_phat_uncertainty(ev_a)\n            p_a = inv(p_a.unsqueeze(1)).squeeze(1)  # map back to orig orientation\n            tta_phats.append(p_a[0].cpu().numpy())\n        tta_phats = np.stack(tta_phats, axis=0)         # [4,H,W]\n        tta_mean_np = tta_phats.mean(axis=0)\n        tta_var_np  = tta_phats.var(axis=0)\n\n        # ---- Assemble row ----\n        row = dict(\n            split=split_name, dataset=DATASET_INFO.get(dataset, dataset),\n            dataset_key=dataset, stem=stem,\n            **seg_metrics,\n        )\n\n        for prefix, umap in [\n            ('single',    single_unc_np),\n            ('evidential', evid_np),\n            ('mcdropout', mc_var_np),\n            ('tta',        tta_var_np),\n        ]:\n            stats = summarize_map(umap, pred_np)\n            for k, v in stats.items():\n                row[f'{prefix}_{k}'] = v\n\n        # Extra: how much did MC / TTA mean prediction disagree with the\n        # single deterministic prediction (useful sanity + feature)\n        row['mc_mean_minus_single_meanabs']  = float(np.abs(mc_mean_np - p_hat_np).mean())\n        row['tta_mean_minus_single_meanabs'] = float(np.abs(tta_mean_np - p_hat_np).mean())\n\n        rows.append(row)\n\n    df = pd.DataFrame(rows)\n    return df\n\n\n# ============================================================\n# SILENT FAILURE DETECTION\n# ============================================================\n\ndef flag_silent_failures(df, cfg):\n    \"\"\"\n    A sample is a \"silent failure\" for a given uncertainty signal if:\n        true Dice < failure_dice_thresh   (bad segmentation)\n        AND\n        signal's mean_fg (or mean_all if fg empty) is below the\n        low_uncertainty_pctile-th percentile of that signal WITHIN\n        THE SAME SPLIT (so thresholds are split-specific, not global,\n        since absolute uncertainty scale can differ across splits).\n\n    Adds one boolean column per signal: '{signal}_silent_failure'\n    plus 'any_silent_failure' (silent for at least one signal) and\n    'all_silent_failure' (silent for every signal -- the most\n    dangerous / hardest-to-catch case).\n    \"\"\"\n    df = df.copy()\n    is_failure = df['dice'] < cfg['failure_dice_thresh']\n\n    signals = ['single', 'evidential', 'mcdropout', 'tta']\n    silent_cols = []\n\n    for split_name, split_df in df.groupby('split'):\n        idx = split_df.index\n        for sig in signals:\n            # Use mean_fg where available (predicted foreground exists),\n            # otherwise fall back to mean_all (e.g. model predicted no\n            # polyp pixels at all -- which for a true-positive case IS\n            # itself a maximally silent failure, handled separately\n            # below via complete_miss).\n            col = f'{sig}_mean_fg'\n            fallback = f'{sig}_mean_all'\n            vals = split_df[col].fillna(split_df[fallback])\n\n            low_thresh = np.nanpercentile(vals, cfg['low_uncertainty_pctile'])\n            low_unc = vals <= low_thresh\n\n            out_col = f'{sig}_silent_failure'\n            df.loc[idx, out_col] = (is_failure.loc[idx] & low_unc).values\n            if out_col not in silent_cols:\n                silent_cols.append(out_col)\n\n    # Complete misses (GT has polyp, model predicted nothing) are always\n    # silent failures for EVERY uncertainty signal computed on the\n    # predicted-foreground region, because there IS no predicted\n    # foreground to measure uncertainty over. Flag these explicitly so\n    # they aren't invisible just because mean_fg was NaN.\n    df['complete_miss_is_silent'] = df['complete_miss'] & (\n        df[[c for c in silent_cols]].fillna(False).sum(axis=1) == 0\n    )\n\n    df['any_silent_failure'] = df[silent_cols].any(axis=1) | df['complete_miss']\n    df['all_silent_failure'] = df[silent_cols].all(axis=1) | df['complete_miss']\n\n    return df\n\n\n# ============================================================\n# REPORTING\n# ============================================================\n\ndef print_split_summary(df, split_name):\n    sd = df[df['split'] == split_name]\n    print(f\"\\n{'='*70}\\n  {split_name.upper()}  (n={len(sd)})\\n{'='*70}\")\n    print(f\"  Gross Dice : {sd['dice'].mean():.4f}  (std={sd['dice'].std():.4f})\")\n    print(f\"  Gross IoU  : {sd['iou'].mean():.4f}\")\n    print(f\"  Median Dice: {sd['dice'].median():.4f}\")\n    print(f\"  Worst 5 Dice values: {sorted(sd['dice'].tolist())[:5]}\")\n\n    print(f\"\\n  Per-dataset Dice:\")\n    for ds, g in sd.groupby('dataset'):\n        print(f\"    {ds:15s} n={len(g):>4}  Dice={g['dice'].mean():.4f}  \"\n              f\"(min={g['dice'].min():.4f}, max={g['dice'].max():.4f})\")\n\n    n_complete_miss = sd['complete_miss'].sum()\n    n_false_alarm   = sd['false_alarm_on_empty'].sum()\n    print(f\"\\n  Complete misses (GT has polyp, pred empty): {n_complete_miss}\")\n    print(f\"  False alarms (GT empty, pred non-empty)    : {n_false_alarm}\")\n\n    n_fail = (sd['dice'] < EVAL_CFG['failure_dice_thresh']).sum()\n    print(f\"\\n  Samples with Dice < {EVAL_CFG['failure_dice_thresh']}: \"\n          f\"{n_fail} / {len(sd)} ({100*n_fail/max(1,len(sd)):.1f}%)\")\n\n    for sig in ['single', 'evidential', 'mcdropout', 'tta']:\n        col = f'{sig}_silent_failure'\n        n_silent = sd[col].sum()\n        pct_of_failures = 100*n_silent/max(1,n_fail)\n        print(f\"    Silent failures via {sig:11s}: {n_silent:>4} \"\n              f\"({pct_of_failures:.1f}% of all failures)\")\n\n    n_any = sd['any_silent_failure'].sum()\n    n_all = sd['all_silent_failure'].sum()\n    print(f\"\\n  Silent for >=1 signal (dangerous): {n_any} \"\n          f\"({100*n_any/max(1,len(sd)):.1f}% of split)\")\n    print(f\"  Silent for ALL 4 signals (worst case): {n_all} \"\n          f\"({100*n_all/max(1,len(sd)):.1f}% of split)\")\n\n\ndef print_worst_cases(df, split_name, n=10):\n    sd = df[df['split'] == split_name].sort_values('dice')\n    print(f\"\\n  Worst {n} cases in {split_name} by Dice:\")\n    cols = ['dataset', 'stem', 'dice', 'iou', 'complete_miss',\n            'evidential_mean_fg', 'mcdropout_mean_fg',\n            'any_silent_failure']\n    print(sd[cols].head(n).to_string(index=False))\n\n\n# ============================================================\n# ENTRY POINT\n# ============================================================\n\ndef main(cfg=EVAL_CFG):\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n\n    out_dir = Path(cfg['output_dir'])\n    out_dir.mkdir(parents=True, exist_ok=True)\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"LOADING MODEL\")\n    print(\"=\"*70)\n    model = load_hybrid_checkpoint(cfg['checkpoint_path'], cfg, device)\n    total_params = sum(p.numel() for p in model.parameters())\n    print(f\"  Params: {total_params/1e6:.2f}M\")\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"LOADING DATA\")\n    print(\"=\"*70)\n    loaders = create_eval_loaders(cfg)\n\n    all_dfs = []\n    for split_key, split_name in [\n        ('val', 'val'),\n        ('indist_test', 'indist_test'),\n        ('benchmark_test', 'benchmark_test'),\n    ]:\n        print(f\"\\nRunning full per-sample evaluation on: {split_name}\")\n        df = evaluate_split_full(model, loaders[split_key], device, split_name, cfg)\n        all_dfs.append(df)\n\n    full_df = pd.concat(all_dfs, ignore_index=True)\n    full_df = flag_silent_failures(full_df, cfg)\n\n    # Save the full per-sample table — this is the core deliverable\n    # feeding Goals 1/2/3 downstream.\n    per_sample_path = out_dir / 'per_sample_full_results.csv'\n    full_df.to_csv(per_sample_path, index=False)\n    print(f\"\\n\\nPer-sample results saved to: {per_sample_path}\")\n\n    # ---- Reporting ----\n    print(\"\\n\\n\" + \"#\"*70)\n    print(\"# DETAILED EVALUATION REPORT\")\n    print(\"#\"*70)\n    for split_name in ['val', 'indist_test', 'benchmark_test']:\n        print_split_summary(full_df, split_name)\n        print_worst_cases(full_df, split_name)\n\n    # ---- Cross-split silent failure summary table ----\n    print(\"\\n\\n\" + \"=\"*70)\n    print(\"SILENT FAILURE SUMMARY ACROSS ALL SPLITS\")\n    print(\"=\"*70)\n    summary_rows = []\n    for split_name, sd in full_df.groupby('split'):\n        n_fail = (sd['dice'] < cfg['failure_dice_thresh']).sum()\n        row = dict(split=split_name, n=len(sd), n_failures=n_fail,\n                   failure_rate=round(n_fail/max(1,len(sd)), 4))\n        for sig in ['single', 'evidential', 'mcdropout', 'tta']:\n            row[f'{sig}_silent_n'] = int(sd[f'{sig}_silent_failure'].sum())\n        row['any_silent_n'] = int(sd['any_silent_failure'].sum())\n        row['all_silent_n'] = int(sd['all_silent_failure'].sum())\n        summary_rows.append(row)\n    summary_df = pd.DataFrame(summary_rows)\n    print(summary_df.to_string(index=False))\n\n    summary_path = out_dir / 'silent_failure_summary.csv'\n    summary_df.to_csv(summary_path, index=False)\n    print(f\"\\nSummary saved to: {summary_path}\")\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"DONE.\")\n    print(f\"  Per-sample data -> {per_sample_path}\")\n    print(f\"  Silent-failure summary -> {summary_path}\")\n    print(\"  Next: use per-sample CSV for Goal 1 (correlation/sparsification\")\n    print(\"  analysis) and as training data for Goal 2/3 (no-GT quality\")\n    print(\"  estimator / severe-failure classifier).\")\n    print(\"=\"*70)\n\n    return full_df, summary_df\n\n\nif __name__ == '__main__':\n    full_df, summary_df = main(EVAL_CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T01:11:34.300813Z","iopub.execute_input":"2026-09-08T01:11:34.301449Z","iopub.status.idle":"2026-09-08T01:16:51.459515Z","shell.execute_reply.started":"2026-09-08T01:11:34.30142Z","shell.execute_reply":"2026-09-08T01:16:51.458599Z"}},"outputs":[{"name":"stdout","text":"Device: cuda\n\n======================================================================\nLOADING MODEL\n======================================================================\n  ✓ Loaded hybrid checkpoint — epoch=54, train-time best val dice=0.9409, dropout_p=0.2, type=hybrid_mcdropout_evidential\n  Params: 45.57M\n\n======================================================================\nLOADING DATA\n======================================================================\nLoaded eval sets — Val:261  Indist:262  Bench:636\n\nRunning full per-sample evaluation on: val\n","output_type":"stream"},{"name":"stderr","text":"  val: 100%|██████████████████████████████████████████████████████| 261/261 [01:11<00:00,  3.66it/s]\n","output_type":"stream"},{"name":"stdout","text":"\nRunning full per-sample evaluation on: indist_test\n","output_type":"stream"},{"name":"stderr","text":"  indist_test: 100%|██████████████████████████████████████████████| 262/262 [01:11<00:00,  3.67it/s]\n","output_type":"stream"},{"name":"stdout","text":"\nRunning full per-sample evaluation on: benchmark_test\n","output_type":"stream"},{"name":"stderr","text":"  benchmark_test: 100%|███████████████████████████████████████████| 636/636 [02:52<00:00,  3.70it/s]","output_type":"stream"},{"name":"stdout","text":"\n\nPer-sample results saved to: /kaggle/working/hybrid_eval/per_sample_full_results.csv\n\n\n######################################################################\n# DETAILED EVALUATION REPORT\n######################################################################\n\n======================================================================\n  VAL  (n=261)\n======================================================================\n  Gross Dice : 0.9418  (std=0.0796)\n  Gross IoU  : 0.8981\n  Median Dice: 0.9664\n  Worst 5 Dice values: [0.24721639509059087, 0.5065287548846629, 0.571428571480325, 0.6039506173165473, 0.6369568677961234]\n\n  Per-dataset Dice:\n    BKAI-IGH        n= 100  Dice=0.9525  (min=0.6370, max=0.9922)\n    CVC-ClinicDB    n=  61  Dice=0.9544  (min=0.7586, max=0.9844)\n    Kvasir-SEG      n= 100  Dice=0.9233  (min=0.2472, max=0.9908)\n\n  Complete misses (GT has polyp, pred empty): 0\n  False alarms (GT empty, pred non-empty)    : 0\n\n  Samples with Dice < 0.5: 1 / 261 (0.4%)\n    Silent failures via single     :    0 (0.0% of all failures)\n    Silent failures via evidential :    0 (0.0% of all failures)\n    Silent failures via mcdropout  :    0 (0.0% of all failures)\n    Silent failures via tta        :    0 (0.0% of all failures)\n\n  Silent for >=1 signal (dangerous): 0 (0.0% of split)\n  Silent for ALL 4 signals (worst case): 0 (0.0% of split)\n\n  Worst 10 cases in val by Dice:\n     dataset                             stem     dice      iou  complete_miss  evidential_mean_fg  mcdropout_mean_fg  any_silent_failure\n  Kvasir-SEG        ck2bxqz3evvg20794iiyv5v2m 0.247216 0.141042          False            0.079537           0.000736               False\n  Kvasir-SEG        ck2bxiswtxuw80838qkisqjwz 0.506529 0.339162          False            0.068073           0.001154               False\n  Kvasir-SEG        cju3uhb79gcgr0871orbrbi3x 0.571429 0.400000          False            0.144456           0.004503               False\n  Kvasir-SEG        cju3xuj20ivgp0818mij8bjrd 0.603951 0.432614          False            0.103614           0.001311               False\n    BKAI-IGH 94abb47953b0e4909384b57bb6a05260 0.636957 0.467305          False            0.074081           0.000742               False\n  Kvasir-SEG        cju1dnz61vfp40988e78bkjga 0.700273 0.538785          False            0.099243           0.004637               False\n  Kvasir-SEG        cju890guyoiti098753yg6cdu 0.730376 0.575269          False            0.052460           0.000343               False\n    BKAI-IGH 8a2710d568df17586ad8f3297c819c90 0.733196 0.578776          False            0.083789           0.003373               False\nCVC-ClinicDB                              576 0.758551 0.611021          False            0.086356           0.000883               False\n  Kvasir-SEG        cju3y21quj0ir0818kgjagr15 0.761901 0.615380          False            0.072654           0.000811               False\n\n======================================================================\n  INDIST_TEST  (n=262)\n======================================================================\n  Gross Dice : 0.9373  (std=0.0894)\n  Gross IoU  : 0.8919\n  Median Dice: 0.9631\n  Worst 5 Dice values: [0.1559262993987239, 0.413287674233575, 0.5087881595663384, 0.6339816056759678, 0.645882751729623]\n\n  Per-dataset Dice:\n    BKAI-IGH        n= 100  Dice=0.9412  (min=0.5088, max=0.9943)\n    CVC-ClinicDB    n=  62  Dice=0.9318  (min=0.1559, max=0.9833)\n    Kvasir-SEG      n= 100  Dice=0.9368  (min=0.4133, max=0.9904)\n\n  Complete misses (GT has polyp, pred empty): 0\n  False alarms (GT empty, pred non-empty)    : 0\n\n  Samples with Dice < 0.5: 2 / 262 (0.8%)\n    Silent failures via single     :    0 (0.0% of all failures)\n    Silent failures via evidential :    0 (0.0% of all failures)\n    Silent failures via mcdropout  :    0 (0.0% of all failures)\n    Silent failures via tta        :    0 (0.0% of all failures)\n\n  Silent for >=1 signal (dangerous): 0 (0.0% of split)\n  Silent for ALL 4 signals (worst case): 0 (0.0% of split)\n\n  Worst 10 cases in indist_test by Dice:\n     dataset                             stem     dice      iou  complete_miss  evidential_mean_fg  mcdropout_mean_fg  any_silent_failure\nCVC-ClinicDB                               22 0.155926 0.084555          False            0.080759           0.000875               False\n  Kvasir-SEG        cju7csvlb22fr0850lvm45n3x 0.413288 0.260468          False            0.143529           0.012396               False\n    BKAI-IGH dacfbed351db40939d9a2f94285ef2a2 0.508788 0.341191          False            0.112689           0.009443               False\nCVC-ClinicDB                               25 0.633982 0.464109          False            0.105046           0.001022               False\n    BKAI-IGH 4c1d4f50a5f3601ad4f13ccf1f4b331a 0.645883 0.476977          False            0.092884           0.004274               False\n  Kvasir-SEG        cju2hfqnmhisa0993gpleeldd 0.658277 0.490621          False            0.096529           0.002694               False\n  Kvasir-SEG        cju2wxv0hxs2f09884w48v8fi 0.664027 0.497036          False            0.113948           0.002316               False\n    BKAI-IGH da881fe4727080cae6daedd989517cb8 0.691589 0.528571          False            0.112796           0.014343               False\n  Kvasir-SEG        cju8bw697rwg308177tg8huas 0.692279 0.529379          False            0.092621           0.000660               False\n  Kvasir-SEG        cju8dpa89u6l80818dj6lldh9 0.705152 0.544583          False            0.064025           0.000247               False\n\n======================================================================\n  BENCHMARK_TEST  (n=636)\n======================================================================\n  Gross Dice : 0.8221  (std=0.2439)\n  Gross IoU  : 0.7494\n  Median Dice: 0.9244\n  Worst 5 Dice values: [2.6975263682474837e-11, 3.170376006493869e-11, 3.76038807190763e-11, 4.9079754598818166e-11, 9.253261773919379e-11]\n\n  Per-dataset Dice:\n    CVC-300         n=  60  Dice=0.9254  (min=0.6402, max=0.9808)\n    CVC-ColonDB     n= 380  Dice=0.7960  (min=0.0000, max=0.9808)\n    ETIS-Larib      n= 196  Dice=0.8411  (min=0.0000, max=0.9890)\n\n  Complete misses (GT has polyp, pred empty): 16\n  False alarms (GT empty, pred non-empty)    : 0\n\n  Samples with Dice < 0.5: 64 / 636 (10.1%)\n    Silent failures via single     :   15 (23.4% of all failures)\n    Silent failures via evidential :   15 (23.4% of all failures)\n    Silent failures via mcdropout  :   20 (31.2% of all failures)\n    Silent failures via tta        :    7 (10.9% of all failures)\n\n  Silent for >=1 signal (dangerous): 21 (3.3% of split)\n  Silent for ALL 4 signals (worst case): 16 (2.5% of split)\n\n  Worst 10 cases in benchmark_test by Dice:\n    dataset stem         dice          iou  complete_miss  evidential_mean_fg  mcdropout_mean_fg  any_silent_failure\nCVC-ColonDB  313 2.697526e-11 2.697526e-11          False            0.207655           0.006262               False\nCVC-ColonDB  325 3.170376e-11 3.170376e-11          False            0.146946           0.003213               False\nCVC-ColonDB  380 3.760388e-11 3.760388e-11          False            0.131335           0.002139               False\nCVC-ColonDB  324 4.907975e-11 4.907975e-11          False            0.144826           0.006634               False\n ETIS-Larib  157 9.253262e-11 9.253262e-11          False            0.117599           0.001174               False\n ETIS-Larib  156 1.233806e-10 1.233806e-10          False            0.130846           0.004130               False\nCVC-ColonDB   30 2.319109e-10 2.319109e-10           True                 NaN                NaN                True\nCVC-ColonDB  342 2.720348e-10 2.720348e-10          False            0.169826           0.005652               False\nCVC-ColonDB  267 2.827255e-10 2.827255e-10          False            0.145435           0.003877               False\nCVC-ColonDB  247 2.937720e-10 2.937720e-10           True                 NaN                NaN                True\n\n\n======================================================================\nSILENT FAILURE SUMMARY ACROSS ALL SPLITS\n======================================================================\n         split   n  n_failures  failure_rate  single_silent_n  evidential_silent_n  mcdropout_silent_n  tta_silent_n  any_silent_n  all_silent_n\nbenchmark_test 636          64        0.1006               15                   15                  20             7            21            16\n   indist_test 262           2        0.0076                0                    0                   0             0             0             0\n           val 261           1        0.0038                0                    0                   0             0             0             0\n\nSummary saved to: /kaggle/working/hybrid_eval/silent_failure_summary.csv\n\n======================================================================\nDONE.\n  Per-sample data -> /kaggle/working/hybrid_eval/per_sample_full_results.csv\n  Silent-failure summary -> /kaggle/working/hybrid_eval/silent_failure_summary.csv\n  Next: use per-sample CSV for Goal 1 (correlation/sparsification\n  analysis) and as training data for Goal 2/3 (no-GT quality\n  estimator / severe-failure classifier).\n======================================================================\n","output_type":"stream"},{"name":"stderr","text":"\n","output_type":"stream"}],"execution_count":6},{"cell_type":"code","source":"\"\"\"\nPolypSeg Hybrid Model — PAPER 1 METRIC SUITE\n==============================================================================\nPurpose: Produce every number and figure needed for a methods-and-metrics\ncomparison paper across FIVE uncertainty signals:\n\n    1. Single      (1 - max p_hat)                [1 pass, dropout OFF]\n    2. Evidential   (C / alpha_0)                  [1 pass, dropout OFF]\n    3. MC Dropout   (var of p_hat across T)        [T passes, dropout ON]\n    4. TTA          (var of p_hat across views)    [4 passes, dropout OFF]\n    5. Hybrid       (normalized MCD + normalized Evidential, i.e. an\n                     explicit epistemic + aleatoric fused estimate)\n                                                    [reuses 2+3, no extra passes]\n\nFor all five signals, this script computes the FULL metric suite a UQ\nmethods/metrics paper needs, at the correct granularity for each metric\n(pixel-level where the literature defines it that way):\n\n    - Expected Calibration Error (ECE) + reliability diagram data\n    - Brier score\n    - Pearson & Spearman correlation between uncertainty and pixel error\n      (pixel-level AND image-level versions)\n    - AUSE (Area Under Sparsification Error) + full sparsification curves\n    - Uncertainty-error spatial Dice overlap at multiple thresholds\n    - Silent-failure audit (image-level), extended to the Hybrid signal\n    - Aleatoric/epistemic decomposition-consistency subgroup analysis\n      (small vs large polyps; in-distribution vs out-of-distribution)\n    - A basic Dice-artifact diagnostic (flags suspicious near-zero-but-\n      nonzero Dice values so they can be inspected before trusting numbers)\n\nPixel-level global metrics (ECE, Brier, AUSE, pixel correlation) use a\nPER-IMAGE PIXEL SUBSAMPLE (configurable fraction) rather than holding every\npixel of every image in memory at once -- this is standard practice for\nthis kind of aggregate analysis and keeps the script's memory bounded\nregardless of dataset size.\n\nRun this in the same Kaggle environment/session where val_split.csv,\nindist_test.csv, benchmark_test.csv exist, with the hybrid checkpoint\navailable at CFG['checkpoint_path'].\n\nOutputs (all under CFG['output_dir']):\n    per_sample_results.csv          -- one row per image, all 5 signals\n    dice_artifact_flags.csv         -- suspicious near-zero Dice cases\n    calibration_metrics.csv         -- ECE + Brier per signal per split\n    reliability_diagram_data.csv    -- binned data for reliability plots\n    correlation_metrics.csv         -- Pearson/Spearman, pixel & image level\n    ause_metrics.csv                -- AUSE scalar per signal per split\n    sparsification_curve_data.csv   -- full curve data for plotting\n    spatial_overlap_metrics.csv     -- uncertainty-error Dice at thresholds\n    silent_failure_summary.csv      -- per split, per signal, incl. Hybrid\n    decomposition_subgroup_table.csv-- aleatoric/epistemic subgroup test\n    figures/*.png                   -- reliability diagrams, sparsification\n                                        curves, decomposition bar charts\n\"\"\"\n\nimport os\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport cv2\nfrom PIL import Image\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport timm\n\nfrom scipy.stats import pearsonr, spearmanr\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG\n# ============================================================\n\nCFG = {\n    \"val_csv\"           : \"val_split.csv\",\n    \"indist_test_csv\"   : \"indist_test.csv\",\n    \"benchmark_test_csv\": \"benchmark_test.csv\",\n\n    \"img_size\"    : 352,\n    \"mean\"        : [0.485, 0.456, 0.406],\n    \"std\"         : [0.229, 0.224, 0.225],\n    \"batch_size\"  : 1,\n    \"num_workers\" : 2,\n    \"dec_channels\": 64,\n    \"num_classes\" : 2,\n    \"dropout_p\"   : 0.2,\n\n    \"mc_passes_T\" : 4,\n\n    # Pixel subsampling for pixel-level global metrics (ECE/Brier/AUSE/\n    # pixel-correlation). Fraction of pixels kept PER IMAGE. Foreground\n    # pixels are always oversampled relative to background since they\n    # are the class of clinical interest and are the minority class --\n    # sampling uniformly at random would drown them out.\n    \"bg_sample_frac\"   : 0.02,   # keep 2% of background pixels per image\n    \"fg_sample_frac\"   : 1.00,   # keep all foreground (GT polyp) pixels\n    \"random_seed\"      : 42,\n\n    # Calibration\n    \"ece_n_bins\": 15,\n\n    # Silent failure / severe failure threshold\n    \"failure_dice_thresh\"    : 0.5,\n    \"low_uncertainty_pctile\" : 25,\n\n    # Spatial overlap thresholds (top-X% most uncertain pixels flagged)\n    \"overlap_thresholds\": [0.10, 0.20, 0.30],\n\n    # Dice-artifact diagnostic: flag any sample whose dice is below this\n    # value but not exactly 0 or 1 -- these are the numerically-suspicious\n    # near-zero cases worth inspecting by hand before trusting results.\n    \"dice_artifact_epsilon\": 1e-6,\n\n    \"checkpoint_path\": (\n        \"/kaggle/input/models/borshon10/pvt-mcd-effi/pytorch/default/1/\"\n        \"kaggle/working/checkpoints_uncertainty/\"\n        \"pvt_v2_b3_hybrid_mcdropout_evidential/best_model.pth\"\n    ),\n\n    \"output_dir\": \"/kaggle/working/paper1_metrics\",\n}\n\nDATASET_INFO = {\n    \"bkai\"    : \"BKAI-IGH\",\n    \"kvasir\"  : \"Kvasir-SEG\",\n    \"clinicdb\": \"CVC-ClinicDB\",\n    \"cvc\"     : \"CVC-ClinicDB\",\n    \"colondb\" : \"CVC-ColonDB\",\n    \"etis\"    : \"ETIS-Larib\",\n    \"cvc300\"  : \"CVC-300\",\n}\n\nSIGNALS = [\"single\", \"evidential\", \"mcdropout\", \"tta\", \"hybrid\"]\n\nnp.random.seed(CFG[\"random_seed\"])\n\n\n# ============================================================\n# DATA (identical to prior scripts)\n# ============================================================\n\ndef get_binary_mask_array(mask_path, is_bkai=False):\n    if is_bkai:\n        mask  = np.array(Image.open(mask_path).convert('RGB'))\n        r,g,b = mask[:,:,0], mask[:,:,1], mask[:,:,2]\n        red   = (r>150) & (r>g*1.5) & (r>b*1.5)\n        green = (g>150) & (g>r*1.5) & (g>b*1.5)\n        return (red|green).astype(np.uint8)*255\n    gray = np.array(Image.open(mask_path).convert('L'))\n    return (gray>127).astype(np.uint8)*255\n\n\ndef get_val_transform(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=CFG[\"mean\"], std=CFG[\"std\"]),\n        ToTensorV2(),\n    ])\n\n\ndef get_size_category(mask_np, ratio_thresh=(0.005, 0.02)):\n    \"\"\"Re-derive the same size_category logic used in the split script,\n    directly from the (resized) binary mask, so this script doesn't\n    depend on the split CSV already having it for every row.\"\"\"\n    h, w = mask_np.shape\n    ratio = mask_np.sum() / (h * w)\n    if ratio < ratio_thresh[0]:\n        return 'diminutive'\n    if ratio < ratio_thresh[1]:\n        return 'small'\n    return 'large'\n\n\nclass PolypSegDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row     = self.df.iloc[idx]\n        is_bkai = row.get('dataset', '') == 'bkai'\n\n        image = np.array(Image.open(row['img_path']).convert('RGB'))\n        mask  = get_binary_mask_array(row['mask_path'], is_bkai=is_bkai)\n\n        if self.transform:\n            aug     = self.transform(image=image, mask=mask)\n            image_t = aug['image']\n            mask_t  = (aug['mask'] / 255.).unsqueeze(0).float()\n        else:\n            image_t = torch.from_numpy(image.transpose(2,0,1)).float()/255.\n            mask_t  = torch.from_numpy(mask).float().unsqueeze(0)/255.\n\n        return {\n            'image'  : image_t,\n            'mask'   : mask_t,\n            'dataset': str(row.get('dataset', 'unknown')),\n            'stem'   : str(row.get('stem', 'unknown')),\n        }\n\n\ndef create_eval_loaders(cfg):\n    val_df       = pd.read_csv(cfg['val_csv'])\n    indist_df    = pd.read_csv(cfg['indist_test_csv'])\n    benchmark_df = pd.read_csv(cfg['benchmark_test_csv'])\n\n    sz, nw, bs = cfg['img_size'], cfg['num_workers'], cfg['batch_size']\n    tf = get_val_transform(sz)\n\n    val_loader = DataLoader(PolypSegDataset(val_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    indist_loader = DataLoader(PolypSegDataset(indist_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n    bench_loader = DataLoader(PolypSegDataset(benchmark_df, tf),\n        batch_size=bs, shuffle=False, num_workers=nw, pin_memory=True)\n\n    print(f\"Loaded eval sets — Val:{len(val_df)}  Indist:{len(indist_df)}  \"\n          f\"Bench:{len(benchmark_df)}\")\n\n    return {\n        'val'           : val_loader,\n        'indist_test'   : indist_loader,\n        'benchmark_test': bench_loader,\n    }\n\n\n# ============================================================\n# MODEL — identical to train_pvt_hybrid.py\n# ============================================================\n\nclass EncoderWrapper(nn.Module):\n    def __init__(self, backbone, source='timm', needs_permute=False):\n        super().__init__()\n        self.backbone      = backbone\n        self.source        = source\n        self.needs_permute = needs_permute\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        if self.needs_permute:\n            feats = [f.permute(0, 3, 1, 2).contiguous() for f in feats]\n        return tuple(feats)\n\n\ndef build_pvt_encoder(pretrained=False, img_size=352):\n    m = timm.create_model(\n        \"pvt_v2_b3\", pretrained=pretrained,\n        features_only=True, out_indices=(0, 1, 2, 3))\n    wrapper = EncoderWrapper(m, source='timm')\n\n    wrapper.eval()\n    with torch.no_grad():\n        dummy = torch.randn(1, 3, img_size, img_size)\n        feats = wrapper(dummy)\n    enc_channels = tuple(f.shape[1] for f in feats)\n\n    return wrapper, enc_channels\n\n\nclass ConvBNReLU(nn.Module):\n    def __init__(self, in_ch, out_ch, kernel=3, stride=1, padding=1):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, kernel, stride, padding, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.block(x)\n\n\nclass HybridDropoutEvidentialDecoder(nn.Module):\n    def __init__(self, enc_channels, dec_channels=64,\n                 dropout_p=0.2, num_classes=2):\n        super().__init__()\n        c = dec_channels\n        c1, c2, c3, c4 = enc_channels\n        self.num_classes = num_classes\n\n        self.lat4 = ConvBNReLU(c4, c)\n        self.lat3 = ConvBNReLU(c3, c)\n        self.lat2 = ConvBNReLU(c2, c)\n        self.lat1 = ConvBNReLU(c1, c)\n\n        self.fuse3 = ConvBNReLU(c * 2, c)\n        self.fuse2 = ConvBNReLU(c * 2, c)\n        self.fuse1 = ConvBNReLU(c * 2, c)\n\n        self.drop_lat4  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat3  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat2  = nn.Dropout2d(p=dropout_p)\n        self.drop_lat1  = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse3 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse2 = nn.Dropout2d(p=dropout_p)\n        self.drop_fuse1 = nn.Dropout2d(p=dropout_p)\n\n        self.seg_head = nn.Sequential(\n            ConvBNReLU(c, c),\n            nn.Conv2d(c, num_classes, 1),\n        )\n\n        self.dropout_p = dropout_p\n\n    def forward(self, features, input_size):\n        f1, f2, f3, f4 = features\n\n        p4 = self.drop_lat4(self.lat4(f4))\n        p3 = self.drop_lat3(self.lat3(f3))\n        p2 = self.drop_lat2(self.lat2(f2))\n        p1 = self.drop_lat1(self.lat1(f1))\n\n        up = lambda t, ref: F.interpolate(\n            t, size=ref.shape[2:], mode='bilinear', align_corners=False)\n\n        p3f = self.drop_fuse3(self.fuse3(torch.cat([up(p4, p3), p3], dim=1)))\n        p2f = self.drop_fuse2(self.fuse2(torch.cat([up(p3f, p2), p2], dim=1)))\n        p1f = self.drop_fuse1(self.fuse1(torch.cat([up(p2f, p1), p1], dim=1)))\n\n        out = F.interpolate(p1f, size=input_size,\n                            mode='bilinear', align_corners=False)\n        raw_output = self.seg_head(out)\n        evidence = F.softplus(raw_output)\n        return evidence\n\n\nclass PolypSegHybrid(nn.Module):\n    def __init__(self, pretrained=False, dec_channels=64,\n                 dropout_p=0.2, num_classes=2, img_size=352):\n        super().__init__()\n        self.encoder, enc_channels = build_pvt_encoder(pretrained, img_size)\n        self.decoder = HybridDropoutEvidentialDecoder(\n            enc_channels, dec_channels, dropout_p, num_classes)\n        self.encoder_name = \"pvt_v2_b3_hybrid_mcdropout_evidential\"\n        self.enc_channels = enc_channels\n        self.num_classes = num_classes\n        self.dropout_p = dropout_p\n\n    def forward(self, image):\n        H, W = image.shape[2:]\n        features = self.encoder(image)\n        evidence = self.decoder(features, (H, W))\n        return {'evidence': evidence}\n\n    def enable_mc_dropout(self):\n        self.eval()\n        for m in self.modules():\n            if isinstance(m, nn.Dropout2d):\n                m.train()\n\n\ndef load_hybrid_checkpoint(ckpt_path, cfg, device):\n    if not os.path.exists(ckpt_path):\n        raise FileNotFoundError(f\"Checkpoint not found: {ckpt_path}\")\n\n    model = PolypSegHybrid(\n        pretrained=False,\n        dec_channels=cfg['dec_channels'],\n        dropout_p=cfg['dropout_p'],\n        num_classes=cfg['num_classes'],\n        img_size=cfg['img_size'],\n    ).to(device)\n\n    ck = torch.load(ckpt_path, map_location=device, weights_only=False)\n    model.load_state_dict(ck['model'])\n    model.eval()\n\n    print(f\"  \\u2713 Loaded hybrid checkpoint — epoch={ck.get('epoch','?')}, \"\n          f\"train-time best val dice={ck.get('best_dice', float('nan')):.4f}, \"\n          f\"dropout_p={ck.get('dropout_p', cfg['dropout_p'])}, \"\n          f\"type={ck.get('checkpoint_type','?')}\")\n\n    return model\n\n\n@torch.no_grad()\ndef evidence_to_phat_uncertainty(evidence):\n    alpha = evidence + 1.0\n    alpha_0 = alpha.sum(dim=1, keepdim=True)\n    p_hat = alpha / alpha_0\n    C = evidence.shape[1]\n    evid_unc = (C / alpha_0).squeeze(1)\n    return p_hat[:, 1, :, :], evid_unc\n\n\n# ============================================================\n# RUNNING ACCUMULATORS FOR PIXEL-LEVEL GLOBAL METRICS\n# ============================================================\n\nclass PixelAccumulator:\n    \"\"\"\n    Accumulates a (subsampled) pool of pixel-level (uncertainty,\n    confidence, correct, error) tuples for ONE signal, across an entire\n    split, without holding every pixel of every image in memory.\n\n    Subsampling: all foreground (GT positive) pixels are kept, and a\n    small random fraction of background pixels are kept, per image --\n    foreground pixels are the minority class and the class of clinical\n    interest, so keeping all of them avoids them being drowned out by\n    the much larger background pixel pool while still bounding memory.\n    \"\"\"\n    def __init__(self, bg_frac=0.02, fg_frac=1.0, seed=42):\n        self.bg_frac = bg_frac\n        self.fg_frac = fg_frac\n        self.rng = np.random.RandomState(seed)\n        self.uncertainty = []\n        self.confidence  = []   # for ECE/Brier: predicted prob of the\n                                 # predicted class (or of polyp class)\n        self.correct     = []   # 1 if pixel correctly classified\n        self.gt          = []   # ground-truth binary label\n        self.pred_prob   = []   # predicted probability of polyp class\n\n    def add(self, uncertainty_map, pred_prob_map, gt_map):\n        \"\"\"All maps are 2D numpy arrays [H,W] for one image.\"\"\"\n        u = uncertainty_map.reshape(-1)\n        p = pred_prob_map.reshape(-1)\n        t = gt_map.reshape(-1).astype(np.uint8)\n        pred_label = (p > 0.5).astype(np.uint8)\n        correct = (pred_label == t).astype(np.uint8)\n        confidence = np.maximum(p, 1 - p)  # confidence in the predicted class\n\n        fg_idx = np.where(t == 1)[0]\n        bg_idx = np.where(t == 0)[0]\n\n        if self.fg_frac < 1.0 and len(fg_idx) > 0:\n            n_keep = max(1, int(len(fg_idx) * self.fg_frac))\n            fg_idx = self.rng.choice(fg_idx, size=n_keep, replace=False)\n\n        n_bg_keep = max(1, int(len(bg_idx) * self.bg_frac)) if len(bg_idx) > 0 else 0\n        if n_bg_keep > 0:\n            bg_idx = self.rng.choice(bg_idx, size=min(n_bg_keep, len(bg_idx)), replace=False)\n        else:\n            bg_idx = np.array([], dtype=int)\n\n        keep_idx = np.concatenate([fg_idx, bg_idx]).astype(int)\n        if len(keep_idx) == 0:\n            return\n\n        self.uncertainty.append(u[keep_idx])\n        self.confidence.append(confidence[keep_idx])\n        self.correct.append(correct[keep_idx])\n        self.gt.append(t[keep_idx])\n        self.pred_prob.append(p[keep_idx])\n\n    def finalize(self):\n        return dict(\n            uncertainty = np.concatenate(self.uncertainty) if self.uncertainty else np.array([]),\n            confidence  = np.concatenate(self.confidence) if self.confidence else np.array([]),\n            correct     = np.concatenate(self.correct) if self.correct else np.array([]),\n            gt          = np.concatenate(self.gt) if self.gt else np.array([]),\n            pred_prob   = np.concatenate(self.pred_prob) if self.pred_prob else np.array([]),\n        )\n\n\n# ============================================================\n# METRIC IMPLEMENTATIONS\n# ============================================================\n\ndef compute_ece(confidence, correct, n_bins=15):\n    \"\"\"Expected Calibration Error + per-bin reliability data.\"\"\"\n    bin_edges = np.linspace(0, 1, n_bins + 1)\n    bin_ids = np.digitize(confidence, bin_edges[1:-1])\n\n    ece = 0.0\n    n_total = len(confidence)\n    bin_rows = []\n    for b in range(n_bins):\n        mask = bin_ids == b\n        n_b = mask.sum()\n        if n_b == 0:\n            continue\n        acc_b = correct[mask].mean()\n        conf_b = confidence[mask].mean()\n        weight = n_b / n_total\n        ece += weight * abs(acc_b - conf_b)\n        bin_rows.append(dict(\n            bin_lower=bin_edges[b], bin_upper=bin_edges[b+1],\n            n=int(n_b), accuracy=acc_b, confidence=conf_b,\n        ))\n    return ece, pd.DataFrame(bin_rows)\n\n\ndef compute_brier(pred_prob, gt):\n    \"\"\"Brier score for binary segmentation: mean squared error between\n    predicted polyp-probability and the binary GT label.\"\"\"\n    return float(np.mean((pred_prob - gt) ** 2))\n\n\ndef compute_ause(uncertainty, error, n_steps=20):\n    \"\"\"\n    Area Under the Sparsification Error curve.\n    error: per-pixel error signal (0/1 incorrect, or continuous |p-gt|)\n    Returns (ause, curve_df) where curve_df has columns:\n        fraction_removed, retained_error_by_uncertainty, retained_error_oracle\n    \"\"\"\n    n = len(uncertainty)\n    order_unc = np.argsort(-uncertainty)      # most uncertain first\n    order_oracle = np.argsort(-error)         # most erroneous first\n\n    fractions = np.linspace(0, 1, n_steps + 1)[:-1]  # 0%, 5%, ..., 95%\n    curve_rows = []\n    for frac in fractions:\n        n_remove = int(frac * n)\n        keep_unc = order_unc[n_remove:]\n        keep_oracle = order_oracle[n_remove:]\n        err_unc = error[keep_unc].mean() if len(keep_unc) > 0 else 0.0\n        err_oracle = error[keep_oracle].mean() if len(keep_oracle) > 0 else 0.0\n        curve_rows.append(dict(\n            fraction_removed=frac,\n            retained_error_uncertainty=err_unc,\n            retained_error_oracle=err_oracle,\n        ))\n    curve_df = pd.DataFrame(curve_rows)\n    ause = float(np.trapz(\n        curve_df['retained_error_uncertainty'] - curve_df['retained_error_oracle'],\n        curve_df['fraction_removed']\n    ))\n    return ause, curve_df\n\n\ndef compute_spatial_overlap(uncertainty_map, pred_prob_map, gt_map, thresholds):\n    \"\"\"\n    Uncertainty-error Dice overlap at multiple top-X% uncertainty\n    thresholds. Error region = pixels where prediction != GT.\n    \"\"\"\n    pred_label = (pred_prob_map > 0.5).astype(np.uint8)\n    error_region = (pred_label != gt_map.astype(np.uint8))\n\n    u = uncertainty_map.reshape(-1)\n    err = error_region.reshape(-1)\n\n    results = {}\n    for thresh in thresholds:\n        n_flag = max(1, int(thresh * len(u)))\n        flagged_idx = np.argsort(-u)[:n_flag]\n        flagged = np.zeros_like(u, dtype=bool)\n        flagged[flagged_idx] = True\n\n        intersection = (flagged & err).sum()\n        dice = (2 * intersection) / (flagged.sum() + err.sum() + 1e-6)\n        results[thresh] = dice\n    return results\n\n\n# ============================================================\n# PER-SAMPLE SEGMENTATION METRICS (+ Dice-artifact diagnostic)\n# ============================================================\n\ndef compute_sample_metrics(pred_binary, target_binary, eps=1e-6):\n    p = pred_binary.reshape(-1).astype(np.float64)\n    t = target_binary.reshape(-1).astype(np.float64)\n    tp = float((p * t).sum())\n    fp = float(p.sum() - tp)\n    fn = float(t.sum() - tp)\n\n    dice = (2*tp + eps) / (2*tp + fp + fn + eps)\n    iou  = (tp + eps) / (tp + fp + fn + eps)\n    prec = (tp + eps) / (tp + fp + eps)\n    rec  = (tp + eps) / (tp + fn + eps)\n\n    return dict(\n        dice=dice, iou=iou, precision=prec, recall=rec,\n        tp=tp, fp=fp, fn=fn,\n        gt_polyp_px=float(t.sum()), pred_polyp_px=float(p.sum()),\n        complete_miss=bool(t.sum() > 0 and p.sum() == 0),\n        false_alarm_on_empty=bool(t.sum() == 0 and p.sum() > 0),\n    )\n\n\ndef flag_dice_artifacts(row, cfg):\n    \"\"\"\n    A Dice value that is extremely close to zero but NOT exactly zero,\n    on a case that is NOT a clean complete_miss/false_alarm, is worth\n    flagging for manual inspection -- it can indicate a mask-loading\n    edge case rather than a genuine near-total segmentation failure.\n    Genuine total misses (tp=0, gt has polyp, pred empty) are already\n    separately tracked via complete_miss and are NOT flagged here.\n    \"\"\"\n    eps = cfg['dice_artifact_epsilon']\n    suspicious = (\n        0 < row['dice'] < eps\n        and not row['complete_miss']\n        and not row['false_alarm_on_empty']\n    )\n    return suspicious\n\n\n# ============================================================\n# MAIN PER-SAMPLE + PIXEL-ACCUMULATION LOOP\n# ============================================================\n\n@torch.no_grad()\ndef evaluate_split_full(model, loader, device, split_name, cfg):\n    model.eval()\n    rows = []\n\n    accumulators = {sig: PixelAccumulator(\n        bg_frac=cfg['bg_sample_frac'], fg_frac=cfg['fg_sample_frac'],\n        seed=cfg['random_seed']) for sig in SIGNALS}\n\n    tta_transforms = [\n        lambda x: x,\n        lambda x: torch.flip(x, dims=[3]),\n        lambda x: torch.flip(x, dims=[2]),\n        lambda x: torch.rot90(x, k=1, dims=[2, 3]),\n    ]\n    tta_inverse = [\n        lambda x: x,\n        lambda x: torch.flip(x, dims=[3]),\n        lambda x: torch.flip(x, dims=[2]),\n        lambda x: torch.rot90(x, k=-1, dims=[2, 3]),\n    ]\n\n    for batch in tqdm(loader, desc=f'  {split_name}', ncols=100):\n        image  = batch['image'].to(device)\n        target = batch['mask'].to(device)\n        dataset = batch['dataset'][0]\n        stem    = batch['stem'][0]\n\n        target_np = (target[0, 0].cpu().numpy() > 0.5).astype(np.uint8)\n\n        # ---- Deterministic pass: Single + Evidential ----\n        model.eval()\n        evidence = model(image)['evidence']\n        p_hat_polyp, evid_unc = evidence_to_phat_uncertainty(evidence)\n        p_hat_np   = p_hat_polyp[0].cpu().numpy()\n        evid_np    = evid_unc[0].cpu().numpy()\n        pred_np    = (p_hat_np > 0.5).astype(np.uint8)\n\n        single_unc_np = 1.0 - np.maximum(p_hat_np, 1.0 - p_hat_np)\n\n        # The reported Dice/IoU/TP/FP/FN for an image is ALWAYS computed\n        # from the single deterministic (dropout-off) prediction, pred_np.\n        # This is intentional: segmentation quality is one property of\n        # the image, not five different numbers per uncertainty signal.\n        # What DOES vary per signal, from here on, is which prediction\n        # each signal's calibration/Brier/overlap numbers are scored\n        # against (see signal_maps below) -- MC-Dropout and TTA are\n        # scored against their own mean prediction, matching standard\n        # practice for those specific methods.\n        seg_metrics = compute_sample_metrics(pred_np, target_np)\n\n        # ---- MC Dropout: T stochastic passes ----\n        model.enable_mc_dropout()\n        mc_phats = []\n        for _ in range(cfg['mc_passes_T']):\n            ev_t = model(image)['evidence']\n            p_t, _ = evidence_to_phat_uncertainty(ev_t)\n            mc_phats.append(p_t[0].cpu().numpy())\n        mc_phats = np.stack(mc_phats, axis=0)\n        mc_mean_np = mc_phats.mean(axis=0)   # MC-Dropout's OWN reference prediction\n        mc_var_np  = mc_phats.var(axis=0)\n        model.eval()\n\n        # ---- TTA: 4 augmented-view passes ----\n        tta_phats = []\n        for fwd, inv in zip(tta_transforms, tta_inverse):\n            aug_img = fwd(image)\n            ev_a = model(aug_img)['evidence']\n            p_a, _ = evidence_to_phat_uncertainty(ev_a)\n            p_a = inv(p_a.unsqueeze(1)).squeeze(1)\n            tta_phats.append(p_a[0].cpu().numpy())\n        tta_phats = np.stack(tta_phats, axis=0)\n        tta_mean_np = tta_phats.mean(axis=0)  # TTA's OWN reference prediction\n        tta_var_np  = tta_phats.var(axis=0)\n\n        # ---- Hybrid: normalized MCD (epistemic) + normalized Evidential\n        # (aleatoric). Min-max normalization is done GLOBALLY after the\n        # full loop (see normalize_hybrid_signal below) since per-image\n        # normalization would remove exactly the cross-image uncertainty\n        # scale information the fused signal needs to be meaningful. A\n        # placeholder raw sum is stored per-pixel here and properly\n        # re-normalized in a second pass once split-wide min/max are known.\n        hybrid_raw_np = evid_np + mc_var_np  # placeholder; see note above\n\n        # ---- Assemble per-image row ----\n        row = dict(\n            split=split_name, dataset=DATASET_INFO.get(dataset, dataset),\n            dataset_key=dataset, stem=stem,\n            size_category=get_size_category(target_np),\n            **seg_metrics,\n        )\n        row['dice_artifact_suspect'] = flag_dice_artifacts(row, cfg)\n\n        # CRITICAL: each signal must be paired with the prediction it\n        # actually describes the uncertainty OF. Single/Evidential/Hybrid\n        # all come from the same deterministic pass, so they legitimately\n        # share p_hat_np. MC-Dropout and TTA each have their OWN mean\n        # prediction (averaged across their respective stochastic/\n        # augmented passes) -- feeding them p_hat_np instead (the\n        # deterministic single-pass prediction) would silently make\n        # every signal's calibration/Brier/error numbers identical,\n        # since they'd all be scored against the same underlying\n        # prediction regardless of which uncertainty map is attached.\n        signal_maps = {\n            'single':     (single_unc_np, p_hat_np),\n            'evidential': (evid_np,       p_hat_np),\n            'mcdropout':  (mc_var_np,     mc_mean_np),\n            'tta':        (tta_var_np,    tta_mean_np),\n            'hybrid':     (hybrid_raw_np, p_hat_np),\n        }\n        for sig, (umap, ref_pred) in signal_maps.items():\n            pred_mask_sig = (ref_pred > 0.5)\n            row[f'{sig}_mean_fg'] = float(umap[pred_mask_sig].mean()) if pred_mask_sig.sum() > 0 else np.nan\n            row[f'{sig}_mean_all'] = float(umap.mean())\n            row[f'{sig}_max_all'] = float(umap.max())\n            accumulators[sig].add(umap, ref_pred, target_np)\n\n            # Spatial overlap needs the true per-image 2D map (not the\n            # flattened, subsampled pixel pool), so it is computed HERE,\n            # per image, then averaged across the split afterward. This\n            # is the only correct place to compute it without a third\n            # inference pass over the data. Uses each signal's own\n            # reference prediction for the error region, matching the\n            # accumulator logic above.\n            overlap_scores = compute_spatial_overlap(\n                umap, ref_pred, target_np, cfg['overlap_thresholds'])\n            for thresh, dice_val in overlap_scores.items():\n                row[f'{sig}_overlap_dice_top{int(thresh*100)}'] = dice_val\n\n        rows.append(row)\n\n    df = pd.DataFrame(rows)\n    accum_data = {sig: acc.finalize() for sig, acc in accumulators.items()}\n    return df, accum_data\n\n\ndef normalize_hybrid_pixel_pool(accum_data):\n    \"\"\"\n    Re-derives a properly min-max-normalized Hybrid signal for the\n    accumulated pixel pools: normalize evidential and MC-Dropout\n    uncertainty pools to [0,1] over the WHOLE split before summing,\n    so neither term dominates purely due to differing raw numeric\n    scale (evidential is bounded in [0,1] by construction; MC-Dropout\n    variance is not, so summing raw values would silently let MCD\n    dominate regardless of its actual informativeness).\n    \"\"\"\n    evid_pool = accum_data['evidential']['uncertainty']\n    mcd_pool  = accum_data['mcdropout']['uncertainty']\n\n    def minmax(x):\n        if len(x) == 0 or x.max() == x.min():\n            return x\n        return (x - x.min()) / (x.max() - x.min())\n\n    evid_norm = minmax(evid_pool)\n    mcd_norm  = minmax(mcd_pool)\n    # Both pools come from the SAME pixels in the SAME order (same\n    # accumulation loop), so elementwise combination is valid.\n    hybrid_pool = evid_norm + mcd_norm\n\n    accum_data['hybrid'] = dict(\n        uncertainty=hybrid_pool,\n        confidence=accum_data['evidential']['confidence'],\n        correct=accum_data['evidential']['correct'],\n        gt=accum_data['evidential']['gt'],\n        pred_prob=accum_data['evidential']['pred_prob'],\n    )\n    return accum_data\n\n\ndef normalize_hybrid_image_column(df):\n    \"\"\"Same min-max normalization logic, applied to the per-image\n    summary columns (mean_fg/mean_all/max_all) for the Hybrid signal,\n    computed per split so image-level Hybrid numbers are on the same\n    footing as the pixel-level ones.\"\"\"\n    df = df.copy()\n    for split_name, split_df in df.groupby('split'):\n        idx = split_df.index\n        for col_suffix in ['mean_fg', 'mean_all', 'max_all']:\n            evid_col = f'evidential_{col_suffix}'\n            mcd_col  = f'mcdropout_{col_suffix}'\n            e = split_df[evid_col]\n            m = split_df[mcd_col]\n            e_norm = (e - e.min()) / (e.max() - e.min() + 1e-12)\n            m_norm = (m - m.min()) / (m.max() - m.min() + 1e-12)\n            df.loc[idx, f'hybrid_{col_suffix}'] = (e_norm + m_norm).values\n    return df\n\n\n# ============================================================\n# SILENT FAILURE FLAGGING (extended to Hybrid)\n# ============================================================\n\ndef flag_silent_failures(df, cfg):\n    df = df.copy()\n    is_failure = df['dice'] < cfg['failure_dice_thresh']\n    silent_cols = []\n\n    for split_name, split_df in df.groupby('split'):\n        idx = split_df.index\n        for sig in SIGNALS:\n            col = f'{sig}_mean_fg'\n            fallback = f'{sig}_mean_all'\n            vals = split_df[col].fillna(split_df[fallback])\n            low_thresh = np.nanpercentile(vals, cfg['low_uncertainty_pctile'])\n            low_unc = vals <= low_thresh\n            out_col = f'{sig}_silent_failure'\n            df.loc[idx, out_col] = (is_failure.loc[idx] & low_unc).values\n            if out_col not in silent_cols:\n                silent_cols.append(out_col)\n\n    df['complete_miss_is_silent'] = df['complete_miss'] & (\n        df[silent_cols].fillna(False).sum(axis=1) == 0\n    )\n    df['any_silent_failure'] = df[silent_cols].any(axis=1) | df['complete_miss']\n    df['all_silent_failure'] = df[silent_cols].all(axis=1) | df['complete_miss']\n    return df\n\n\n# ============================================================\n# DECOMPOSITION-CONSISTENCY SUBGROUP ANALYSIS\n# ============================================================\n\ndef decomposition_subgroup_analysis(df):\n    \"\"\"\n    Tests whether Evidential (aleatoric-leaning) correlates more\n    strongly with error on data-ambiguity subgroups (small/diminutive\n    polyps), while MC-Dropout/TTA/Hybrid (epistemic-leaning) correlate\n    more strongly with error on distribution-shift subgroups\n    (benchmark/out-of-distribution vs in-distribution).\n\n    Uses image-level mean_fg (falling back to mean_all) vs (1 - dice)\n    as the error proxy, per signal per subgroup, with Pearson r.\n    \"\"\"\n    rows = []\n\n    # Subgroup 1: polyp size (ambiguity proxy)\n    size_groups = {\n        'small_or_diminutive': df['size_category'].isin(['small', 'diminutive']),\n        'large': df['size_category'] == 'large',\n    }\n    for group_name, mask in size_groups.items():\n        sub = df[mask]\n        for sig in SIGNALS:\n            vals = sub[f'{sig}_mean_fg'].fillna(sub[f'{sig}_mean_all'])\n            err = 1 - sub['dice']\n            valid = vals.notna() & err.notna()\n            if valid.sum() < 5:\n                continue\n            r, p = pearsonr(vals[valid], err[valid])\n            rows.append(dict(axis='size', subgroup=group_name, signal=sig,\n                              n=int(valid.sum()), pearson_r=r, p_value=p))\n\n    # Subgroup 2: distribution shift\n    shift_groups = {\n        'in_distribution': df['split'].isin(['val', 'indist_test']),\n        'out_of_distribution': df['split'] == 'benchmark_test',\n    }\n    for group_name, mask in shift_groups.items():\n        sub = df[mask]\n        for sig in SIGNALS:\n            vals = sub[f'{sig}_mean_fg'].fillna(sub[f'{sig}_mean_all'])\n            err = 1 - sub['dice']\n            valid = vals.notna() & err.notna()\n            if valid.sum() < 5:\n                continue\n            r, p = pearsonr(vals[valid], err[valid])\n            rows.append(dict(axis='distribution_shift', subgroup=group_name,\n                              signal=sig, n=int(valid.sum()), pearson_r=r, p_value=p))\n\n    return pd.DataFrame(rows)\n\n\n# ============================================================\n# PLOTTING\n# ============================================================\n\ndef plot_reliability_diagrams(reliability_data, out_dir):\n    fig, axes = plt.subplots(1, len(SIGNALS), figsize=(4*len(SIGNALS), 4), sharey=True)\n    for ax, sig in zip(axes, SIGNALS):\n        sub = reliability_data[reliability_data['signal'] == sig]\n        if len(sub) == 0:\n            continue\n        ax.plot([0, 1], [0, 1], 'k--', alpha=0.5, label='Perfect calibration')\n        ax.plot(sub['confidence'], sub['accuracy'], 'o-', label=sig)\n        ax.set_title(sig)\n        ax.set_xlabel('Confidence')\n        ax.set_ylim(0, 1); ax.set_xlim(0, 1)\n    axes[0].set_ylabel('Accuracy')\n    plt.tight_layout()\n    plt.savefig(out_dir / 'reliability_diagrams.png', dpi=150)\n    plt.close()\n\n\ndef plot_sparsification_curves(curve_data, out_dir):\n    splits = curve_data['split'].unique()\n    fig, axes = plt.subplots(1, len(splits), figsize=(5*len(splits), 4), sharey=True)\n    if len(splits) == 1:\n        axes = [axes]\n    for ax, split_name in zip(axes, splits):\n        sub = curve_data[curve_data['split'] == split_name]\n        oracle_plotted = False\n        for sig in SIGNALS:\n            sig_sub = sub[sub['signal'] == sig]\n            if len(sig_sub) == 0:\n                continue\n            ax.plot(sig_sub['fraction_removed'], sig_sub['retained_error_uncertainty'],\n                     label=sig)\n            if not oracle_plotted:\n                ax.plot(sig_sub['fraction_removed'], sig_sub['retained_error_oracle'],\n                         'k--', label='Oracle', alpha=0.6)\n                oracle_plotted = True\n        ax.set_title(split_name)\n        ax.set_xlabel('Fraction of pixels removed')\n    axes[0].set_ylabel('Retained error')\n    axes[-1].legend(fontsize=8)\n    plt.tight_layout()\n    plt.savefig(out_dir / 'sparsification_curves.png', dpi=150)\n    plt.close()\n\n\ndef plot_decomposition_bars(decomp_df, out_dir):\n    fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))\n\n    size_df = decomp_df[decomp_df['axis'] == 'size']\n    if len(size_df) > 0:\n        pivot = size_df.pivot(index='signal', columns='subgroup', values='pearson_r')\n        pivot.plot(kind='bar', ax=axes[0])\n        axes[0].set_title('Correlation with error: polyp size subgroups')\n        axes[0].set_ylabel(\"Pearson r (uncertainty vs. 1-Dice)\")\n        axes[0].axhline(0, color='k', linewidth=0.5)\n\n    shift_df = decomp_df[decomp_df['axis'] == 'distribution_shift']\n    if len(shift_df) > 0:\n        pivot = shift_df.pivot(index='signal', columns='subgroup', values='pearson_r')\n        pivot.plot(kind='bar', ax=axes[1])\n        axes[1].set_title('Correlation with error: distribution shift subgroups')\n        axes[1].set_ylabel(\"Pearson r (uncertainty vs. 1-Dice)\")\n        axes[1].axhline(0, color='k', linewidth=0.5)\n\n    plt.tight_layout()\n    plt.savefig(out_dir / 'decomposition_consistency.png', dpi=150)\n    plt.close()\n\n\n# ============================================================\n# MAIN\n# ============================================================\n\ndef main(cfg=CFG):\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Device: {device}\")\n\n    out_dir = Path(cfg['output_dir'])\n    fig_dir = out_dir / 'figures'\n    out_dir.mkdir(parents=True, exist_ok=True)\n    fig_dir.mkdir(parents=True, exist_ok=True)\n\n    print(\"\\n\" + \"=\"*70 + \"\\nLOADING MODEL\\n\" + \"=\"*70)\n    model = load_hybrid_checkpoint(cfg['checkpoint_path'], cfg, device)\n\n    print(\"\\n\" + \"=\"*70 + \"\\nLOADING DATA\\n\" + \"=\"*70)\n    loaders = create_eval_loaders(cfg)\n\n    all_dfs = []\n    all_accum = {}   # split -> {signal -> pixel pool dict}\n    for split_key, split_name in [\n        ('val', 'val'), ('indist_test', 'indist_test'),\n        ('benchmark_test', 'benchmark_test'),\n    ]:\n        print(f\"\\nEvaluating split: {split_name}\")\n        df, accum = evaluate_split_full(model, loaders[split_key], device, split_name, cfg)\n        accum = normalize_hybrid_pixel_pool(accum)\n        all_dfs.append(df)\n        all_accum[split_name] = accum\n\n    full_df = pd.concat(all_dfs, ignore_index=True)\n    full_df = normalize_hybrid_image_column(full_df)\n    full_df = flag_silent_failures(full_df, cfg)\n\n    per_sample_path = out_dir / 'per_sample_results.csv'\n    full_df.to_csv(per_sample_path, index=False)\n    print(f\"\\nPer-sample results -> {per_sample_path}\")\n\n    # ---- Dice-artifact diagnostic ----\n    artifacts = full_df[full_df['dice_artifact_suspect']]\n    artifacts_path = out_dir / 'dice_artifact_flags.csv'\n    artifacts[['split','dataset','stem','dice','tp','fp','fn',\n               'gt_polyp_px','pred_polyp_px','complete_miss']].to_csv(artifacts_path, index=False)\n    print(f\"Dice-artifact flags ({len(artifacts)} suspicious cases) -> {artifacts_path}\")\n    if len(artifacts) > 0:\n        print(\"  \\u26a0 INSPECT THESE BEFORE TRUSTING BENCHMARK NUMBERS:\")\n        print(artifacts[['split','dataset','stem','dice','tp','fp','fn']].head(10).to_string(index=False))\n\n    # ---- Calibration (ECE + Brier), pixel-level, per signal per split ----\n    print(\"\\n\" + \"=\"*70 + \"\\nCALIBRATION METRICS\\n\" + \"=\"*70)\n    calib_rows = []\n    reliability_rows = []\n    for split_name, accum in all_accum.items():\n        for sig in SIGNALS:\n            pool = accum[sig]\n            if len(pool['confidence']) == 0:\n                continue\n            ece, bin_df = compute_ece(pool['confidence'], pool['correct'], cfg['ece_n_bins'])\n            brier = compute_brier(pool['pred_prob'], pool['gt'])\n            calib_rows.append(dict(split=split_name, signal=sig, ece=ece, brier=brier,\n                                    n_pixels=len(pool['confidence'])))\n            bin_df['split'] = split_name\n            bin_df['signal'] = sig\n            reliability_rows.append(bin_df)\n    calib_df = pd.DataFrame(calib_rows)\n    calib_df.to_csv(out_dir / 'calibration_metrics.csv', index=False)\n    print(calib_df.to_string(index=False))\n\n    reliability_df = pd.concat(reliability_rows, ignore_index=True) if reliability_rows else pd.DataFrame()\n    reliability_df.to_csv(out_dir / 'reliability_diagram_data.csv', index=False)\n    if len(reliability_df) > 0:\n        # plot only for benchmark_test (most informative split, OOD)\n        plot_reliability_diagrams(\n            reliability_df[reliability_df['split'] == 'benchmark_test'], fig_dir)\n\n    # ---- Correlation (pixel-level + image-level) ----\n    print(\"\\n\" + \"=\"*70 + \"\\nCORRELATION METRICS\\n\" + \"=\"*70)\n    corr_rows = []\n    for split_name, accum in all_accum.items():\n        for sig in SIGNALS:\n            pool = accum[sig]\n            if len(pool['uncertainty']) < 10:\n                continue\n            error = (pool['pred_prob'] > 0.5).astype(int) != pool['gt']\n            error = error.astype(float)\n            pr, pp = pearsonr(pool['uncertainty'], error)\n            sr, sp = spearmanr(pool['uncertainty'], error)\n            corr_rows.append(dict(split=split_name, signal=sig, level='pixel',\n                                   pearson_r=pr, pearson_p=pp,\n                                   spearman_r=sr, spearman_p=sp,\n                                   n=len(pool['uncertainty'])))\n\n    for split_name, split_df in full_df.groupby('split'):\n        for sig in SIGNALS:\n            vals = split_df[f'{sig}_mean_fg'].fillna(split_df[f'{sig}_mean_all'])\n            err = 1 - split_df['dice']\n            valid = vals.notna() & err.notna()\n            if valid.sum() < 10:\n                continue\n            pr, pp = pearsonr(vals[valid], err[valid])\n            sr, sp = spearmanr(vals[valid], err[valid])\n            corr_rows.append(dict(split=split_name, signal=sig, level='image',\n                                   pearson_r=pr, pearson_p=pp,\n                                   spearman_r=sr, spearman_p=sp,\n                                   n=int(valid.sum())))\n    corr_df = pd.DataFrame(corr_rows)\n    corr_df.to_csv(out_dir / 'correlation_metrics.csv', index=False)\n    print(corr_df.to_string(index=False))\n\n    # ---- AUSE + sparsification curves ----\n    print(\"\\n\" + \"=\"*70 + \"\\nAUSE / SPARSIFICATION\\n\" + \"=\"*70)\n    ause_rows = []\n    curve_rows = []\n    for split_name, accum in all_accum.items():\n        for sig in SIGNALS:\n            pool = accum[sig]\n            if len(pool['uncertainty']) < 10:\n                continue\n            error = ((pool['pred_prob'] > 0.5).astype(int) != pool['gt']).astype(float)\n            ause, curve_df = compute_ause(pool['uncertainty'], error)\n            ause_rows.append(dict(split=split_name, signal=sig, ause=ause))\n            curve_df['split'] = split_name\n            curve_df['signal'] = sig\n            curve_rows.append(curve_df)\n    ause_df = pd.DataFrame(ause_rows)\n    ause_df.to_csv(out_dir / 'ause_metrics.csv', index=False)\n    print(ause_df.to_string(index=False))\n\n    curve_all = pd.concat(curve_rows, ignore_index=True) if curve_rows else pd.DataFrame()\n    curve_all.to_csv(out_dir / 'sparsification_curve_data.csv', index=False)\n    if len(curve_all) > 0:\n        plot_sparsification_curves(curve_all, fig_dir)\n\n    # ---- Spatial overlap ----\n    # Computed per-image during the main inference loop (true 2D maps,\n    # not the flattened pixel pool), then averaged here per split per\n    # signal per threshold -- this is the correct, non-approximated\n    # version of the metric.\n    print(\"\\n\" + \"=\"*70 + \"\\nSPATIAL OVERLAP (uncertainty-error Dice)\\n\" + \"=\"*70)\n    overlap_rows = []\n    for split_name, split_df in full_df.groupby('split'):\n        for sig in SIGNALS:\n            for thresh in cfg['overlap_thresholds']:\n                col = f'{sig}_overlap_dice_top{int(thresh*100)}'\n                if col not in split_df.columns:\n                    continue\n                overlap_rows.append(dict(\n                    split=split_name, signal=sig, threshold=thresh,\n                    mean_overlap_dice=split_df[col].mean(),\n                    std_overlap_dice=split_df[col].std(),\n                ))\n    overlap_df = pd.DataFrame(overlap_rows)\n    overlap_df.to_csv(out_dir / 'spatial_overlap_metrics.csv', index=False)\n    print(overlap_df.to_string(index=False))\n\n    # ---- Decomposition-consistency subgroup analysis ----\n    print(\"\\n\" + \"=\"*70 + \"\\nDECOMPOSITION-CONSISTENCY ANALYSIS\\n\" + \"=\"*70)\n    decomp_df = decomposition_subgroup_analysis(full_df)\n    decomp_df.to_csv(out_dir / 'decomposition_subgroup_table.csv', index=False)\n    print(decomp_df.to_string(index=False))\n    if len(decomp_df) > 0:\n        plot_decomposition_bars(decomp_df, fig_dir)\n\n    # ---- Silent failure summary (now incl. Hybrid) ----\n    print(\"\\n\" + \"=\"*70 + \"\\nSILENT FAILURE SUMMARY\\n\" + \"=\"*70)\n    summary_rows = []\n    for split_name, sd in full_df.groupby('split'):\n        n_fail = (sd['dice'] < cfg['failure_dice_thresh']).sum()\n        row = dict(split=split_name, n=len(sd), n_failures=int(n_fail),\n                   failure_rate=round(n_fail/max(1,len(sd)), 4))\n        for sig in SIGNALS:\n            row[f'{sig}_silent_n'] = int(sd[f'{sig}_silent_failure'].sum())\n        row['any_silent_n'] = int(sd['any_silent_failure'].sum())\n        row['all_silent_n'] = int(sd['all_silent_failure'].sum())\n        summary_rows.append(row)\n    summary_df = pd.DataFrame(summary_rows)\n    summary_df.to_csv(out_dir / 'silent_failure_summary.csv', index=False)\n    print(summary_df.to_string(index=False))\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"DONE. All outputs in:\", out_dir)\n    print(\"=\"*70)\n\n    return dict(\n        per_sample=full_df, calibration=calib_df, correlation=corr_df,\n        ause=ause_df, decomposition=decomp_df, silent_failure=summary_df,\n        dice_artifacts=artifacts, spatial_overlap=overlap_df,\n    )\n\n\n\nif __name__ == '__main__':\n    results = main(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T00:49:16.930348Z","iopub.execute_input":"2026-09-08T00:49:16.931314Z","iopub.status.idle":"2026-09-08T00:56:40.138591Z","shell.execute_reply.started":"2026-09-08T00:49:16.931276Z","shell.execute_reply":"2026-09-08T00:56:40.137702Z"}},"outputs":[{"name":"stdout","text":"Device: cuda\n\n======================================================================\nLOADING MODEL\n======================================================================\n  ✓ Loaded hybrid checkpoint — epoch=54, train-time best val dice=0.9409, dropout_p=0.2, type=hybrid_mcdropout_evidential\n\n======================================================================\nLOADING DATA\n======================================================================\nLoaded eval sets — Val:261  Indist:262  Bench:636\n\nEvaluating split: val\n","output_type":"stream"},{"name":"stderr","text":"  val: 100%|██████████████████████████████████████████████████████| 261/261 [01:24<00:00,  3.09it/s]\n","output_type":"stream"},{"name":"stdout","text":"\nEvaluating split: indist_test\n","output_type":"stream"},{"name":"stderr","text":"  indist_test: 100%|██████████████████████████████████████████████| 262/262 [01:26<00:00,  3.03it/s]\n","output_type":"stream"},{"name":"stdout","text":"\nEvaluating split: benchmark_test\n","output_type":"stream"},{"name":"stderr","text":"  benchmark_test: 100%|███████████████████████████████████████████| 636/636 [03:29<00:00,  3.04it/s]\n","output_type":"stream"},{"name":"stdout","text":"\nPer-sample results -> /kaggle/working/paper1_metrics/per_sample_results.csv\nDice-artifact flags (12 suspicious cases) -> /kaggle/working/paper1_metrics/dice_artifact_flags.csv\n  ⚠ INSPECT THESE BEFORE TRUSTING BENCHMARK NUMBERS:\n         split     dataset stem         dice  tp      fp      fn\nbenchmark_test CVC-ColonDB  267 2.827255e-10 0.0  2488.0  1049.0\nbenchmark_test CVC-ColonDB  313 2.697526e-11 0.0    88.0 36983.0\nbenchmark_test CVC-ColonDB  324 4.907975e-11 0.0  3419.0 16956.0\nbenchmark_test CVC-ColonDB  325 3.170376e-11 0.0  9273.0 22269.0\nbenchmark_test CVC-ColonDB  342 2.720348e-10 0.0  3309.0   367.0\nbenchmark_test CVC-ColonDB  358 3.985652e-10 0.0  2012.0   497.0\nbenchmark_test CVC-ColonDB  360 2.984184e-10 0.0  2859.0   492.0\nbenchmark_test CVC-ColonDB  380 3.760388e-11 0.0 25707.0   886.0\nbenchmark_test  ETIS-Larib  156 1.233806e-10 0.0  7122.0   983.0\nbenchmark_test  ETIS-Larib  157 9.253262e-11 0.0  9585.0  1222.0\n\n======================================================================\nCALIBRATION METRICS\n======================================================================\n         split     signal      ece    brier  n_pixels\n           val     single 0.011791 0.043878   3783026\n           val evidential 0.011791 0.043878   3783026\n           val  mcdropout 0.010537 0.044292   3783026\n           val        tta 0.007607 0.042209   3783026\n           val     hybrid 0.011791 0.043878   3783026\n   indist_test     single 0.033561 0.065246   3807423\n   indist_test evidential 0.033561 0.065246   3807423\n   indist_test  mcdropout 0.032490 0.065853   3807423\n   indist_test        tta 0.027303 0.063144   3807423\n   indist_test     hybrid 0.033561 0.065246   3807423\nbenchmark_test     single 0.137912 0.164865   6334521\nbenchmark_test evidential 0.137912 0.164865   6334521\nbenchmark_test  mcdropout 0.137121 0.165073   6334521\nbenchmark_test        tta 0.111704 0.148060   6334521\nbenchmark_test     hybrid 0.137912 0.164865   6334521\n\n======================================================================\nCORRELATION METRICS\n======================================================================\n         split     signal level  pearson_r    pearson_p  spearman_r   spearman_p       n\n           val     single pixel   0.427039 0.000000e+00    0.243578 0.000000e+00 3783026\n           val evidential pixel   0.438229 0.000000e+00    0.245053 0.000000e+00 3783026\n           val  mcdropout pixel   0.286421 0.000000e+00    0.253618 0.000000e+00 3783026\n           val        tta pixel   0.230952 0.000000e+00    0.270297 0.000000e+00 3783026\n           val     hybrid pixel   0.439039 0.000000e+00    0.245217 0.000000e+00 3783026\n   indist_test     single pixel   0.322995 0.000000e+00    0.152952 0.000000e+00 3807423\n   indist_test evidential pixel   0.315790 0.000000e+00    0.153139 0.000000e+00 3807423\n   indist_test  mcdropout pixel   0.213323 0.000000e+00    0.157876 0.000000e+00 3807423\n   indist_test        tta pixel   0.242942 0.000000e+00    0.269119 0.000000e+00 3807423\n   indist_test     hybrid pixel   0.317940 0.000000e+00    0.153412 0.000000e+00 3807423\nbenchmark_test     single pixel   0.272530 0.000000e+00    0.157078 0.000000e+00 6334521\nbenchmark_test evidential pixel   0.305045 0.000000e+00    0.164637 0.000000e+00 6334521\nbenchmark_test  mcdropout pixel   0.155461 0.000000e+00    0.151436 0.000000e+00 6334521\nbenchmark_test        tta pixel   0.264606 0.000000e+00    0.333996 0.000000e+00 6334521\nbenchmark_test     hybrid pixel   0.299565 0.000000e+00    0.163455 0.000000e+00 6334521\nbenchmark_test     single image   0.512566 6.726070e-44    0.676758 2.223394e-86     636\nbenchmark_test evidential image   0.419772 1.544036e-28    0.631197 5.467575e-72     636\nbenchmark_test  mcdropout image   0.219062 2.383100e-08    0.472322 1.180099e-36     636\nbenchmark_test        tta image   0.602490 4.296615e-64    0.653413 1.076812e-78     636\nbenchmark_test     hybrid image   0.485329 6.841699e-39    0.707515 1.160838e-97     636\n   indist_test     single image   0.632437 1.128420e-30    0.769885 1.236196e-52     262\n   indist_test evidential image   0.594358 2.049839e-26    0.744661 1.473760e-47     262\n   indist_test  mcdropout image   0.336546 2.334380e-08    0.522461 9.414734e-20     262\n   indist_test        tta image   0.443189 4.954075e-14    0.696026 2.783395e-39     262\n   indist_test     hybrid image   0.511522 7.151267e-19    0.699252 8.863173e-40     262\n           val     single image   0.540371 3.446994e-21    0.740572 1.284519e-46     261\n           val evidential image   0.472495 6.400227e-16    0.678631 1.412829e-36     261\n           val  mcdropout image   0.214457 4.853591e-04    0.456821 7.332710e-15     261\n           val        tta image   0.279325 4.594382e-06    0.606203 1.407383e-27     261\n           val     hybrid image   0.420609 1.299677e-12    0.674279 5.797294e-36     261\n\n======================================================================\nAUSE / SPARSIFICATION\n======================================================================\n         split     signal     ause\n           val     single 0.019901\n           val evidential 0.019912\n           val  mcdropout 0.019379\n           val        tta 0.013829\n           val     hybrid 0.019845\n   indist_test     single 0.050552\n   indist_test evidential 0.050606\n   indist_test  mcdropout 0.051238\n   indist_test        tta 0.029703\n   indist_test     hybrid 0.050556\nbenchmark_test     single 0.138652\nbenchmark_test evidential 0.137828\nbenchmark_test  mcdropout 0.137458\nbenchmark_test        tta 0.074666\nbenchmark_test     hybrid 0.137915\n\n======================================================================\nSPATIAL OVERLAP (uncertainty-error Dice)\n======================================================================\n         split     signal  threshold  mean_overlap_dice  std_overlap_dice\nbenchmark_test     single        0.1           0.140942          0.135267\nbenchmark_test     single        0.2           0.098722          0.129737\nbenchmark_test     single        0.3           0.078149          0.123537\nbenchmark_test evidential        0.1           0.140902          0.137060\nbenchmark_test evidential        0.2           0.098467          0.129744\nbenchmark_test evidential        0.3           0.078071          0.123525\nbenchmark_test  mcdropout        0.1           0.136075          0.126820\nbenchmark_test  mcdropout        0.2           0.094949          0.120271\nbenchmark_test  mcdropout        0.3           0.076045          0.116846\nbenchmark_test        tta        0.1           0.136290          0.138508\nbenchmark_test        tta        0.2           0.093059          0.125420\nbenchmark_test        tta        0.3           0.075008          0.122997\nbenchmark_test     hybrid        0.1           0.140931          0.136911\nbenchmark_test     hybrid        0.2           0.098506          0.129741\nbenchmark_test     hybrid        0.3           0.078075          0.123519\n   indist_test     single        0.1           0.121275          0.114221\n   indist_test     single        0.2           0.074868          0.091541\n   indist_test     single        0.3           0.056297          0.083243\n   indist_test evidential        0.1           0.121431          0.114662\n   indist_test evidential        0.2           0.074866          0.091536\n   indist_test evidential        0.3           0.056295          0.083238\n   indist_test  mcdropout        0.1           0.121861          0.115886\n   indist_test  mcdropout        0.2           0.075016          0.090629\n   indist_test  mcdropout        0.3           0.056666          0.082217\n   indist_test        tta        0.1           0.120390          0.123288\n   indist_test        tta        0.2           0.074280          0.094752\n   indist_test        tta        0.3           0.056082          0.086948\n   indist_test     hybrid        0.1           0.121525          0.114819\n   indist_test     hybrid        0.2           0.074869          0.091532\n   indist_test     hybrid        0.3           0.056299          0.083246\n           val     single        0.1           0.125628          0.133256\n           val     single        0.2           0.077785          0.107388\n           val     single        0.3           0.057483          0.092944\n           val evidential        0.1           0.126162          0.136037\n           val evidential        0.2           0.077785          0.107393\n           val evidential        0.3           0.057484          0.092948\n           val  mcdropout        0.1           0.121519          0.123741\n           val  mcdropout        0.2           0.076347          0.104542\n           val  mcdropout        0.3           0.056650          0.091239\n           val        tta        0.1           0.115647          0.124711\n           val        tta        0.2           0.073963          0.105732\n           val        tta        0.3           0.056384          0.096734\n           val     hybrid        0.1           0.126132          0.135809\n           val     hybrid        0.2           0.077796          0.107432\n           val     hybrid        0.3           0.057489          0.092972\n\n======================================================================\nDECOMPOSITION-CONSISTENCY ANALYSIS\n======================================================================\n              axis            subgroup     signal   n  pearson_r       p_value\n              size small_or_diminutive     single 295   0.423936  2.689470e-14\n              size small_or_diminutive evidential 295   0.272913  1.953626e-06\n              size small_or_diminutive  mcdropout 295   0.164665  4.574172e-03\n              size small_or_diminutive        tta 295   0.426202  1.895882e-14\n              size small_or_diminutive     hybrid 295   0.171807  3.072460e-03\n              size               large     single 864   0.613043  2.653948e-90\n              size               large evidential 864   0.573782  8.484973e-77\n              size               large  mcdropout 864   0.373883  4.710465e-30\n              size               large        tta 864   0.703510 5.397895e-130\n              size               large     hybrid 864   0.459701  2.151273e-46\ndistribution_shift     in_distribution     single 523   0.589705  2.664317e-50\ndistribution_shift     in_distribution evidential 523   0.536176  2.916019e-40\ndistribution_shift     in_distribution  mcdropout 523   0.261506  1.262529e-09\ndistribution_shift     in_distribution        tta 523   0.344791  4.801039e-16\ndistribution_shift     in_distribution     hybrid 523   0.471350  2.769639e-30\ndistribution_shift out_of_distribution     single 636   0.512566  6.726070e-44\ndistribution_shift out_of_distribution evidential 636   0.419772  1.544036e-28\ndistribution_shift out_of_distribution  mcdropout 636   0.219062  2.383100e-08\ndistribution_shift out_of_distribution        tta 636   0.602490  4.296615e-64\ndistribution_shift out_of_distribution     hybrid 636   0.485329  6.841699e-39\n\n======================================================================\nSILENT FAILURE SUMMARY\n======================================================================\n         split   n  n_failures  failure_rate  single_silent_n  evidential_silent_n  mcdropout_silent_n  tta_silent_n  hybrid_silent_n  any_silent_n  all_silent_n\nbenchmark_test 636          64        0.1006               15                   15                  18             6                5            19            16\n   indist_test 262           2        0.0076                0                    0                   0             0                0             0             0\n           val 261           1        0.0038                0                    0                   0             0                0             0             0\n\n======================================================================\nDONE. All outputs in: /kaggle/working/paper1_metrics\n======================================================================\n","output_type":"stream"}],"execution_count":2},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##new code regarding the tables and the figures here ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:52:49.864379Z","iopub.execute_input":"2026-09-07T13:52:49.864962Z","iopub.status.idle":"2026-09-07T13:52:49.874662Z","shell.execute_reply.started":"2026-09-07T13:52:49.864923Z","shell.execute_reply":"2026-09-07T13:52:49.871544Z"}},"outputs":[],"execution_count":1},{"cell_type":"code","source":"\"\"\"\nPolypSeg Paper 1 — TABLES AND FIGURES ASSEMBLY (STANDALONE)\n==============================================================================\nFully self-contained. Does not import or depend on any other script in this\nproject -- only reads the CSV files already produced by:\n    (a) the 8-encoder ablation evaluation script (leaderboard_summary.csv,\n        full_encoder_comparison.csv)\n    (b) the hybrid-model uncertainty metric suite script (paper1_metrics/*.csv)\n\nEvery reader function is DEFENSIVE: it inspects the actual columns/values\npresent in each CSV at runtime rather than assuming one fixed schema, and\nprints exactly what it found so a schema mismatch is diagnosed immediately\ninstead of silently producing an empty table. If a table/figure genuinely\ncannot be built from what's available, it is skipped with a clear message\nand the script continues -- it never crashes on one missing piece.\n\nRun this as: python paper1_assets_builder_standalone.py\n(No other script needs to be run first, other than the two upstream\npipelines that produced the CSV inputs themselves.)\n\"\"\"\n\nimport os\nimport warnings\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib\nmatplotlib.use('Agg')\nimport matplotlib.pyplot as plt\n\nwarnings.filterwarnings(\"ignore\")\n\n# ============================================================\n# CONFIG -- EDIT THESE PATHS TO MATCH YOUR KAGGLE SESSION\n# ============================================================\n\nCFG = {\n    \"leaderboard_csv\": \"/kaggle/working/leaderboard_summary.csv\",\n    \"full_encoder_csv\": \"/kaggle/working/full_encoder_comparison.csv\",\n    \"metrics_dir\": \"/kaggle/working/paper1_metrics\",\n    \"output_dir\": \"/kaggle/working/paper1_assets\",\n\n    \"dpi\": 200,\n    \"signal_order\": [\"single\", \"evidential\", \"mcdropout\", \"tta\", \"hybrid\"],\n    \"signal_labels\": {\n        \"single\": \"Single\", \"evidential\": \"Evidential\",\n        \"mcdropout\": \"MC-Dropout\", \"tta\": \"TTA\", \"hybrid\": \"Hybrid\",\n    },\n    \"split_order\": [\"val\", \"indist_test\", \"benchmark_test\"],\n    \"split_labels\": {\n        \"val\": \"Validation\", \"indist_test\": \"In-Distribution Test\",\n        \"benchmark_test\": \"Benchmark (Unseen)\",\n    },\n\n    # Known benchmark-only dataset display names, used to recognize\n    # per-dataset rows in full_encoder_comparison.csv regardless of\n    # exactly how the 'split' or 'dataset' column encodes them.\n    \"benchmark_dataset_names\": [\"ColonDB\", \"CVC-ColonDB\", \"ETIS\", \"ETIS-Larib\",\n                                 \"CVC-300\", \"CVC300\"],\n\n    \"qualitative_cases\": [],\n}\n\nSIGNAL_COLORS = {\n    \"single\": \"#4C72B0\", \"evidential\": \"#DD8452\", \"mcdropout\": \"#55A868\",\n    \"tta\": \"#C44E52\", \"hybrid\": \"#8172B3\",\n}\n\n\n# ============================================================\n# GENERIC HELPERS\n# ============================================================\n\ndef safe_read_csv(path, label):\n    if not os.path.exists(path):\n        print(f\"  \\u26a0 SKIPPED: {label} -- file not found at {path}\")\n        return None\n    df = pd.read_csv(path)\n    print(f\"  \\u2713 Loaded {label}: {len(df)} rows, columns={list(df.columns)}\")\n    return df\n\n\ndef save_table(df, out_dir, name, index=False):\n    if df is None or len(df) == 0:\n        print(f\"    \\u26a0 {name}: nothing to save (empty result)\")\n        return\n    csv_path = out_dir / f\"{name}.csv\"\n    md_path = out_dir / f\"{name}.md\"\n    df.to_csv(csv_path, index=index)\n    try:\n        md_path.write_text(df.to_markdown(index=index))\n    except ImportError:\n        md_path.write_text(df.to_string(index=index))\n    print(f\"    -> saved {csv_path.name} / {md_path.name}\")\n\n\ndef order_signals(df, col='signal'):\n    order = [s for s in CFG['signal_order'] if s in df[col].unique()]\n    df = df.copy()\n    df[col] = pd.Categorical(df[col], categories=order, ordered=True)\n    return df.sort_values(col)\n\n\ndef order_splits(df, col='split'):\n    order = [s for s in CFG['split_order'] if s in df[col].unique()]\n    df = df.copy()\n    df[col] = pd.Categorical(df[col], categories=order, ordered=True)\n    return df.sort_values(col)\n\n\ndef pretty_signal(s):\n    return CFG['signal_labels'].get(s, s)\n\n\ndef pretty_split(s):\n    return CFG['split_labels'].get(s, s)\n\n\n# ============================================================\n# TABLE 1 & FIGURE 1 -- ENCODER ABLATION LEADERBOARD\n# ============================================================\n\ndef build_table1_and_figure1(tables_dir, figures_dir):\n    print(\"\\n--- Table 1 / Figure 1: Encoder ablation leaderboard ---\")\n    lb = safe_read_csv(CFG['leaderboard_csv'], \"encoder leaderboard\")\n    if lb is None:\n        return None\n\n    dice_col = next((c for c in lb.columns if 'bench' in c.lower() and 'dice' in c.lower()), None)\n    if dice_col is None:\n        print(\"  \\u26a0 Could not find a 'Bench...Dice' column; printing available columns:\")\n        print(f\"    {list(lb.columns)}\")\n        return None\n\n    table1 = lb.copy()\n    table1 = table1.sort_values(dice_col, ascending=False).reset_index(drop=True)\n    if 'Rank' in table1.columns:\n        table1 = table1.drop(columns=['Rank'])\n    table1.insert(0, 'Rank', range(1, len(table1) + 1))\n    save_table(table1, tables_dir, \"table1_encoder_leaderboard\")\n\n    encoder_col = next((c for c in table1.columns if c.lower() == 'encoder'), table1.columns[1])\n    param_col = next((c for c in table1.columns if 'param' in c.lower()), None)\n\n    fig, ax = plt.subplots(figsize=(7, 5))\n    if param_col is not None:\n        ax.scatter(table1[param_col], table1[dice_col], s=60, color='#4C72B0')\n        for _, row in table1.iterrows():\n            ax.annotate(str(row[encoder_col]), (row[param_col], row[dice_col]),\n                        fontsize=8, xytext=(4, 4), textcoords='offset points')\n        ax.set_xlabel('Parameters (M)')\n        ax.set_ylabel('Benchmark (Unseen) Dice')\n        ax.set_title('Encoder size vs. out-of-distribution generalization')\n    else:\n        ax.barh(table1[encoder_col].astype(str), table1[dice_col], color='#4C72B0')\n        ax.invert_yaxis()\n        ax.set_xlabel('Benchmark (Unseen) Dice')\n        ax.set_title('Encoder ablation: benchmark generalization')\n    plt.tight_layout()\n    fig_path = figures_dir / 'figure1_encoder_params_vs_dice.png'\n    plt.savefig(fig_path, dpi=CFG['dpi'])\n    plt.close()\n    print(f\"    -> saved {fig_path.name}\")\n    return table1\n\n\n# ============================================================\n# TABLE 2 -- ENCODER PER-DATASET BREAKDOWN (BENCHMARK ONLY)\n# ============================================================\n\ndef build_table2(tables_dir):\n    \"\"\"\n    Defensive against multiple possible schemas for\n    full_encoder_comparison.csv:\n      Schema A: a 'split' column encodes per-dataset rows as e.g.\n                'benchmark_test_CVC-ColonDB'.\n      Schema B: a 'split' column is just 'benchmark_test' / 'benchmark'\n                for ALL benchmark rows, and a separate 'dataset' column\n                holds the actual per-dataset name (e.g. 'CVC-ColonDB').\n    This function tries A first, falls back to B, and prints the raw\n    unique values it saw if neither pattern matches, so the mismatch\n    is diagnosable in one glance rather than a silent empty table.\n    \"\"\"\n    print(\"\\n--- Table 2: Encoder per-benchmark-dataset breakdown ---\")\n    full = safe_read_csv(CFG['full_encoder_csv'], \"full encoder comparison\")\n    if full is None:\n        return None\n\n    if 'split' not in full.columns:\n        print(\"  \\u26a0 No 'split' column in full_encoder_comparison.csv; cannot proceed.\")\n        return None\n\n    print(f\"    Unique 'split' values: {sorted(full['split'].astype(str).unique())[:20]}\")\n    if 'dataset' in full.columns:\n        print(f\"    Unique 'dataset' values: {sorted(full['dataset'].astype(str).unique())[:20]}\")\n\n    dice_col = 'dice' if 'dice' in full.columns else next(\n        (c for c in full.columns if 'dice' in c.lower()), None)\n    if dice_col is None:\n        print(\"  \\u26a0 No dice-like column found.\")\n        return None\n\n    bench_names = CFG['benchmark_dataset_names']\n\n    # --- Schema A: per-dataset name embedded in 'split' string ---\n    mask_a = full['split'].astype(str).apply(\n        lambda s: any(name in s for name in bench_names))\n    rows_a = full[mask_a].copy()\n\n    pivot = None\n    if len(rows_a) > 0:\n        def extract_dataset_name(s):\n            for name in bench_names:\n                if name in s:\n                    return name\n            return s\n        rows_a['dataset_extracted'] = rows_a['split'].astype(str).apply(extract_dataset_name)\n        encoder_col = 'encoder' if 'encoder' in rows_a.columns else next(\n            (c for c in rows_a.columns if 'encoder' in c.lower()), None)\n        if encoder_col is not None:\n            pivot = rows_a.pivot_table(index=encoder_col, columns='dataset_extracted',\n                                        values=dice_col, aggfunc='mean')\n\n    # --- Schema B: 'dataset' column already holds the per-dataset name,\n    # filtered by a benchmark-like split value ---\n    if pivot is None and 'dataset' in full.columns:\n        mask_b = full['dataset'].astype(str).apply(\n            lambda s: any(name in s for name in bench_names))\n        rows_b = full[mask_b].copy()\n        if len(rows_b) > 0:\n            encoder_col = 'encoder' if 'encoder' in rows_b.columns else next(\n                (c for c in rows_b.columns if 'encoder' in c.lower()), None)\n            if encoder_col is not None:\n                pivot = rows_b.pivot_table(index=encoder_col, columns='dataset',\n                                            values=dice_col, aggfunc='mean')\n\n    if pivot is None:\n        print(\"  \\u26a0 Neither schema matched -- no per-dataset benchmark rows \"\n              \"could be identified. Inspect the printed unique values above \"\n              \"and adjust CFG['benchmark_dataset_names'] if the naming differs.\")\n        return None\n\n    pivot = pivot.reset_index().rename(columns={pivot.index.name or 'index': 'Encoder'})\n    sort_col = next((c for c in pivot.columns if 'colondb' in c.lower()), None)\n    if sort_col:\n        pivot = pivot.sort_values(sort_col, ascending=False).reset_index(drop=True)\n    save_table(pivot, tables_dir, \"table2_encoder_per_dataset\")\n    return pivot\n\n\n# ============================================================\n# TABLE 3 & FIGURE 2 -- HYBRID MODEL SEGMENTATION PERFORMANCE\n# ============================================================\n\ndef build_table3_and_figure2(per_sample, tables_dir, figures_dir):\n    print(\"\\n--- Table 3 / Figure 2: Hybrid model segmentation performance ---\")\n    if per_sample is None:\n        return None\n\n    rows = []\n    for split in CFG['split_order']:\n        sd = per_sample[per_sample['split'] == split]\n        if len(sd) == 0:\n            continue\n        for dataset, gd in sd.groupby('dataset'):\n            rows.append(dict(\n                Split=pretty_split(split), Dataset=dataset, n=len(gd),\n                Dice=gd['dice'].mean(), IoU=gd['iou'].mean(),\n                Precision=gd['precision'].mean(), Recall=gd['recall'].mean(),\n                Complete_Misses=int(gd['complete_miss'].sum()) if 'complete_miss' in gd else 0,\n                False_Alarms=int(gd['false_alarm_on_empty'].sum()) if 'false_alarm_on_empty' in gd else 0,\n            ))\n        rows.append(dict(\n            Split=pretty_split(split), Dataset='ALL', n=len(sd),\n            Dice=sd['dice'].mean(), IoU=sd['iou'].mean(),\n            Precision=sd['precision'].mean(), Recall=sd['recall'].mean(),\n            Complete_Misses=int(sd['complete_miss'].sum()) if 'complete_miss' in sd else 0,\n            False_Alarms=int(sd['false_alarm_on_empty'].sum()) if 'false_alarm_on_empty' in sd else 0,\n        ))\n    table3 = pd.DataFrame(rows)\n    save_table(table3, tables_dir, \"table3_hybrid_segmentation_performance\")\n\n    fig, ax = plt.subplots(figsize=(6, 5))\n    data = [per_sample[per_sample['split'] == s]['dice'].dropna().values\n            for s in CFG['split_order'] if s in per_sample['split'].unique()]\n    labels = [pretty_split(s) for s in CFG['split_order'] if s in per_sample['split'].unique()]\n    bp = ax.boxplot(data, labels=labels, patch_artist=True, showfliers=True)\n    palette = ['#4C72B0', '#55A868', '#C44E52']\n    for patch, color in zip(bp['boxes'], palette):\n        patch.set_facecolor(color)\n        patch.set_alpha(0.6)\n    ax.set_ylabel('Dice score')\n    ax.set_title('Segmentation quality distribution by split')\n    ax.axhline(0.5, color='gray', linestyle='--', linewidth=1, alpha=0.7,\n               label='Failure threshold (Dice = 0.5)')\n    ax.legend(fontsize=8)\n    plt.tight_layout()\n    fig_path = figures_dir / 'figure2_dice_distribution_by_split.png'\n    plt.savefig(fig_path, dpi=CFG['dpi'])\n    plt.close()\n    print(f\"    -> saved {fig_path.name}\")\n    return table3\n\n\n# ============================================================\n# TABLE 4 & FIGURE 3 -- CALIBRATION\n# ============================================================\n\ndef build_table4_and_figure3(tables_dir, figures_dir):\n    print(\"\\n--- Table 4 / Figure 3: Calibration (ECE, Brier) ---\")\n    calib = safe_read_csv(Path(CFG['metrics_dir']) / 'calibration_metrics.csv',\n                           \"calibration metrics\")\n    if calib is None:\n        return None\n\n    calib = order_splits(order_signals(calib))\n    table4 = calib.copy()\n    table4['signal'] = table4['signal'].map(pretty_signal)\n    table4['split'] = table4['split'].map(pretty_split)\n    table4 = table4.rename(columns={\n        'signal': 'Signal', 'split': 'Split', 'ece': 'ECE',\n        'brier': 'Brier', 'n_pixels': 'N (pixels, subsampled)'})\n    save_table(table4, tables_dir, \"table4_calibration\")\n\n    rel = safe_read_csv(Path(CFG['metrics_dir']) / 'reliability_diagram_data.csv',\n                         \"reliability diagram data\")\n    if rel is not None and len(rel) > 0:\n        rel_bench = rel[rel['split'] == 'benchmark_test']\n        signals_present = [s for s in CFG['signal_order'] if s in rel_bench['signal'].unique()]\n        if len(signals_present) > 0:\n            fig, axes = plt.subplots(1, len(signals_present),\n                                      figsize=(3.2 * len(signals_present), 3.5), sharey=True)\n            if len(signals_present) == 1:\n                axes = [axes]\n            for ax, sig in zip(axes, signals_present):\n                sub = rel_bench[rel_bench['signal'] == sig]\n                ax.plot([0, 1], [0, 1], 'k--', alpha=0.5, linewidth=1)\n                ax.plot(sub['confidence'], sub['accuracy'], 'o-',\n                         color=SIGNAL_COLORS.get(sig, 'gray'), markersize=4)\n                ax.set_title(pretty_signal(sig), fontsize=10)\n                ax.set_xlabel('Confidence')\n                ax.set_xlim(0, 1); ax.set_ylim(0, 1)\n                ax.grid(alpha=0.2)\n            axes[0].set_ylabel('Accuracy')\n            fig.suptitle('Reliability diagrams — Benchmark (unseen) split', y=1.03)\n            fig.text(0.5, -0.02,\n                     \"Note: Single, Evidential, and Hybrid share the same underlying\\n\"\n                     \"deterministic prediction (dropout off), so their calibration curves are\\n\"\n                     \"expected to be near-identical here; only their uncertainty MAPS differ,\\n\"\n                     \"which is what Figures 4-5 and Tables 5-7 evaluate.\",\n                     ha='center', fontsize=7, style='italic', color='dimgray')\n            plt.tight_layout()\n            fig_path = figures_dir / 'figure3_reliability_diagrams.png'\n            plt.savefig(fig_path, dpi=CFG['dpi'], bbox_inches='tight')\n            plt.close()\n            print(f\"    -> saved {fig_path.name}\")\n    return table4\n\n\n# ============================================================\n# TABLE 5 -- CORRELATION + RANKING\n# ============================================================\n\ndef build_table5(tables_dir):\n    print(\"\\n--- Table 5: Correlation and ranking (Pearson/Spearman + AUSE) ---\")\n    corr = safe_read_csv(Path(CFG['metrics_dir']) / 'correlation_metrics.csv',\n                          \"correlation metrics\")\n    ause = safe_read_csv(Path(CFG['metrics_dir']) / 'ause_metrics.csv', \"AUSE metrics\")\n    if corr is None:\n        return None\n\n    corr = order_splits(order_signals(corr))\n    table5 = corr[['split', 'signal', 'level', 'pearson_r', 'spearman_r', 'n']].copy()\n\n    if ause is not None:\n        ause_pixel = ause.copy()\n        ause_pixel['level'] = 'pixel'\n        table5 = table5.merge(\n            ause_pixel[['split', 'signal', 'level', 'ause']],\n            on=['split', 'signal', 'level'], how='left')\n    else:\n        table5['ause'] = np.nan\n\n    table5['signal'] = table5['signal'].map(pretty_signal)\n    table5['split'] = table5['split'].map(pretty_split)\n    table5 = table5.rename(columns={\n        'signal': 'Signal', 'split': 'Split', 'level': 'Level',\n        'pearson_r': 'Pearson r', 'spearman_r': 'Spearman r',\n        'ause': 'AUSE', 'n': 'N'})\n    save_table(table5, tables_dir, \"table5_correlation_ranking\")\n    return table5\n\n\n# ============================================================\n# FIGURE 4 -- SPARSIFICATION CURVES\n# ============================================================\n\ndef build_figure4(figures_dir):\n    print(\"\\n--- Figure 4: Sparsification curves ---\")\n    curve = safe_read_csv(Path(CFG['metrics_dir']) / 'sparsification_curve_data.csv',\n                           \"sparsification curve data\")\n    if curve is None or len(curve) == 0:\n        return\n\n    splits_present = [s for s in CFG['split_order'] if s in curve['split'].unique()]\n    if len(splits_present) == 0:\n        return\n    fig, axes = plt.subplots(1, len(splits_present),\n                              figsize=(5 * len(splits_present), 4.2), sharey=False)\n    if len(splits_present) == 1:\n        axes = [axes]\n    for ax, split in zip(axes, splits_present):\n        sub = curve[curve['split'] == split]\n        oracle_done = False\n        for sig in CFG['signal_order']:\n            sig_sub = sub[sub['signal'] == sig]\n            if len(sig_sub) == 0:\n                continue\n            ax.plot(sig_sub['fraction_removed'], sig_sub['retained_error_uncertainty'],\n                     label=pretty_signal(sig), color=SIGNAL_COLORS.get(sig, 'gray'),\n                     linewidth=1.8)\n            if not oracle_done:\n                ax.plot(sig_sub['fraction_removed'], sig_sub['retained_error_oracle'],\n                         'k--', label='Oracle', alpha=0.6, linewidth=1.2)\n                oracle_done = True\n        ax.set_title(pretty_split(split))\n        ax.set_xlabel('Fraction of pixels removed (most uncertain first)')\n        ax.grid(alpha=0.2)\n    axes[0].set_ylabel('Mean retained error')\n    axes[-1].legend(fontsize=8, loc='upper right')\n    plt.tight_layout()\n    fig_path = figures_dir / 'figure4_sparsification_curves.png'\n    plt.savefig(fig_path, dpi=CFG['dpi'], bbox_inches='tight')\n    plt.close()\n    print(f\"    -> saved {fig_path.name}\")\n\n\n# ============================================================\n# TABLE 6 -- SPATIAL OVERLAP\n# ============================================================\n\ndef build_table6(tables_dir):\n    print(\"\\n--- Table 6: Spatial overlap (uncertainty-error Dice) ---\")\n    overlap = safe_read_csv(Path(CFG['metrics_dir']) / 'spatial_overlap_metrics.csv',\n                             \"spatial overlap metrics\")\n    if overlap is None:\n        return None\n\n    overlap = order_splits(order_signals(overlap))\n    table6 = overlap.copy()\n    table6['signal'] = table6['signal'].map(pretty_signal)\n    table6['split'] = table6['split'].map(pretty_split)\n    table6['threshold'] = (table6['threshold'] * 100).astype(int).astype(str) + '%'\n    table6 = table6.rename(columns={\n        'signal': 'Signal', 'split': 'Split', 'threshold': 'Top-X% Flagged',\n        'mean_overlap_dice': 'Mean Overlap Dice', 'std_overlap_dice': 'Std'})\n    save_table(table6, tables_dir, \"table6_spatial_overlap\")\n    return table6\n\n\n# ============================================================\n# TABLE 7 & FIGURE 5 -- DECOMPOSITION CONSISTENCY\n# ============================================================\n\ndef build_table7_and_figure5(tables_dir, figures_dir):\n    print(\"\\n--- Table 7 / Figure 5: Decomposition-consistency analysis ---\")\n    decomp = safe_read_csv(Path(CFG['metrics_dir']) / 'decomposition_subgroup_table.csv',\n                            \"decomposition subgroup table\")\n    if decomp is None or len(decomp) == 0:\n        return None\n\n    decomp = order_signals(decomp)\n    table7 = decomp.copy()\n    table7['signal'] = table7['signal'].map(pretty_signal)\n    table7 = table7.rename(columns={\n        'axis': 'Axis', 'subgroup': 'Subgroup', 'signal': 'Signal',\n        'n': 'N', 'pearson_r': 'Pearson r', 'p_value': 'p-value'})\n    save_table(table7, tables_dir, \"table7_decomposition_consistency\")\n\n    fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))\n\n    size_df = decomp[decomp['axis'] == 'size']\n    if len(size_df) > 0:\n        pivot = size_df.pivot(index='signal', columns='subgroup', values='pearson_r')\n        pivot = pivot.reindex([s for s in CFG['signal_order'] if s in pivot.index])\n        pivot.index = [pretty_signal(s) for s in pivot.index]\n        pivot.plot(kind='bar', ax=axes[0])\n        axes[0].set_title('Correlation with error: polyp size subgroups')\n        axes[0].set_ylabel(\"Pearson r (uncertainty vs. 1 - Dice)\")\n        axes[0].axhline(0, color='k', linewidth=0.5)\n        axes[0].tick_params(axis='x', rotation=30)\n\n    shift_df = decomp[decomp['axis'] == 'distribution_shift']\n    if len(shift_df) > 0:\n        pivot = shift_df.pivot(index='signal', columns='subgroup', values='pearson_r')\n        pivot = pivot.reindex([s for s in CFG['signal_order'] if s in pivot.index])\n        pivot.index = [pretty_signal(s) for s in pivot.index]\n        pivot.plot(kind='bar', ax=axes[1])\n        axes[1].set_title('Correlation with error: distribution shift subgroups')\n        axes[1].set_ylabel(\"Pearson r (uncertainty vs. 1 - Dice)\")\n        axes[1].axhline(0, color='k', linewidth=0.5)\n        axes[1].tick_params(axis='x', rotation=30)\n\n    plt.tight_layout()\n    fig_path = figures_dir / 'figure5_decomposition_consistency.png'\n    plt.savefig(fig_path, dpi=CFG['dpi'], bbox_inches='tight')\n    plt.close()\n    print(f\"    -> saved {fig_path.name}\")\n    return table7\n\n\n# ============================================================\n# TABLE 8 -- SILENT FAILURE SUMMARY\n# ============================================================\n\ndef build_table8(tables_dir):\n    print(\"\\n--- Table 8: Silent failure summary ---\")\n    sf = safe_read_csv(Path(CFG['metrics_dir']) / 'silent_failure_summary.csv',\n                        \"silent failure summary\")\n    if sf is None:\n        return None\n\n    sf = order_splits(sf)\n    table8 = sf.copy()\n    table8['split'] = table8['split'].map(pretty_split)\n    rename_map = {'split': 'Split', 'n': 'N', 'n_failures': 'N Failures',\n                  'failure_rate': 'Failure Rate'}\n    for sig in CFG['signal_order']:\n        col = f'{sig}_silent_n'\n        if col in table8.columns:\n            rename_map[col] = f'{pretty_signal(sig)} Silent (n)'\n    if 'any_silent_n' in table8.columns:\n        rename_map['any_silent_n'] = 'Silent \\u2265 1 Signal (n)'\n    if 'all_silent_n' in table8.columns:\n        rename_map['all_silent_n'] = 'Silent All Signals (n)'\n    table8 = table8.rename(columns=rename_map)\n    save_table(table8, tables_dir, \"table8_silent_failure\")\n    return table8\n\n\n# ============================================================\n# FIGURE 6 -- QUALITATIVE CASE STUDY (template only)\n# ============================================================\n\ndef build_figure6(figures_dir):\n    print(\"\\n--- Figure 6: Qualitative case study ---\")\n    cases = CFG['qualitative_cases']\n    if not cases:\n        print(\"  \\u26a0 SKIPPED: no qualitative_cases configured. Populate \"\n              \"CFG['qualitative_cases'] with stem/dataset/img_path/mask_path \"\n              \"and precomputed uncertainty maps to enable this figure.\")\n        return\n    print(\"  NOTE: template only -- wire up actual image/mask/uncertainty \"\n          \"array loading here once specific cases are chosen.\")\n\n\n# ============================================================\n# MAIN\n# ============================================================\n\ndef main(cfg=CFG):\n    out_dir = Path(cfg['output_dir'])\n    tables_dir = out_dir / 'tables'\n    figures_dir = out_dir / 'figures'\n    tables_dir.mkdir(parents=True, exist_ok=True)\n    figures_dir.mkdir(parents=True, exist_ok=True)\n\n    print(\"=\"*70)\n    print(\"LOADING PER-SAMPLE DATA\")\n    print(\"=\"*70)\n    per_sample = safe_read_csv(Path(cfg['metrics_dir']) / 'per_sample_results.csv',\n                                \"per-sample results\")\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"BUILDING TABLES AND FIGURES\")\n    print(\"=\"*70)\n\n    build_table1_and_figure1(tables_dir, figures_dir)\n    build_table2(tables_dir)\n    build_table3_and_figure2(per_sample, tables_dir, figures_dir)\n    build_table4_and_figure3(tables_dir, figures_dir)\n    build_table5(tables_dir)\n    build_figure4(figures_dir)\n    build_table6(tables_dir)\n    build_table7_and_figure5(tables_dir, figures_dir)\n    build_table8(tables_dir)\n    build_figure6(figures_dir)\n\n    print(\"\\n\" + \"=\"*70)\n    print(f\"DONE. All tables in {tables_dir}, all figures in {figures_dir}\")\n    print(\"=\"*70)\n\n\nif __name__ == '__main__':\n    main(CFG)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-10T01:04:41.623966Z","iopub.execute_input":"2026-09-10T01:04:41.624934Z","iopub.status.idle":"2026-09-10T01:04:43.05018Z","shell.execute_reply.started":"2026-09-10T01:04:41.624902Z","shell.execute_reply":"2026-09-10T01:04:43.049168Z"}},"outputs":[{"name":"stdout","text":"======================================================================\nLOADING PER-SAMPLE DATA\n======================================================================\n  ⚠ SKIPPED: per-sample results -- file not found at /kaggle/working/paper1_metrics/per_sample_results.csv\n\n======================================================================\nBUILDING TABLES AND FIGURES\n======================================================================\n\n--- Table 1 / Figure 1: Encoder ablation leaderboard ---\n  ⚠ SKIPPED: encoder leaderboard -- file not found at /kaggle/working/leaderboard_summary.csv\n\n--- Table 2: Encoder per-benchmark-dataset breakdown ---\n  ⚠ SKIPPED: full encoder comparison -- file not found at /kaggle/working/full_encoder_comparison.csv\n\n--- Table 3 / Figure 2: Hybrid model segmentation performance ---\n\n--- Table 4 / Figure 3: Calibration (ECE, Brier) ---\n  ⚠ SKIPPED: calibration metrics -- file not found at /kaggle/working/paper1_metrics/calibration_metrics.csv\n\n--- Table 5: Correlation and ranking (Pearson/Spearman + AUSE) ---\n  ⚠ SKIPPED: correlation metrics -- file not found at /kaggle/working/paper1_metrics/correlation_metrics.csv\n  ⚠ SKIPPED: AUSE metrics -- file not found at /kaggle/working/paper1_metrics/ause_metrics.csv\n\n--- Figure 4: Sparsification curves ---\n  ⚠ SKIPPED: sparsification curve data -- file not found at /kaggle/working/paper1_metrics/sparsification_curve_data.csv\n\n--- Table 6: Spatial overlap (uncertainty-error Dice) ---\n  ⚠ SKIPPED: spatial overlap metrics -- file not found at /kaggle/working/paper1_metrics/spatial_overlap_metrics.csv\n\n--- Table 7 / Figure 5: Decomposition-consistency analysis ---\n  ⚠ SKIPPED: decomposition subgroup table -- file not found at /kaggle/working/paper1_metrics/decomposition_subgroup_table.csv\n\n--- Table 8: Silent failure summary ---\n  ⚠ SKIPPED: silent failure summary -- file not found at /kaggle/working/paper1_metrics/silent_failure_summary.csv\n\n--- Figure 6: Qualitative case study ---\n  ⚠ SKIPPED: no qualitative_cases configured. Populate CFG['qualitative_cases'] with stem/dataset/img_path/mask_path and precomputed uncertainty maps to enable this figure.\n\n======================================================================\nDONE. All tables in /kaggle/working/paper1_assets/tables, all figures in /kaggle/working/paper1_assets/figures\n======================================================================\n","output_type":"stream"}],"execution_count":1},{"cell_type":"code","source":"!zip -rq trial1.zip /kaggle/working/paper1_assets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T01:31:19.915713Z","iopub.execute_input":"2026-09-08T01:31:19.916162Z","iopub.status.idle":"2026-09-08T01:31:20.116915Z","shell.execute_reply.started":"2026-09-08T01:31:19.916132Z","shell.execute_reply":"2026-09-08T01:31:20.115836Z"}},"outputs":[],"execution_count":12},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink('trial1.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-08T01:31:47.716627Z","iopub.execute_input":"2026-09-08T01:31:47.717369Z","iopub.status.idle":"2026-09-08T01:31:47.723846Z","shell.execute_reply.started":"2026-09-08T01:31:47.717339Z","shell.execute_reply":"2026-09-08T01:31:47.72325Z"}},"outputs":[{"execution_count":14,"output_type":"execute_result","data":{"text/plain":"/kaggle/working/trial1.zip","text/html":"<a href='trial1.zip' target='_blank'>trial1.zip</a><br>"},"metadata":{}}],"execution_count":14},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}